diff --git a/include/swift/AST/DiagnosticEngine.h b/include/swift/AST/DiagnosticEngine.h index 56c9868b0593a..41e8b666f4c9f 100644 --- a/include/swift/AST/DiagnosticEngine.h +++ b/include/swift/AST/DiagnosticEngine.h @@ -767,6 +767,17 @@ namespace swift { return limitBehaviorUntilSwiftVersion(limit, languageMode); } + /// Limit the diagnostic behavior to warning until the next future + /// language mode. + /// + /// This should be preferred over passing the next major version to + /// `warnUntilSwiftVersion` to make it easier to find and update clients + /// when a new language mode is introduced. + /// + /// This helps stage in fixes for stricter diagnostics as warnings + /// until the next major language version. + InFlightDiagnostic &warnUntilFutureSwiftVersion(); + /// Limit the diagnostic behavior to warning until the specified version. /// /// This helps stage in fixes for stricter diagnostics as warnings diff --git a/include/swift/AST/Expr.h b/include/swift/AST/Expr.h index 7515022f3dc91..d6f4cd78b4401 100644 --- a/include/swift/AST/Expr.h +++ b/include/swift/AST/Expr.h @@ -267,7 +267,7 @@ class alignas(8) Expr : public ASTAllocated { Kind : 2 ); - SWIFT_INLINE_BITFIELD(ClosureExpr, AbstractClosureExpr, 1+1+1+1+1+1+1, + SWIFT_INLINE_BITFIELD(ClosureExpr, AbstractClosureExpr, 1+1+1+1+1+1+1+1, /// True if closure parameters were synthesized from anonymous closure /// variables. HasAnonymousClosureVars : 1, @@ -295,7 +295,12 @@ class alignas(8) Expr : public ASTAllocated { /// isolation checks when it either specifies or inherits isolation /// and was passed as an argument to a function that is not fully /// concurrency checked. - RequiresDynamicIsolationChecking : 1 + RequiresDynamicIsolationChecking : 1, + + /// Whether this closure was type-checked as an argument to a macro. This + /// is only populated after type-checking, and only exists for diagnostic + /// logic. Do not add more uses of this. + IsMacroArgument : 1 ); SWIFT_INLINE_BITFIELD_FULL(BindOptionalExpr, Expr, 16, @@ -4316,6 +4321,7 @@ class ClosureExpr : public AbstractClosureExpr { Bits.ClosureExpr.IsPassedToSendingParameter = false; Bits.ClosureExpr.NoGlobalActorAttribute = false; Bits.ClosureExpr.RequiresDynamicIsolationChecking = false; + Bits.ClosureExpr.IsMacroArgument = false; } SourceRange getSourceRange() const; @@ -4394,6 +4400,17 @@ class ClosureExpr : public AbstractClosureExpr { Bits.ClosureExpr.RequiresDynamicIsolationChecking = value; } + /// Whether this closure was type-checked as an argument to a macro. This + /// is only populated after type-checking, and only exists for diagnostic + /// logic. Do not add more uses of this. + bool isMacroArgument() const { + return Bits.ClosureExpr.IsMacroArgument; + } + + void setIsMacroArgument(bool value = true) { + Bits.ClosureExpr.IsMacroArgument = value; + } + /// Determine whether this closure expression has an /// explicitly-specified result type. bool hasExplicitResultType() const { return ArrowLoc.isValid(); } diff --git a/include/swift/Basic/Version.h b/include/swift/Basic/Version.h index 092a4e626e518..a97ef7eec93b2 100644 --- a/include/swift/Basic/Version.h +++ b/include/swift/Basic/Version.h @@ -131,6 +131,13 @@ class Version { /// SWIFT_VERSION_MINOR. static Version getCurrentLanguageVersion(); + /// Returns a major version to represent the next future language mode. This + /// exists to make it easier to find and update clients when a new language + /// mode is added. + static constexpr unsigned getFutureMajorLanguageVersion() { + return 7; + } + // List of backward-compatibility versions that we permit passing as // -swift-version static std::array getValidEffectiveVersions() { diff --git a/lib/AST/DiagnosticEngine.cpp b/lib/AST/DiagnosticEngine.cpp index c487fcdf60ada..b8b2ec9b55ffd 100644 --- a/lib/AST/DiagnosticEngine.cpp +++ b/lib/AST/DiagnosticEngine.cpp @@ -452,7 +452,7 @@ InFlightDiagnostic::limitBehaviorUntilSwiftVersion( // version. We do this before limiting the behavior, because // wrapIn will result in the behavior of the wrapping diagnostic. if (limit >= DiagnosticBehavior::Warning) { - if (majorVersion > 6) { + if (majorVersion >= version::Version::getFutureMajorLanguageVersion()) { wrapIn(diag::error_in_a_future_swift_lang_mode); } else { wrapIn(diag::error_in_swift_lang_mode, majorVersion); @@ -472,6 +472,11 @@ InFlightDiagnostic::limitBehaviorUntilSwiftVersion( return *this; } +InFlightDiagnostic &InFlightDiagnostic::warnUntilFutureSwiftVersion() { + using namespace version; + return warnUntilSwiftVersion(Version::getFutureMajorLanguageVersion()); +} + InFlightDiagnostic & InFlightDiagnostic::warnUntilSwiftVersion(unsigned majorVersion) { return limitBehaviorUntilSwiftVersion(DiagnosticBehavior::Warning, diff --git a/lib/Basic/Version.cpp b/lib/Basic/Version.cpp index 94085bf601654..13eb04be0db84 100644 --- a/lib/Basic/Version.cpp +++ b/lib/Basic/Version.cpp @@ -181,16 +181,19 @@ std::optional Version::getEffectiveLanguageVersion() const { static_assert(SWIFT_VERSION_MAJOR == 6, "getCurrentLanguageVersion is no longer correct here"); return Version::getCurrentLanguageVersion(); - case 7: - // Allow version '7' in asserts compilers *only* so that we can start - // testing changes planned for after Swift 6. Note that it's still not - // listed in `Version::getValidEffectiveVersions()`. - // FIXME: When Swift 7 becomes real, remove 'REQUIRES: swift7' from tests - // using '-swift-version 7'. + + // FIXME: When Swift 7 becomes real, remove 'REQUIRES: swift7' from tests + // using '-swift-version 7'. + + case Version::getFutureMajorLanguageVersion(): + // Allow the future language mode version in asserts compilers *only* so + // that we can start testing changes planned for after the current latest + // language mode. Note that it'll not be listed in + // `Version::getValidEffectiveVersions()`. #ifdef NDEBUG LLVM_FALLTHROUGH; #else - return Version{7}; + return Version{Version::getFutureMajorLanguageVersion()}; #endif default: return std::nullopt; diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 05addbf8425f3..7357b76e234dc 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -6053,13 +6053,15 @@ static bool hasCurriedSelf(ConstraintSystem &cs, ConcreteDeclRef callee, static void applyContextualClosureFlags(Expr *expr, bool implicitSelfCapture, bool inheritActorContext, bool isPassedToSendingParameter, - bool requiresDynamicIsolationChecking) { + bool requiresDynamicIsolationChecking, + bool isMacroArg) { if (auto closure = dyn_cast(expr)) { closure->setAllowsImplicitSelfCapture(implicitSelfCapture); closure->setInheritsActorContext(inheritActorContext); closure->setIsPassedToSendingParameter(isPassedToSendingParameter); closure->setRequiresDynamicIsolationChecking( requiresDynamicIsolationChecking); + closure->setIsMacroArgument(isMacroArg); return; } @@ -6067,14 +6069,16 @@ static void applyContextualClosureFlags(Expr *expr, bool implicitSelfCapture, applyContextualClosureFlags(captureList->getClosureBody(), implicitSelfCapture, inheritActorContext, isPassedToSendingParameter, - requiresDynamicIsolationChecking); + requiresDynamicIsolationChecking, + isMacroArg); } if (auto identity = dyn_cast(expr)) { applyContextualClosureFlags(identity->getSubExpr(), implicitSelfCapture, inheritActorContext, isPassedToSendingParameter, - requiresDynamicIsolationChecking); + requiresDynamicIsolationChecking, + isMacroArg); } } @@ -6199,7 +6203,8 @@ ArgumentList *ExprRewriter::coerceCallArguments( }(); auto applyFlagsToArgument = [¶mInfo, - &closuresRequireDynamicIsolationChecking]( + &closuresRequireDynamicIsolationChecking, + &locator]( unsigned paramIdx, Expr *argument) { if (!isClosureLiteralExpr(argument)) return; @@ -6207,11 +6212,13 @@ ArgumentList *ExprRewriter::coerceCallArguments( bool isImplicitSelfCapture = paramInfo.isImplicitSelfCapture(paramIdx); bool inheritsActorContext = paramInfo.inheritsActorContext(paramIdx); bool isPassedToSendingParameter = paramInfo.isSendingParameter(paramIdx); + bool isMacroArg = isExpr(locator.getAnchor()); applyContextualClosureFlags(argument, isImplicitSelfCapture, inheritsActorContext, isPassedToSendingParameter, - closuresRequireDynamicIsolationChecking); + closuresRequireDynamicIsolationChecking, + isMacroArg); }; // Quickly test if any further fix-ups for the argument types are necessary. diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 2a85ba5304ba5..d23f9978059e0 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -2243,11 +2243,12 @@ class ImplicitSelfUsageChecker : public BaseDiagnosticWalker { invalidImplicitSelfShouldOnlyWarn510(base, closure)) { warnUntilVersion.emplace(6); } - // Prior to Swift 7, downgrade to a warning if we're in a macro to preserve - // compatibility with the Swift 6 diagnostic behavior where we previously - // skipped diagnosing. - if (!Ctx.isSwiftVersionAtLeast(7) && isInMacro()) - warnUntilVersion.emplace(7); + // Prior to the next language mode, downgrade to a warning if we're in a + // macro to preserve compatibility with the Swift 6 diagnostic behavior + // where we previously skipped diagnosing. + auto futureVersion = version::Version::getFutureMajorLanguageVersion(); + if (!Ctx.isSwiftVersionAtLeast(futureVersion) && isInMacro()) + warnUntilVersion.emplace(futureVersion); auto diag = Ctx.Diags.diagnose(loc, ID, std::move(Args)...); if (warnUntilVersion) diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 5f618001f51b4..46288ed8412e2 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -1249,7 +1249,7 @@ void AttributeChecker::visitAccessControlAttr(AccessControlAttr *attr) { diagnose(attr->getLocation(), diag::access_control_non_objc_open_member, VD) .fixItReplace(attr->getRange(), "public") - .warnUntilSwiftVersion(7); + .warnUntilFutureSwiftVersion(); } } } diff --git a/lib/Sema/TypeCheckAvailability.cpp b/lib/Sema/TypeCheckAvailability.cpp index 59d8056cf16bc..3f4fbfbad0317 100644 --- a/lib/Sema/TypeCheckAvailability.cpp +++ b/lib/Sema/TypeCheckAvailability.cpp @@ -276,16 +276,18 @@ static bool shouldAllowReferenceToUnavailableInSwiftDeclaration( return false; } -// Utility function to help determine if noasync diagnostics are still -// appropriate even if a `DeclContext` returns `false` from `isAsyncContext()`. -static bool shouldTreatDeclContextAsAsyncForDiagnostics(const DeclContext *DC) { - if (auto *D = DC->getAsDecl()) - if (auto *FD = dyn_cast(D)) +/// Retrieve the innermost DeclContext that should be consulted for noasync +/// checking. +static const DeclContext * +getInnermostDeclContextForNoAsync(const DeclContext *DC) { + if (auto *D = DC->getAsDecl()) { + if (auto *FD = dyn_cast(D)) { if (FD->isDeferBody()) // If this is a defer body, we should delegate to its parent. - return shouldTreatDeclContextAsAsyncForDiagnostics(DC->getParent()); - - return DC->isAsyncContext(); + return getInnermostDeclContextForNoAsync(DC->getParent()); + } + } + return DC; } /// A class that walks the AST to find the innermost (i.e., deepest) node that @@ -2758,7 +2760,8 @@ static bool diagnoseDeclAsyncAvailability(const ValueDecl *D, SourceRange R, const Expr *call, const ExportContext &Where) { // If we are not in an (effective) async context, don't check it - if (!shouldTreatDeclContextAsAsyncForDiagnostics(Where.getDeclContext())) + auto *noAsyncDC = getInnermostDeclContextForNoAsync(Where.getDeclContext()); + if (!noAsyncDC->isAsyncContext()) return false; ASTContext &ctx = Where.getDeclContext()->getASTContext(); @@ -2774,14 +2777,27 @@ diagnoseDeclAsyncAvailability(const ValueDecl *D, SourceRange R, } } + // In Swift 6 we previously didn't coerce macro arguments to parameter types, + // so closure arguments may be treated as async in cases where they weren't in + // Swift 6. As such we need to warn if the use is within a closure macro + // argument until the next language mode. + auto shouldWarnUntilFutureVersion = [&]() { + auto *CE = dyn_cast(noAsyncDC); + return CE && CE->isMacroArgument(); + }; + // @available(noasync) spelling if (auto attr = D->getNoAsyncAttr()) { SourceLoc diagLoc = call ? call->getLoc() : R.Start; auto diag = ctx.Diags.diagnose(diagLoc, diag::async_unavailable_decl, D, attr->getMessage()); - diag.warnUntilSwiftVersion(6); - diag.limitBehaviorWithPreconcurrency(DiagnosticBehavior::Warning, - D->preconcurrency()); + if (D->preconcurrency()) { + diag.limitBehavior(DiagnosticBehavior::Warning); + } else if (shouldWarnUntilFutureVersion()) { + diag.warnUntilFutureSwiftVersion(); + } else { + diag.warnUntilSwiftVersion(6); + } if (!attr->getRename().empty()) { fixItAvailableAttrRename(diag, R, D, attr->getRename(), call); @@ -2797,10 +2813,16 @@ diagnoseDeclAsyncAvailability(const ValueDecl *D, SourceRange R, // @_unavailableFromAsync spelling const UnavailableFromAsyncAttr *attr = D->getAttrs().getAttribute(); - SourceLoc diagLoc = call ? call->getLoc() : R.Start; - ctx.Diags - .diagnose(diagLoc, diag::async_unavailable_decl, D, attr->Message) - .warnUntilSwiftVersion(6); + { + SourceLoc diagLoc = call ? call->getLoc() : R.Start; + auto diag = ctx.Diags.diagnose(diagLoc, diag::async_unavailable_decl, D, + attr->Message); + if (shouldWarnUntilFutureVersion()) { + diag.warnUntilFutureSwiftVersion(); + } else { + diag.warnUntilSwiftVersion(6); + } + } D->diagnose(diag::decl_declared_here, D); return true; } diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 9391caf61e011..6bfcb5c43bcd6 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -2676,7 +2676,7 @@ namespace { fromType, toType); if (downgradeToWarning) - diag.warnUntilSwiftVersion(7); + diag.warnUntilFutureSwiftVersion(); } for (auto type : nonSendableTypes) { diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index d12803a8554e0..36e3528563a39 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -5169,7 +5169,8 @@ static bool diagnoseTypeWitnessAvailability( return false; // In Swift 6 and earlier type witness availability diagnostics are warnings. - const unsigned warnBeforeVersion = 7; + using namespace version; + const unsigned warnBeforeVersion = Version::getFutureMajorLanguageVersion(); bool shouldError = ctx.LangOpts.EffectiveLanguageVersion.isVersionAtLeast(warnBeforeVersion); diff --git a/test/Macros/issue-80835.swift b/test/Macros/issue-80835.swift new file mode 100644 index 0000000000000..863e988ea6e34 --- /dev/null +++ b/test/Macros/issue-80835.swift @@ -0,0 +1,84 @@ +// RUN: %empty-directory(%t) +// RUN: %host-build-swift -swift-version 5 -emit-library -o %t/%target-library-name(MacroDefinition) -module-name=MacroDefinition %S/Inputs/syntax_macro_definitions.swift + +// RUN: %target-typecheck-verify-swift -swift-version 6 -load-plugin-library %t/%target-library-name(MacroDefinition) -verify-additional-prefix swift6- +// RUN: %target-typecheck-verify-swift -swift-version 7 -load-plugin-library %t/%target-library-name(MacroDefinition) -verify-additional-prefix swift7- + +// REQUIRES: swift_swift_parser +// REQUIRES: swift7 + +// https://github.com/swiftlang/swift/issues/80835 + +@available(*, noasync) +func noasyncFn() {} + +@_unavailableFromAsync +func unavailableFromAsyncFn() {} // expected-note {{'unavailableFromAsyncFn()' declared here}} + +@freestanding(expression) +macro asyncMacro(_ fn: () async -> Void) = #externalMacro(module: "MacroDefinition", type: "GenericToVoidMacro") + +@freestanding(declaration) +macro asyncMacroDecl(_ fn: () async -> Void) = #externalMacro(module: "MacroDefinition", type: "EmptyDeclarationMacro") + +@attached(peer) +macro AsyncMacro(_ fn: () async -> Void) = #externalMacro(module: "MacroDefinition", type: "WrapperMacro") + +func takesAsyncFn(_ fn: () async -> Void) {} + +#asyncMacro { + defer { + noasyncFn() + // expected-swift7-error@-1 {{global function 'noasyncFn' is unavailable from asynchronous contexts}} + // expected-swift6-warning@-2 {{global function 'noasyncFn' is unavailable from asynchronous contexts; this will be an error in a future Swift language mode}} + } + noasyncFn() + // expected-swift7-error@-1 {{global function 'noasyncFn' is unavailable from asynchronous contexts}} + // expected-swift6-warning@-2 {{global function 'noasyncFn' is unavailable from asynchronous contexts; this will be an error in a future Swift language mode}} + + unavailableFromAsyncFn() + // expected-swift7-error@-1 {{global function 'unavailableFromAsyncFn' is unavailable from asynchronous contexts}} + // expected-swift6-warning@-2 {{global function 'unavailableFromAsyncFn' is unavailable from asynchronous contexts; this will be an error in a future Swift language mode}} + + // This was always an error. + let _: () async -> Void = { + noasyncFn() + // expected-error@-1 {{global function 'noasyncFn' is unavailable from asynchronous contexts}} + } +} + +// This was always an error. +takesAsyncFn { + noasyncFn() + // expected-error@-1 {{global function 'noasyncFn' is unavailable from asynchronous contexts}} +} + +#asyncMacroDecl { + noasyncFn() + // expected-swift7-error@-1 {{global function 'noasyncFn' is unavailable from asynchronous contexts}} + // expected-swift6-warning@-2 {{global function 'noasyncFn' is unavailable from asynchronous contexts; this will be an error in a future Swift language mode}} +} + +typealias Magic = T + +// expected-swift7-error@+2 {{global function 'noasyncFn' is unavailable from asynchronous contexts}} +// expected-swift6-warning@+1 {{global function 'noasyncFn' is unavailable from asynchronous contexts; this will be an error in a future Swift language mode}} +@AsyncMacro({ noasyncFn() }) +func foo() { + #asyncMacro(({ + noasyncFn() + // expected-swift7-error@-1 {{global function 'noasyncFn' is unavailable from asynchronous contexts}} + // expected-swift6-warning@-2 {{global function 'noasyncFn' is unavailable from asynchronous contexts; this will be an error in a future Swift language mode}} + })) + + #asyncMacroDecl(({ + noasyncFn() + // expected-swift7-error@-1 {{global function 'noasyncFn' is unavailable from asynchronous contexts}} + // expected-swift6-warning@-2 {{global function 'noasyncFn' is unavailable from asynchronous contexts; this will be an error in a future Swift language mode}} + })) + + // This was never treated as async. + #asyncMacro({ + noasyncFn() + } as Magic) +}