From 2d0b54bb95630cfe1a2c96dbf1cc546f9499bae4 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Wed, 18 Sep 2024 17:59:25 -0700 Subject: [PATCH 01/15] [ClangImporter] Try to warn about missing types --- .../swift/AST/DiagnosticsClangImporter.def | 10 ++++ lib/ClangImporter/ImportType.cpp | 51 +++++++++++++++---- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/include/swift/AST/DiagnosticsClangImporter.def b/include/swift/AST/DiagnosticsClangImporter.def index 76ceaef0f0f0e..53f5f53033f6e 100644 --- a/include/swift/AST/DiagnosticsClangImporter.def +++ b/include/swift/AST/DiagnosticsClangImporter.def @@ -322,5 +322,15 @@ ERROR(unknown_template_parameter,none, ERROR(type_template_parameter_expected,none, "template parameter '%0' expected to be a type parameter", (StringRef)) +NOTE(bridged_type_not_found_in_module,none, + "could not find type '%0' for bridging; module '%1' may be broken", + (StringRef, StringRef)) + +NOTE(bridged_pointer_type_not_found,none, + "could not find type '%select{UnsafeMutableRawPointer|UnsafeRawPointer|" + "UnsafeMutablePointer|UnsafePointer|AutoreleasingUnsafeMutablePointer}0' " + "for bridging; module 'Swift' may be broken", + (unsigned)) + #define UNDEFINE_DIAGNOSTIC_MACROS #include "DefineDiagnosticMacros.h" diff --git a/lib/ClangImporter/ImportType.cpp b/lib/ClangImporter/ImportType.cpp index 4796757a03a41..40f7be7116df7 100644 --- a/lib/ClangImporter/ImportType.cpp +++ b/lib/ClangImporter/ImportType.cpp @@ -554,6 +554,8 @@ namespace { if (auto wrapped = pointeeType->wrapInPointer(pointerKind)) { return {wrapped, ImportHint::OtherPointer}; } else { + addImportDiagnostic(Diagnostic(diag::bridged_pointer_type_not_found, + pointerKind)); return Type(); } } @@ -611,8 +613,11 @@ namespace { pointerKind = PTK_UnsafeMutablePointer; } - return {pointeeType->wrapInPointer(pointerKind), - ImportHint::None}; + auto pointerType = pointeeType->wrapInPointer(pointerKind); + if (!pointerType) + addImportDiagnostic(Diagnostic(diag::bridged_pointer_type_not_found, + pointerKind)); + return {pointerType, ImportHint::None}; } ImportResult VisitMemberPointer(const clang::MemberPointerType *type) { @@ -1414,7 +1419,8 @@ static Type maybeImportNSErrorOutParameter(ClangImporter::Implementation &impl, static Type maybeImportCFOutParameter(ClangImporter::Implementation &impl, Type importedType, - ImportTypeAttrs attrs) { + ImportTypeAttrs attrs, + llvm::function_ref addImportDiagnostic) { PointerTypeKind PTK; auto elementType = importedType->getAnyPointerElementType(PTK); if (!elementType || PTK != PTK_UnsafeMutablePointer) @@ -1446,6 +1452,9 @@ static Type maybeImportCFOutParameter(ClangImporter::Implementation &impl, pointerKind = PTK_AutoreleasingUnsafeMutablePointer; resultTy = resultTy->wrapInPointer(pointerKind); + if (!resultTy) + addImportDiagnostic(Diagnostic(diag::bridged_pointer_type_not_found, + pointerKind)); return resultTy; } @@ -1483,7 +1492,12 @@ static ImportedType adjustTypeForConcreteImport( importKind != ImportTypeKind::Result) { return {Type(), false}; } - importedType = impl.getNamedSwiftType(impl.getStdlibModule(), "Void"); + importedType = impl.SwiftContext.getVoidType(); + if (!importedType) { + addImportDiagnostic(Diagnostic(diag::bridged_type_not_found_in_module, + "Void", "Swift")); + return {Type(), false}; + } break; case ImportHint::ObjCBridged: @@ -1599,7 +1613,8 @@ static ImportedType adjustTypeForConcreteImport( if (attrs.contains(ImportTypeAttr::CFRetainedOutParameter) || attrs.contains(ImportTypeAttr::CFUnretainedOutParameter)) { if (Type outParamTy = - maybeImportCFOutParameter(impl, importedType, attrs)) { + maybeImportCFOutParameter(impl, importedType, attrs, + addImportDiagnostic)) { importedType = outParamTy; break; } @@ -2220,8 +2235,15 @@ ImportedType ClangImporter::Implementation::importFunctionReturnType( op == clang::OverloadedOperatorKind::OO_MinusEqual || op == clang::OverloadedOperatorKind::OO_StarEqual || op == clang::OverloadedOperatorKind::OO_SlashEqual) && - clangDecl->getReturnType()->isReferenceType()) - return {SwiftContext.getVoidType(), false}; + clangDecl->getReturnType()->isReferenceType()) { + auto voidTy = SwiftContext.getVoidType(); + if (!voidTy) + addImportDiagnostic(clangDecl, + Diagnostic(diag::bridged_type_not_found_in_module, + "Void", "Swift"), + clangDecl->getLocation()); + return {voidTy, false}; + } // Fix up optionality. OptionalTypeKind OptionalityOfReturn; @@ -2388,7 +2410,10 @@ ImportedType ClangImporter::Implementation::importFunctionParamsAndReturnType( auto genericType = findGenericTypeInGenericDecls(*this, templateParamType, genericParams, getImportTypeAttrs(clangDecl), addDiag); - importedType = {genericType->wrapInPointer(pointerKind), false}; + auto genericPointerType = genericType->wrapInPointer(pointerKind); + if (!genericPointerType) + addDiag(Diagnostic(diag::bridged_pointer_type_not_found, pointerKind)); + importedType = {genericPointerType, false}; } else if (!(isa(returnType) || isa(returnType)) || // TODO: we currently don't lazily load operator return types, but @@ -2472,8 +2497,11 @@ ClangImporter::Implementation::importParameterType( auto genericType = findGenericTypeInGenericDecls( *this, templateParamType, genericParams, attrs, addImportDiagnosticFn); swiftParamTy = genericType->wrapInPointer(pointerKind); - if (!swiftParamTy) + if (!swiftParamTy) { + addImportDiagnosticFn(Diagnostic(diag::bridged_pointer_type_not_found, + pointerKind)); return std::nullopt; + } } else if (isa(paramTy) && isa(paramTy->getPointeeType())) { // We don't support universal reference, bail. @@ -3515,6 +3543,11 @@ ImportedType ClangImporter::Implementation::importAccessorParamsAndReturnType( *params = ParameterList::create(SwiftContext, paramInfo); resultTy = SwiftContext.getVoidType(); + if (!resultTy) + addImportDiagnostic(clangDecl, + Diagnostic(diag::bridged_type_not_found_in_module, + "Void", "Swift"), + clangDecl->getLocation()); isIUO = false; } From 9f0130b6b61a05054d10237aec9d46635bccfac7 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Wed, 25 Sep 2024 10:58:47 -0700 Subject: [PATCH 02/15] =?UTF-8?q?[NFC]=20Dump=20an=20ASTScopeImpl=E2=80=99?= =?UTF-8?q?s=20parent=20nodes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/swift/AST/ASTScope.h | 3 +++ lib/AST/ASTScopePrinting.cpp | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/include/swift/AST/ASTScope.h b/include/swift/AST/ASTScope.h index 73cf141218ea3..fc7ae83327096 100644 --- a/include/swift/AST/ASTScope.h +++ b/include/swift/AST/ASTScope.h @@ -250,12 +250,15 @@ class ASTScopeImpl : public ASTAllocated { void printRange(llvm::raw_ostream &out) const; + void printParents(llvm::raw_ostream &out) const; + protected: virtual void printSpecifics(llvm::raw_ostream &out) const {} virtual NullablePtr addressForPrinting() const; public: SWIFT_DEBUG_DUMP; + SWIFT_DEBUG_DUMPER(dumpParents()); void dumpOneScopeMapLocation(std::pair lineColumn); diff --git a/lib/AST/ASTScopePrinting.cpp b/lib/AST/ASTScopePrinting.cpp index 14cc533850234..049ed0200c738 100644 --- a/lib/AST/ASTScopePrinting.cpp +++ b/lib/AST/ASTScopePrinting.cpp @@ -40,6 +40,8 @@ using namespace ast_scope; void ASTScopeImpl::dump() const { print(llvm::errs(), 0, false); } +void ASTScopeImpl::dumpParents() const { printParents(llvm::errs()); } + void ASTScopeImpl::dumpOneScopeMapLocation( std::pair lineColumn) { auto bufferID = getSourceFile()->getBufferID(); @@ -124,6 +126,21 @@ void ASTScopeImpl::printRange(llvm::raw_ostream &out) const { printSourceRange(out, range, getSourceManager()); } +void ASTScopeImpl::printParents(llvm::raw_ostream &out) const { + SmallVector nodes; + const ASTScopeImpl *cursor = this; + do { + nodes.push_back(cursor); + cursor = cursor->getParent().getPtrOrNull(); + } while (cursor); + + std::reverse(nodes.begin(), nodes.end()); + + for (auto i : indices(nodes)) { + nodes[i]->print(out, i, /*lastChild=*/true, /*printChildren=*/false); + } +} + #pragma mark printSpecifics From 9b947f90d445ad8c471754df91a0a547c29ab97a Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Fri, 4 Oct 2024 14:35:56 -0700 Subject: [PATCH 03/15] [NFC] Improve lookup flag printing facilities --- include/swift/AST/NameLookup.h | 2 ++ lib/AST/NameLookup.cpp | 2 ++ lib/Sema/TypeCheckNameLookup.cpp | 20 ++++++++++++++++++++ lib/Sema/TypeChecker.h | 4 ++++ 4 files changed, 28 insertions(+) diff --git a/include/swift/AST/NameLookup.h b/include/swift/AST/NameLookup.h index 66469ef1a558c..6d69c40b8a6af 100644 --- a/include/swift/AST/NameLookup.h +++ b/include/swift/AST/NameLookup.h @@ -253,6 +253,8 @@ enum class UnqualifiedLookupFlags { /// This lookup should include members that would otherwise be filtered out /// because they come from a module that has not been imported. IgnoreMissingImports = 1 << 10, + + // Reminder: If you add a flag, make sure you update simple_display() below }; using UnqualifiedLookupOptions = OptionSet; diff --git a/lib/AST/NameLookup.cpp b/lib/AST/NameLookup.cpp index 3bfcf8f7f6e83..c8ed48f4fb508 100644 --- a/lib/AST/NameLookup.cpp +++ b/lib/AST/NameLookup.cpp @@ -118,6 +118,8 @@ void swift::simple_display(llvm::raw_ostream &out, {UnqualifiedLookupFlags::TypeLookup, "TypeLookup"}, {UnqualifiedLookupFlags::MacroLookup, "MacroLookup"}, {UnqualifiedLookupFlags::ModuleLookup, "ModuleLookup"}, + {UnqualifiedLookupFlags::DisregardSelfBounds, "DisregardSelfBounds"}, + {UnqualifiedLookupFlags::IgnoreMissingImports, "IgnoreMissingImports"}, }; auto flagsToPrint = llvm::make_filter_range( diff --git a/lib/Sema/TypeCheckNameLookup.cpp b/lib/Sema/TypeCheckNameLookup.cpp index 47032efe2bceb..366cf2b01c111 100644 --- a/lib/Sema/TypeCheckNameLookup.cpp +++ b/lib/Sema/TypeCheckNameLookup.cpp @@ -32,6 +32,26 @@ using namespace swift; +void swift::simple_display(llvm::raw_ostream &out, NameLookupOptions options) { + using Flag = std::pair; + Flag possibleFlags[] = { + {NameLookupFlags::IgnoreAccessControl, "IgnoreAccessControl"}, + {NameLookupFlags::IncludeOuterResults, "IncludeOuterResults"}, + {NameLookupFlags::IncludeUsableFromInline, "IncludeUsableFromInline"}, + {NameLookupFlags::ExcludeMacroExpansions, "ExcludeMacroExpansions"}, + {NameLookupFlags::IgnoreMissingImports, "IgnoreMissingImports"}, + }; + + auto flagsToPrint = llvm::make_filter_range( + possibleFlags, [&](Flag flag) { return options.contains(flag.first); }); + + out << "{ "; + interleave( + flagsToPrint, [&](Flag flag) { out << flag.second; }, + [&] { out << ", "; }); + out << " }"; +} + namespace { /// Builder that helps construct a lookup result from the raw lookup /// data. diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index 03f744f1486d1..0e08c07f22812 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -161,11 +161,15 @@ enum class NameLookupFlags { /// Whether to include members that would otherwise be filtered out because /// they come from a module that has not been imported. IgnoreMissingImports = 1 << 4, + + // Reminder: If you add a flag, make sure you update simple_display() below }; /// A set of options that control name lookup. using NameLookupOptions = OptionSet; +void simple_display(llvm::raw_ostream &out, NameLookupOptions opts); + inline NameLookupOptions operator|(NameLookupFlags flag1, NameLookupFlags flag2) { return NameLookupOptions(flag1) | flag2; From 73377fe6b47328672ebd6eefdb6a0dddb66e6c79 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Mon, 7 Oct 2024 17:13:47 -0700 Subject: [PATCH 04/15] [NFC-ish] Fix some crashes from early Decl::dump() Changes dump output used in one test; otherwise NFC. --- lib/AST/ASTDumper.cpp | 17 +++++++++++++---- test/type/parameterized_protocol.swift | 6 +++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 38da374da8897..f9b3583d08d96 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -1383,9 +1383,13 @@ namespace { OS << "\")"; }); } - auto lifetimeString = getDumpString(VD->getLifetimeAnnotation()); - if (!lifetimeString.empty()) - printFlag(lifetimeString); + // In some cases, getLifetimeAnnotation() can fail before extension + // binding. hasResolvedImports() approximates an extension binding check. + if (VD->getModuleContext()->hasResolvedImports()) { + auto lifetimeString = getDumpString(VD->getLifetimeAnnotation()); + if (!lifetimeString.empty()) + printFlag(lifetimeString); + } } void printCommon(NominalTypeDecl *NTD, const char *Name, StringRef Label, @@ -1905,8 +1909,13 @@ void swift::printContext(raw_ostream &os, DeclContext *dc) { break; case DeclContextKind::ExtensionDecl: - if (auto extendedNominal = cast(dc)->getExtendedNominal()) { + if (auto repr = cast(dc)->getExtendedTypeRepr()) { + repr->print(os); + } else if (cast(dc)->hasBeenBound()) { + auto extendedNominal = cast(dc)->getExtendedNominal(); printName(os, extendedNominal->getName()); + } else { + os << ""; } os << " extension"; break; diff --git a/test/type/parameterized_protocol.swift b/test/type/parameterized_protocol.swift index a52027b18a4d5..83f7a74ca4d9c 100644 --- a/test/type/parameterized_protocol.swift +++ b/test/type/parameterized_protocol.swift @@ -1,7 +1,7 @@ // RUN: %target-typecheck-verify-swift -disable-availability-checking -// RUN: not %target-swift-frontend -typecheck %s -debug-generic-signatures -disable-availability-checking 2>&1 | %FileCheck %s - +// RUN: not %target-swift-frontend -typecheck %s -debug-generic-signatures -disable-availability-checking >%t.output 2>&1 +// RUN: %FileCheck --input-file %t.output %s /// Test some invalid syntax first @@ -176,7 +176,7 @@ struct OpaqueTypes { // CHECK: Generic signature: extension Sequence { - // CHECK-LABEL: parameterized_protocol.(file).Sequence extension.doSomethingGeneric@ + // CHECK-LABEL: parameterized_protocol.(file).Sequence extension.doSomethingGeneric@ // CHECK: Generic signature: func doSomethingGeneric(_: E) {} } From c9f539e146fec1e7d04b864529dc934e37e4b08a Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Mon, 9 Dec 2024 10:18:30 -0800 Subject: [PATCH 05/15] [NFC] Extract autodiff parsing code to Decl.cpp The `@differentiable` and `@derivative` attributes need a parent pointer. Move the code to populate it from Parser to AST so it can be more easily shared between the parsers. Done in preparation for similar code to be added for `@abi`. --- include/swift/AST/Attr.h | 24 +++++++++++--- include/swift/AST/Decl.h | 5 +++ lib/AST/Decl.cpp | 11 ++++++ lib/Parse/ParseDecl.cpp | 68 +++++++++++--------------------------- lib/Parse/ParseGeneric.cpp | 2 +- lib/Parse/ParsePattern.cpp | 2 +- 6 files changed, 57 insertions(+), 55 deletions(-) diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 1ee7c96db3d49..8437051a0afd2 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -2875,6 +2875,12 @@ template struct ToAttributeKind { return cast(Attr); return std::nullopt; } + + std::optional operator()(DeclAttribute *Attr) { + if (isa(Attr) && (Attr->isValid() || AllowInvalid)) + return cast(Attr); + return std::nullopt; + } }; /// The @_allowFeatureSuppression(Foo, Bar) attribute. The feature @@ -3047,17 +3053,25 @@ class DeclAttributes { } public: - template + template using AttributeKindRange = - OptionalTransformRange, + OptionalTransformRange, ToAttributeKind, - const_iterator>; + Iterator>; + + /// Return a range with all attributes in DeclAttributes with AttrKind + /// ATTR. + template + AttributeKindRange getAttributes() const { + return AttributeKindRange( + make_range(begin(), end()), ToAttributeKind()); + } /// Return a range with all attributes in DeclAttributes with AttrKind /// ATTR. template - AttributeKindRange getAttributes() const { - return AttributeKindRange( + AttributeKindRange getAttributes() { + return AttributeKindRange( make_range(begin(), end()), ToAttributeKind()); } diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 6655ef88123cb..6bc3b7ee53482 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -1022,6 +1022,11 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated, public Swi /// attribute macro expansion. DeclAttributes getSemanticAttrs() const; + /// Set this declaration's attributes to the specified attribute list, + /// applying any post-processing logic appropriate for attributes parsed + /// from source code. + void attachParsedAttrs(DeclAttributes attrs); + /// True if this declaration provides an implementation for an imported /// Objective-C declaration. This implies various restrictions and special /// behaviors for it and, if it's an extension, its members. diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 336cfd745cc5a..54ab6e1b60e42 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -407,6 +407,17 @@ DeclAttributes Decl::getSemanticAttrs() const { return getAttrs(); } +void Decl::attachParsedAttrs(DeclAttributes attrs) { + ASSERT(getAttrs().isEmpty() && "attaching when there are already attrs?"); + + for (auto *attr : attrs.getAttributes()) + attr->setOriginalDeclaration(this); + for (auto *attr : attrs.getAttributes()) + attr->setOriginalDeclaration(this); + + getAttrs() = attrs; +} + void Decl::visitAuxiliaryDecls( AuxiliaryDeclCallback callback, bool visitFreestandingExpanded diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 323eeaef50401..e7accdf51c6bb 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -6030,20 +6030,6 @@ void Parser::consumeDecl(ParserPosition BeginParserPosition, bool IsTopLevel) { } } -/// Set the original declaration in `@differentiable` attributes. -/// -/// Necessary because `Parser::parseNewDeclAttribute` (which calls -/// `Parser::parseDifferentiableAttribute`) does not have access to the -/// parent declaration of parsed attributes. -static void -setOriginalDeclarationForDifferentiableAttributes(DeclAttributes attrs, - Decl *D) { - for (auto *attr : attrs.getAttributes()) - const_cast(attr)->setOriginalDeclaration(D); - for (auto *attr : attrs.getAttributes()) - const_cast(attr)->setOriginalDeclaration(D); -} - /// Determine the declaration parsing options to use when parsing the decl in /// the given context. static Parser::ParseDeclOptions getParseDeclOptions(DeclContext *DC) { @@ -6469,7 +6455,6 @@ ParserStatus Parser::parseDecl(bool IsAtStartOfLineOrPreviousHadSemi, Decl *D = DeclResult.get(); if (!HandlerAlreadyCalled) Handler(D); - setOriginalDeclarationForDifferentiableAttributes(D->getAttrs(), D); } if (!DeclResult.isParseErrorOrHasCompletion()) { @@ -6665,7 +6650,7 @@ ParserResult Parser::parseDeclImport(ParseDeclOptions Flags, auto *ID = ImportDecl::create(Context, CurDeclContext, ImportLoc, Kind, KindLoc, importPath.get()); - ID->getAttrs() = Attributes; + ID->attachParsedAttrs(Attributes); return DCC.fixupParserResult(ID); } @@ -7109,7 +7094,7 @@ Parser::parseDeclExtension(ParseDeclOptions Flags, DeclAttributes &Attributes) { Context.AllocateCopy(Inherited), CurDeclContext, trailingWhereClause); - ext->getAttrs() = Attributes; + ext->attachParsedAttrs(Attributes); if (trailingWhereHadCodeCompletion && CodeCompletionCallbacks) CodeCompletionCallbacks->setParsedDecl(ext); @@ -7444,7 +7429,7 @@ parseDeclTypeAlias(Parser::ParseDeclOptions Flags, DeclAttributes &Attributes) { } TAD->setUnderlyingTypeRepr(UnderlyingTy.getPtrOrNull()); - TAD->getAttrs() = Attributes; + TAD->attachParsedAttrs(Attributes); // Parse a 'where' clause if present. if (Tok.is(tok::kw_where)) { @@ -7561,7 +7546,7 @@ ParserResult Parser::parseDeclAssociatedType(Parser::ParseDeclOptions auto assocType = AssociatedTypeDecl::createParsed( Context, CurDeclContext, AssociatedTypeLoc, Id, IdLoc, UnderlyingTy.getPtrOrNull(), TrailingWhere); - assocType->getAttrs() = Attributes; + assocType->attachParsedAttrs(Attributes); if (!Inherited.empty()) assocType->setInherited(Context.AllocateCopy(Inherited)); return makeParserResult(Status, assocType); @@ -7916,7 +7901,7 @@ bool Parser::parseAccessorAfterIntroducer( auto *accessor = AccessorDecl::createParsed( Context, Kind, storage, /*declLoc*/ Loc, /*accessorKeywordLoc*/ Loc, param, asyncLoc, throwsLoc, thrownTy, CurDeclContext); - accessor->getAttrs() = Attributes; + accessor->attachParsedAttrs(Attributes); // Collect this accessor and detect conflicts. if (auto existingAccessor = accessors.add(accessor)) { @@ -8195,7 +8180,7 @@ void Parser::parseExpandedAttributeList(SmallVectorImpl &items, // macro will attach the attribute list to. MissingDecl *missing = MissingDecl::create(Context, CurDeclContext, Tok.getLoc()); - missing->getAttrs() = attributes; + missing->attachParsedAttrs(attributes); items.push_back(ASTNode(missing)); return; @@ -8325,11 +8310,6 @@ Parser::parseDeclVarGetSet(PatternBindingEntry &entry, ParseDeclOptions Flags, accessors.record(*this, PrimaryVar, Invalid); - // Set original declaration in `@differentiable` attributes. - for (auto *accessor : accessors.Accessors) - setOriginalDeclarationForDifferentiableAttributes(accessor->getAttrs(), - accessor); - return makeParserResult(AccessorStatus, PrimaryVar); } @@ -8619,12 +8599,9 @@ Parser::parseDeclVar(ParseDeclOptions Flags, // Configure all vars with attributes, 'static' and parent pattern. pattern->forEachVariable([&](VarDecl *VD) { VD->setStatic(StaticLoc.isValid()); - VD->getAttrs() = Attributes; + VD->attachParsedAttrs(Attributes); VD->setTopLevelGlobal(topLevelDecl); - // Set original declaration in `@differentiable` attributes. - setOriginalDeclarationForDifferentiableAttributes(Attributes, VD); - Decls.push_back(VD); if (hasOpaqueReturnTy && sf && !InInactiveClauseEnvironment && !InFreestandingMacroArgument) { @@ -8944,7 +8921,7 @@ ParserResult Parser::parseDeclFunc(SourceLoc StaticLoc, diagnoseOperatorFixityAttributes(*this, Attributes, FD); // Add the attributes here so if we need them while parsing the body // they are available. - FD->getAttrs() = Attributes; + FD->attachParsedAttrs(Attributes); // Pass the function signature to code completion. if (Status.hasCodeCompletion()) { @@ -9135,7 +9112,7 @@ ParserResult Parser::parseDeclEnum(ParseDeclOptions Flags, EnumDecl *ED = new (Context) EnumDecl(EnumLoc, EnumName, EnumNameLoc, { }, GenericParams, CurDeclContext); - ED->getAttrs() = Attributes; + ED->attachParsedAttrs(Attributes); ContextChange CC(*this, ED); @@ -9330,7 +9307,7 @@ Parser::parseDeclEnumCase(ParseDeclOptions Flags, result->setImplicit(); // Parse error } - result->getAttrs() = Attributes; + result->attachParsedAttrs(Attributes); Elements.push_back(result); // Continue through the comma-separated list. @@ -9397,7 +9374,7 @@ ParserResult Parser::parseDeclStruct(ParseDeclOptions Flags, { }, GenericParams, CurDeclContext); - SD->getAttrs() = Attributes; + SD->attachParsedAttrs(Attributes); ContextChange CC(*this, SD); @@ -9486,7 +9463,7 @@ ParserResult Parser::parseDeclClass(ParseDeclOptions Flags, ClassDecl *CD = new (Context) ClassDecl(ClassLoc, ClassName, ClassNameLoc, { }, GenericParams, CurDeclContext, isExplicitActorDecl); - CD->getAttrs() = Attributes; + CD->attachParsedAttrs(Attributes); // Parsed classes never have missing vtable entries. CD->setHasMissingVTableEntries(false); @@ -9665,7 +9642,7 @@ parseDeclProtocol(ParseDeclOptions Flags, DeclAttributes &Attributes) { Context.AllocateCopy(PrimaryAssociatedTypeNames), Context.AllocateCopy(InheritedProtocols), TrailingWhere); - Proto->getAttrs() = Attributes; + Proto->attachParsedAttrs(Attributes); if (whereClauseHadCodeCompletion && CodeCompletionCallbacks) CodeCompletionCallbacks->setParsedDecl(Proto); @@ -9792,7 +9769,7 @@ Parser::parseDeclSubscript(SourceLoc StaticLoc, auto *const Subscript = SubscriptDecl::createParsed( Context, StaticLoc, StaticSpelling, SubscriptLoc, Indices.get(), ArrowLoc, ElementTy.get(), CurDeclContext, GenericParams); - Subscript->getAttrs() = Attributes; + Subscript->attachParsedAttrs(Attributes); // Let the source file track the opaque return type mapping, if any. if (ElementTy.get() && ElementTy.get()->hasOpaque() && @@ -9853,11 +9830,6 @@ Parser::parseDeclSubscript(SourceLoc StaticLoc, accessors.record(*this, Subscript, (Invalid || !Status.isSuccess() || Status.hasCodeCompletion())); - // Set original declaration in `@differentiable` attributes. - for (auto *accessor : accessors.Accessors) - setOriginalDeclarationForDifferentiableAttributes(accessor->getAttrs(), - accessor); - // No need to setLocalDiscriminator because subscripts cannot // validly appear outside of type decls. return makeParserResult(Status, Subscript); @@ -9967,7 +9939,7 @@ Parser::parseDeclInit(ParseDeclOptions Flags, DeclAttributes &Attributes) { thrownTy, BodyParams, GenericParams, CurDeclContext, FuncRetTy); CD->setImplicitlyUnwrappedOptional(IUO); - CD->getAttrs() = Attributes; + CD->attachParsedAttrs(Attributes); // Parse a 'where' clause if present. if (Tok.is(tok::kw_where)) { @@ -10056,7 +10028,7 @@ parseDeclDeinit(ParseDeclOptions Flags, DeclAttributes &Attributes) { auto *DD = new (Context) DestructorDecl(DestructorLoc, CurDeclContext); parseAbstractFunctionBody(DD); - DD->getAttrs() = Attributes; + DD->attachParsedAttrs(Attributes); // Reject 'destructor' functions outside of structs, enums, classes, or // extensions that provide objc implementations. @@ -10263,7 +10235,7 @@ Parser::parseDeclOperatorImpl(SourceLoc OperatorLoc, Identifier Name, diagnoseOperatorFixityAttributes(*this, Attributes, res); - res->getAttrs() = Attributes; + res->attachParsedAttrs(Attributes); return makeParserResult(res); } @@ -10320,7 +10292,7 @@ Parser::parseDeclPrecedenceGroup(ParseDeclOptions flags, higherThanKeywordLoc, higherThan, lowerThanKeywordLoc, lowerThan, rbraceLoc); - result->getAttrs() = attributes; + result->attachParsedAttrs(attributes); return result; }; auto createInvalid = [&](bool hasCodeCompletion) { @@ -10575,7 +10547,7 @@ ParserResult Parser::parseDeclMacro(DeclAttributes &attributes) { auto *macro = new (Context) MacroDecl( macroLoc, macroFullName, macroNameLoc, genericParams, parameterList, arrowLoc, resultType, definition, CurDeclContext); - macro->getAttrs() = attributes; + macro->attachParsedAttrs(attributes); // Parse a 'where' clause if present. if (Tok.is(tok::kw_where)) { @@ -10611,7 +10583,7 @@ Parser::parseDeclMacroExpansion(ParseDeclOptions flags, auto *med = MacroExpansionDecl::create( CurDeclContext, poundLoc, macroNameRef, macroNameLoc, leftAngleLoc, Context.AllocateCopy(genericArgs), rightAngleLoc, argList); - med->getAttrs() = attributes; + med->attachParsedAttrs(attributes); return makeParserResult(status, med); } diff --git a/lib/Parse/ParseGeneric.cpp b/lib/Parse/ParseGeneric.cpp index 818e3bab7f79e..265ba7cec0c82 100644 --- a/lib/Parse/ParseGeneric.cpp +++ b/lib/Parse/ParseGeneric.cpp @@ -148,7 +148,7 @@ Parser::parseGenericParametersBeforeWhere(SourceLoc LAngleLoc, GenericParams.push_back(Param); // Attach attributes. - Param->getAttrs() = attributes; + Param->attachParsedAttrs(attributes); // Parse the comma, if the list continues. HasComma = consumeIf(tok::comma); diff --git a/lib/Parse/ParsePattern.cpp b/lib/Parse/ParsePattern.cpp index 0f775790a29c2..7f91715b2c2c1 100644 --- a/lib/Parse/ParsePattern.cpp +++ b/lib/Parse/ParsePattern.cpp @@ -571,7 +571,7 @@ mapParsedParameters(Parser &parser, auto param = ParamDecl::createParsed( ctx, paramInfo.SpecifierLoc, argNameLoc, argName, paramNameLoc, paramName, paramInfo.DefaultArg, parser.CurDeclContext); - param->getAttrs() = paramInfo.Attrs; + param->attachParsedAttrs(paramInfo.Attrs); bool parsingEnumElt = (paramContext == Parser::ParameterContextKind::EnumElement); From 94ff062edd6f425a9b8ea92bb1106a7852fa7599 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Wed, 4 Dec 2024 17:17:09 -0800 Subject: [PATCH 06/15] Parse and serialize `@abi` attribute MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This attribute will allow you to specify an alternate version of the declaration used for mangling. It will allow minor adjustments to be made to declarations so long as they’re still compatible at the calling convention level, such as refining isolation or sendability, renaming without breaking ABI, etc. The attribute is behind the experimental feature flag `ABIAttribute`. --- include/swift/AST/ASTContext.h | 14 + include/swift/AST/Attr.h | 25 ++ include/swift/AST/Decl.h | 5 + include/swift/AST/DeclAttr.def | 6 +- include/swift/AST/DiagnosticsParse.def | 3 + include/swift/Basic/Features.def | 3 + include/swift/Parse/Parser.h | 18 +- lib/AST/ASTDumper.cpp | 14 +- lib/AST/Attr.cpp | 36 +++ lib/AST/Decl.cpp | 39 +++ lib/AST/FeatureSet.cpp | 5 + lib/ASTGen/Sources/ASTGen/DeclAttrs.swift | 11 + lib/Parse/ParseDecl.cpp | 148 +++++++++-- lib/Sema/TypeCheckAttr.cpp | 4 + lib/Sema/TypeCheckDeclOverride.cpp | 4 + lib/Sema/TypeCheckStmt.cpp | 34 ++- lib/Serialization/DeclTypeRecordNodes.def | 3 +- lib/Serialization/Deserialization.cpp | 110 +++++--- lib/Serialization/ModuleFormat.h | 14 +- lib/Serialization/Serialization.cpp | 21 ++ test/attr/attr_abi.swift | 300 ++++++++++++++++++++++ 21 files changed, 735 insertions(+), 82 deletions(-) create mode 100644 test/attr/attr_abi.swift diff --git a/include/swift/AST/ASTContext.h b/include/swift/AST/ASTContext.h index d3638bb3219a6..a1faec2df4126 100644 --- a/include/swift/AST/ASTContext.h +++ b/include/swift/AST/ASTContext.h @@ -67,6 +67,7 @@ namespace llvm { } namespace swift { + class ABIAttr; class AbstractFunctionDecl; class ASTContext; enum class Associativity : unsigned char; @@ -414,6 +415,19 @@ class ASTContext final { llvm::SmallPtrSet> DerivativeAttrs; + /// For each ABI-only declaration (created with the `@abi` attribute), points + /// to its API-only counterpart. That is, this table maps each + /// `ABIAttr::abiDecl` to the declaration the `ABIAttr` is attached to. + /// + /// \seeAlso \c recordABIAttr() + llvm::DenseMap ABIDeclCounterparts; + + /// Register \c this->abiDecl 's relationship with \p owner in + /// `ABIDeclCounterparts` . This is necessary for + /// \c ABIRoleInfo::ABIRoleInfo() to determine that \c this->abiDecl + /// is ABI-only and locate its API counterpart. + void recordABIAttr(ABIAttr *attr, Decl *owner); + /// The Swift module currently being compiled. ModuleDecl *MainModule = nullptr; diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 8437051a0afd2..66c25d8d712b6 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -2914,6 +2914,31 @@ class AllowFeatureSuppressionAttr final UNIMPLEMENTED_CLONE(AllowFeatureSuppressionAttr) }; +/// Defines the @abi attribute. +class ABIAttr : public DeclAttribute { +public: + ABIAttr(Decl *abiDecl, SourceLoc AtLoc, SourceRange Range, bool Implicit) + : DeclAttribute(DeclAttrKind::ABI, AtLoc, Range, Implicit), + abiDecl(abiDecl) + { } + + ABIAttr(Decl *abiDecl, bool Implicit) + : ABIAttr(abiDecl, SourceLoc(), SourceRange(), Implicit) {} + + /// The declaration which will be used to compute a mangled name. + /// + /// \note For a \c VarDecl with a parent \c PatternBindingDecl , this should + /// point to the parent \c PatternBindingDecl . (That accommodates the way + /// sibling \c VarDecl s share attributes.) + Decl *abiDecl; + + static bool classof(const DeclAttribute *DA) { + return DA->getKind() == DeclAttrKind::ABI; + } + + UNIMPLEMENTED_CLONE(ABIAttr) +}; + /// Attributes that may be applied to declarations. class DeclAttributes { /// Linked list of declaration attributes. diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 6bc3b7ee53482..bbb5545dfb237 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -2662,6 +2662,11 @@ class PatternBindingDecl final : public Decl, /// Returns \c true if this pattern binding was created by the debugger. bool isDebuggerBinding() const { return Bits.PatternBindingDecl.IsDebugger; } + /// Returns the \c VarDecl in this PBD at the same offset in the same + /// pattern entry as \p otherVar is in its PBD, or \c nullptr if this PBD is + /// too different from \p otherVar 's to find an equivalent variable. + VarDecl *getVarAtSimilarStructuralPosition(VarDecl *otherVar); + static bool classof(const Decl *D) { return D->getKind() == DeclKind::PatternBinding; } diff --git a/include/swift/AST/DeclAttr.def b/include/swift/AST/DeclAttr.def index 63f22ec57be1a..d8f7a80e494d0 100644 --- a/include/swift/AST/DeclAttr.def +++ b/include/swift/AST/DeclAttr.def @@ -523,7 +523,11 @@ DECL_ATTR(safe, Safe, ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove, 163) -LAST_DECL_ATTR(Safe) +DECL_ATTR(abi, ABI, + OnAbstractFunction | OnVar /* will eventually add types */ | LongAttribute | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove, + 164) + +LAST_DECL_ATTR(ABI) #undef DECL_ATTR_ALIAS #undef CONTEXTUAL_DECL_ATTR_ALIAS diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index f6f09a00ee4e3..1a74f4fab3830 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -228,6 +228,9 @@ ERROR(let_cannot_be_addressed_property,none, ERROR(disallowed_var_multiple_getset,none, "'var' declarations with multiple variables cannot have explicit" " getters/setters", ()) +ERROR(stub_decl_cannot_have_body,none, + "stub %0 cannot have body", + (DescriptiveDeclKind)) ERROR(disallowed_init,none, "initial value is not allowed here", ()) diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 7c343f44b2bd2..8ab9f8cea9666 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -436,6 +436,9 @@ EXPERIMENTAL_FEATURE(GenerateForceToMainActorThunks, false) EXPERIMENTAL_FEATURE(AddressableParameters, true) +/// Allow the @abi attribute. +EXPERIMENTAL_FEATURE(ABIAttribute, true) + #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index e561826431f8b..28e9e8764024f 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -933,7 +933,7 @@ class Parser { bool isStartOfGetSetAccessor(); /// Flags that control the parsing of declarations. - enum ParseDeclFlags { + enum ParseDeclFlags : uint16_t { PD_Default = 0, PD_AllowTopLevel = 1 << 1, PD_HasContainerType = 1 << 2, @@ -944,6 +944,7 @@ class Parser { PD_InExtension = 1 << 7, PD_InStruct = 1 << 8, PD_InEnum = 1 << 9, + PD_StubOnly = 1 << 10, }; /// Options that control the parsing of declarations. @@ -953,20 +954,24 @@ class Parser { ParserStatus parseDecl(bool IsAtStartOfLineOrPreviousHadSemi, bool IfConfigsAreDeclAttrs, - llvm::function_ref Handler); + llvm::function_ref Handler, + bool stubOnly = false); std::pair, std::optional> parseDeclListDelayed(IterableDeclContext *IDC); bool parseMemberDeclList(SourceLoc &LBLoc, SourceLoc &RBLoc, Diag<> LBraceDiag, Diag<> RBraceDiag, - IterableDeclContext *IDC); + IterableDeclContext *IDC, + ParseDeclOptions Flags); bool canDelayMemberDeclParsing(bool &HasOperatorDeclarations, bool &HasNestedClassDeclarations, - bool &HasDerivativeDeclarations); + bool &HasDerivativeDeclarations, + ParseDeclOptions Flags); - bool canDelayFunctionBodyParsing(bool &HasNestedTypeDeclarations); + bool canDelayFunctionBodyParsing(bool &HasNestedTypeDeclarations, + ParseDeclOptions Flags); bool delayParsingDeclList(SourceLoc LBLoc, SourceLoc &RBLoc, IterableDeclContext *IDC); @@ -1318,7 +1323,8 @@ class Parser { bool HasFuncKeyword = true); BodyAndFingerprint parseAbstractFunctionBodyImpl(AbstractFunctionDecl *AFD); - void parseAbstractFunctionBody(AbstractFunctionDecl *AFD); + void parseAbstractFunctionBody(AbstractFunctionDecl *AFD, + ParseDeclOptions Flags); BodyAndFingerprint parseAbstractFunctionBodyDelayed(AbstractFunctionDecl *AFD); diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index f9b3583d08d96..7caf34efc2263 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -967,9 +967,10 @@ namespace { } /// Print an unnamed field containing a node's name, read from a declaration. - void printDeclName(const ValueDecl *D, bool leadingSpace = true) { - if (D->getName()) { - printName(D->getName(), leadingSpace); + void printDeclName(const Decl *D, bool leadingSpace = true) { + auto VD = dyn_cast(D); + if (VD && VD->getName()) { + printName(VD->getName(), leadingSpace); } else { if (leadingSpace) OS << ' '; @@ -979,7 +980,7 @@ namespace { } /// Print a field containing a node's name, read from a declaration. - void printDeclNameField(const ValueDecl *D, StringRef name) { + void printDeclNameField(const Decl *D, StringRef name) { printFieldRaw([&](raw_ostream &os) { printDeclName(D, /*leadingSpace=*/false); }, name); @@ -3862,6 +3863,11 @@ class PrintAttribute : public AttributeVisitor, #undef TRIVIAL_ATTR_PRINTER + void visitABIAttr(ABIAttr *Attr, StringRef label) { + printCommon(Attr, "abi_attr", label); + printRec(Attr->abiDecl, "decl"); + printFoot(); + } void visitAccessControlAttr(AccessControlAttr *Attr, StringRef label) { printCommon(Attr, "access_control_attr", label); printField(Attr->getAccess(), "access_level"); diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index 8a0ac68e90068..c1aef83673d07 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -1645,6 +1645,16 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options, break; } + case DeclAttrKind::ABI: { + auto *attr = cast(this); + Printer << "@abi("; + if (attr->abiDecl) + attr->abiDecl->print(Printer, Options); + Printer << ")"; + + break; + } + #define SIMPLE_DECL_ATTR(X, CLASS, ...) case DeclAttrKind::CLASS: #include "swift/AST/DeclAttr.def" llvm_unreachable("handled above"); @@ -1690,6 +1700,8 @@ StringRef DeclAttribute::getAttrName() const { case DeclAttrKind::CLASS: \ return #NAME; #include "swift/AST/DeclAttr.def" + case DeclAttrKind::ABI: + return "abi"; case DeclAttrKind::SILGenName: return "_silgen_name"; case DeclAttrKind::Alignment: @@ -2869,6 +2881,30 @@ LifetimeAttr *LifetimeAttr::create(ASTContext &context, SourceLoc atLoc, return new (context) LifetimeAttr(atLoc, baseRange, implicit, entry); } +void ASTContext::recordABIAttr(ABIAttr *attr, Decl *owner) { + // The ABIAttr on a VarDecl ought to point to its PBD. + if (auto VD = dyn_cast(owner)) { + if (auto PBD = VD->getParentPatternBinding()) { + owner = PBD; + } + } + + auto recordABIDecl = [&](Decl *decl) { + ABIDeclCounterparts.insert({ decl, owner }); + }; + + if (auto abiPBD = dyn_cast(attr->abiDecl)) { + // Add to *every* VarDecl in the ABI PBD, even ones that don't properly + // match anything in the API PBD. + for (auto i : range(abiPBD->getNumPatternEntries())) { + abiPBD->getPattern(i)->forEachVariable(recordABIDecl); + } + return; + } + + recordABIDecl(attr->abiDecl); +} + void swift::simple_display(llvm::raw_ostream &out, const DeclAttribute *attr) { if (attr) attr->print(out); diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 54ab6e1b60e42..90ac999688eec 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -414,6 +414,8 @@ void Decl::attachParsedAttrs(DeclAttributes attrs) { attr->setOriginalDeclaration(this); for (auto *attr : attrs.getAttributes()) attr->setOriginalDeclaration(this); + for (auto attr : attrs.getAttributes()) + this->getASTContext().recordABIAttr(attr, this); getAttrs() = attrs; } @@ -4268,6 +4270,43 @@ void ValueDecl::setRenamedDecl(const AvailableAttr *attr, std::move(renameDecl)); } +VarDecl *PatternBindingDecl:: +getVarAtSimilarStructuralPosition(VarDecl *otherVar) { + auto otherPBD = otherVar->getParentPatternBinding(); + + if (!otherPBD) + return getSingleVar(); + if (otherPBD == this) + return otherVar; + + // Find the entry index and sibling index for `otherVar` within its PBD. + auto entryIndex = otherPBD->getPatternEntryIndexForVarDecl(otherVar); + + SmallVector otherSiblings; + otherPBD->getPattern(entryIndex)->collectVariables(otherSiblings); + size_t siblingIndex = + llvm::find(otherSiblings, otherVar) - otherSiblings.begin(); + + ASSERT(siblingIndex != otherSiblings.size() + && "otherVar not in its own pattern?"); + + // Look up the same entry index and sibling index in this PBD, returning null + // if that index doesn't exist. + + // Corresponding PBD has more patterns in it + if (entryIndex >= getNumPatternEntries()) + return nullptr; + + SmallVector siblings; + getPattern(entryIndex)->collectVariables(siblings); + + // Corresponding pattern has more vars in it + if (siblingIndex >= siblings.size()) + return nullptr; + + return siblings[siblingIndex]; +} + SourceLoc Decl::getAttributeInsertionLoc(bool forModifier) const { // Some decls have a parent/child split where the introducer keyword is on the // parent, but the attributes are on the children. If this is a child in such diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index cd9150a10cca1..94a35fe0c2404 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -323,6 +323,11 @@ static bool usesFeatureAllowUnsafeAttribute(Decl *decl) { return decl->getAttrs().hasAttribute(); } +static bool usesFeatureABIAttribute(Decl *decl) { + auto abiAttr = decl->getAttrs().getAttribute(); + return abiAttr && !abiAttr->isInverse(); +} + UNINTERESTING_FEATURE(WarnUnsafe) UNINTERESTING_FEATURE(SafeInterop) UNINTERESTING_FEATURE(SafeInteropWrappers) diff --git a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift index 17e165e15a2a0..dfac6a0e7f2e2 100644 --- a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift +++ b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift @@ -111,6 +111,8 @@ extension ASTGenVisitor { let attrName = identTy.name.rawText let attrKind = BridgedDeclAttrKind(from: attrName.bridged) switch attrKind { + case .ABI: + return self.generateABIAttr(attribute: node)?.asDeclAttribute case .alignment: return handle(self.generateAlignmentAttr(attribute: node)?.asDeclAttribute) case .allowFeatureSuppression: @@ -328,6 +330,15 @@ extension ASTGenVisitor { return handle(self.generateCustomAttr(attribute: node)?.asDeclAttribute) } + /// E.g.: + /// ``` + /// @abi(func fn()) + /// ``` + func generateABIAttr(attribute node: AttributeSyntax) -> BridgedABIAttr? { + #warning("TODO: implement generateABIAttr") + fatalError("TODO: implement generateABIAttr") + } + /// E.g.: /// ``` /// @_alignment(8) diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index e7accdf51c6bb..e7f4fdf13e5e4 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -3490,6 +3490,56 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, } break; } + + case DeclAttrKind::ABI: { + SourceLoc lParenLoc = Tok.getLoc(); + if (!consumeIfAttributeLParen()) { + diagnose(lParenLoc, diag::attr_expected_lparen, AttrName, + DeclAttribute::isDeclModifier(DK)); + return makeParserSuccess(); + } + + Decl *abiDecl = nullptr; + Status |= parseDecl(/*atStart=*/true, /*ifConfigInAttr=*/true, [&](Decl *D){ + // Look through a TopLevelCodeDecl. + // FIXME: Do we need to reparent these decls to match their counterparts? + // Feels like there might be more to do here. + if (auto tlcd = dyn_cast(D)) { + ASSERT(tlcd->getBody()->getNumElements() == 1 + && "TopLevelCodeDecl with != 1 element?"); + D = tlcd->getBody()->getElements().front().dyn_cast(); + if (!D) + return; + } + // TODO: MacroExpansionDecl? + + // When parsing a compound decl, like a PBD, we'll get called for both the + // enclosing decl and the ones inside it. We only want the enclosing decl, + // which will always come first. + if (!abiDecl) + abiDecl = D; + }, /*stubOnly=*/true); + ASSERT(!isa_and_nonnull(abiDecl) + && "should have gotten PBD, not VarDecl"); + + SourceLoc rParenLoc; + if (parseMatchingToken(tok::r_paren, rParenLoc, + DiagRef(diag::attr_expected_rparen, + { AttrName, + DeclAttribute::isDeclModifier(DK) }), + lParenLoc)) { + return makeParserSuccess(); + } + + if (abiDecl) { + Attributes.add(new (Context) ABIAttr(abiDecl, + AtLoc, { Loc, rParenLoc }, + /*implicit=*/false)); + } + + break; + } + case DeclAttrKind::Available: { if (!consumeIfAttributeLParen()) { diagnose(Loc, diag::attr_expected_lparen, AttrName, @@ -6084,8 +6134,12 @@ static Parser::ParseDeclOptions getParseDeclOptions(DeclContext *DC) { /// \endverbatim ParserStatus Parser::parseDecl(bool IsAtStartOfLineOrPreviousHadSemi, bool IfConfigsAreDeclAttrs, - llvm::function_ref Handler) { + llvm::function_ref Handler, + bool stubOnly) { ParseDeclOptions Flags = getParseDeclOptions(CurDeclContext); + if (stubOnly) { + Flags |= PD_StubOnly; + } ParserPosition BeginParserPosition; if (isIDEInspectionFirstPass()) BeginParserPosition = getParserPosition(); @@ -6889,7 +6943,18 @@ ParserStatus Parser::parseDeclItem(bool &PreviousHadSemi, bool Parser::parseMemberDeclList(SourceLoc &LBLoc, SourceLoc &RBLoc, Diag<> LBraceDiag, Diag<> RBraceDiag, - IterableDeclContext *IDC) { + IterableDeclContext *IDC, + ParseDeclOptions Flags) { + if (Flags.contains(PD_StubOnly) && Tok.isNot(tok::l_brace)) { + // Hey, look, it really is a stub! + LBLoc = RBLoc = SourceLoc(); + + // Cache the empty result to prevent delayed parsing. + Context.evaluator.cacheOutput(ParseMembersRequest{IDC}, + FingerprintAndMembers{std::nullopt, {}}); + return false; + } + if (parseToken(tok::l_brace, LBLoc, LBraceDiag)) { LBLoc = RBLoc = PreviousLoc; @@ -6910,7 +6975,8 @@ bool Parser::parseMemberDeclList(SourceLoc &LBLoc, SourceLoc &RBLoc, if (canDelayMemberDeclParsing(HasOperatorDeclarations, HasNestedClassDeclarations, - HasDerivativeDeclarations)) { + HasDerivativeDeclarations, + Flags)) { if (HasOperatorDeclarations) IDC->setMaybeHasOperatorDeclarations(); if (HasNestedClassDeclarations) @@ -6930,6 +6996,19 @@ bool Parser::parseMemberDeclList(SourceLoc &LBLoc, SourceLoc &RBLoc, IDC->setMaybeHasOperatorDeclarations(); IDC->setMaybeHasNestedClassDeclarations(); IDC->setMaybeHasDerivativeDeclarations(); + + if (Flags.contains(PD_StubOnly)) { + diagnose(LBLoc, diag::stub_decl_cannot_have_body, + IDC->getDecl()->getDescriptiveKind()) + .fixItRemove({LBLoc, RBLoc}); + + // Cache the empty result to prevent delayed parsing. + Context.evaluator.cacheOutput(ParseMembersRequest{IDC}, + FingerprintAndMembers{std::nullopt, {}}); + + return true; + } + Context.evaluator.cacheOutput( ParseMembersRequest{IDC}, FingerprintAndMembers{ @@ -6991,9 +7070,10 @@ Parser::parseDeclList(SourceLoc LBLoc, SourceLoc &RBLoc, Diag<> ErrorDiag, bool Parser::canDelayMemberDeclParsing(bool &HasOperatorDeclarations, bool &HasNestedClassDeclarations, - bool &HasDerivativeDeclarations) { + bool &HasDerivativeDeclarations, + ParseDeclOptions Flags) { // If explicitly disabled, respect the flag. - if (!isDelayedParsingEnabled()) + if (!isDelayedParsingEnabled() || Flags.contains(PD_StubOnly)) return false; // Skip until the matching right curly bracket; if we find a pound directive, @@ -7106,7 +7186,7 @@ Parser::parseDeclExtension(ParseDeclOptions Flags, DeclAttributes &Attributes) { if (parseMemberDeclList(LBLoc, RBLoc, diag::expected_lbrace_extension, diag::expected_rbrace_extension, - ext)) + ext, Flags)) status.setIsParseError(); // Don't propagate the code completion bit from members: we cannot help @@ -7602,9 +7682,11 @@ static ParameterList *parseOptionalAccessorArgument(SourceLoc SpecifierLoc, return ParameterList::create(P.Context, StartLoc, param, EndLoc); } -bool Parser::canDelayFunctionBodyParsing(bool &HasNestedTypeDeclarations) { +bool Parser::canDelayFunctionBodyParsing(bool &HasNestedTypeDeclarations, + ParseDeclOptions Flags) { // If explicitly disabled, respect the flag. - if (!isDelayedParsingEnabled() && !isIDEInspectionFirstPass()) + if (!isDelayedParsingEnabled() && !isIDEInspectionFirstPass() && + !Flags.contains(PD_StubOnly)) return false; // Skip until the matching right curly bracket; If it has a potential regex @@ -7928,8 +8010,8 @@ bool Parser::parseAccessorAfterIntroducer( // It's okay not to have a body if there's an external asm name. if (!Tok.is(tok::l_brace)) { - // Accessors don't need bodies in module interfaces - if (SF.Kind == SourceFileKind::Interface) + // Accessors don't need bodies in module interfaces or @abi attrs + if (SF.Kind == SourceFileKind::Interface || Flags.contains(PD_StubOnly)) return false; // _silgen_name'd accessors don't need bodies. @@ -7943,7 +8025,7 @@ bool Parser::parseAccessorAfterIntroducer( return false; } - parseAbstractFunctionBody(accessor); + parseAbstractFunctionBody(accessor, Flags); return false; } @@ -7969,7 +8051,7 @@ ParserStatus Parser::parseGetSet(ParseDeclOptions Flags, ParameterList *Indices, /*asyncLoc*/ SourceLoc(), /*throwsLoc*/ SourceLoc(), /*thrownTy*/ nullptr, CurDeclContext); accessors.add(getter); - parseAbstractFunctionBody(getter); + parseAbstractFunctionBody(getter, Flags); accessors.RBLoc = getter->getEndLoc(); }; @@ -8308,6 +8390,14 @@ Parser::parseDeclVarGetSet(PatternBindingEntry &entry, ParseDeclOptions Flags, Invalid = true; } + // Reject accessors when we're parsing stubs only. + if (Flags.contains(PD_StubOnly) && !accessors.Accessors.empty()) { + diagnose(Tok, diag::stub_decl_cannot_have_body, + PrimaryVar->getDescriptiveKind()) + .fixItRemove({ accessors.LBLoc, accessors.RBLoc }); + Invalid = true; + } + accessors.record(*this, PrimaryVar, Invalid); return makeParserResult(AccessorStatus, PrimaryVar); @@ -8568,6 +8658,8 @@ Parser::parseDeclVar(ParseDeclOptions Flags, return makeParserResult(Status, PBD); }; bool HasNext; + bool DisallowInit = + Flags.contains(PD_DisallowInit) || Flags.contains(PD_StubOnly); do { Pattern *pattern; { @@ -8645,7 +8737,7 @@ Parser::parseDeclVar(ParseDeclOptions Flags, // If this Pattern binding was not supposed to have an initializer, but it // did, diagnose this and remove it. - if (Flags & PD_DisallowInit && init.isNonNull()) { + if (DisallowInit && init.isNonNull()) { diagnose(EqualLoc, diag::disallowed_init); init = nullptr; } @@ -8655,7 +8747,7 @@ Parser::parseDeclVar(ParseDeclOptions Flags, // that downstream clients know that it was present (well, at least the = // was present). This silences downstream diagnostics checking to make // sure that some PBD's that require initializers actually had them. - if (!(Flags & PD_DisallowInit) && init.isNull()) + if (!DisallowInit && init.isNull()) init = makeParserResult(init, new (Context) ErrorExpr(EqualLoc)); @@ -8937,7 +9029,7 @@ ParserResult Parser::parseDeclFunc(SourceLoc StaticLoc, skipSingle(); } } else if (!Status.hasCodeCompletion()) { - parseAbstractFunctionBody(FD); + parseAbstractFunctionBody(FD, Flags); } return DCC.fixupParserResult(FD); @@ -8993,7 +9085,8 @@ Parser::parseAbstractFunctionBodyImpl(AbstractFunctionDecl *AFD) { } /// Parse function body into \p AFD or skip it for delayed parsing. -void Parser::parseAbstractFunctionBody(AbstractFunctionDecl *AFD) { +void Parser::parseAbstractFunctionBody(AbstractFunctionDecl *AFD, + ParseDeclOptions Flags) { if (!Tok.is(tok::l_brace)) { checkForInputIncomplete(); return; @@ -9026,7 +9119,9 @@ void Parser::parseAbstractFunctionBody(AbstractFunctionDecl *AFD) { }; bool HasNestedTypeDeclarations; - if (canDelayFunctionBodyParsing(HasNestedTypeDeclarations)) { + if (canDelayFunctionBodyParsing(HasNestedTypeDeclarations, Flags)) { + ASSERT(!Flags.contains(PD_StubOnly) + && "stub-only should parse body immediately"); BodyRange.End = PreviousLoc; assert(SourceMgr.isBeforeInBuffer(BodyRange.Start, BodyRange.End) || @@ -9043,6 +9138,13 @@ void Parser::parseAbstractFunctionBody(AbstractFunctionDecl *AFD) { (void)parseAbstractFunctionBodyImpl(AFD); BodyRange = AFD->getBodySourceRange(); setIDEInspectionDelayedDeclStateIfNeeded(); + + if (Flags.contains(PD_StubOnly)) { + diagnose(BodyRange.Start, diag::stub_decl_cannot_have_body, + AFD->getDescriptiveKind()) + .fixItRemove(BodyRange); + AFD->setBody(nullptr, AbstractFunctionDecl::BodyKind::None); + } } BodyAndFingerprint @@ -9142,7 +9244,7 @@ ParserResult Parser::parseDeclEnum(ParseDeclOptions Flags, if (parseMemberDeclList(LBLoc, RBLoc, diag::expected_lbrace_enum, diag::expected_rbrace_enum, - ED)) + ED, Flags)) Status.setIsParseError(); } @@ -9406,7 +9508,7 @@ ParserResult Parser::parseDeclStruct(ParseDeclOptions Flags, if (parseMemberDeclList(LBLoc, RBLoc, diag::expected_lbrace_struct, diag::expected_rbrace_struct, - SD)) + SD, Flags)) Status.setIsParseError(); } @@ -9522,7 +9624,7 @@ ParserResult Parser::parseDeclClass(ParseDeclOptions Flags, : diag::expected_lbrace_class, isExplicitActorDecl ? diag::expected_rbrace_actor : diag::expected_rbrace_class, - CD)) + CD, Flags)) Status.setIsParseError(); } @@ -9657,7 +9759,7 @@ parseDeclProtocol(ParseDeclOptions Flags, DeclAttributes &Attributes) { if (parseMemberDeclList(LBraceLoc, RBraceLoc, diag::expected_lbrace_protocol, diag::expected_rbrace_protocol, - Proto)) + Proto, Flags)) Status.setIsParseError(); } @@ -9973,7 +10075,7 @@ Parser::parseDeclInit(ParseDeclOptions Flags, DeclAttributes &Attributes) { skipSingle(); } } else if(!Status.hasCodeCompletion()) { - parseAbstractFunctionBody(CD); + parseAbstractFunctionBody(CD, Flags); } return makeParserResult(Status, CD); @@ -10026,7 +10128,7 @@ parseDeclDeinit(ParseDeclOptions Flags, DeclAttributes &Attributes) { } auto *DD = new (Context) DestructorDecl(DestructorLoc, CurDeclContext); - parseAbstractFunctionBody(DD); + parseAbstractFunctionBody(DD, Flags); DD->attachParsedAttrs(Attributes); diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 75a33c75c6791..de0ed02228ac2 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -190,6 +190,10 @@ class AttributeChecker : public AttributeVisitor { IGNORED_ATTR(PreInverseGenerics) #undef IGNORED_ATTR + void visitABIAttr(ABIAttr *attr) { + // TODO: Validate more + } + void visitAlignmentAttr(AlignmentAttr *attr) { // Alignment must be a power of two. auto value = attr->getValue(); diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index b46c058b801ec..1736094088d7b 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1744,6 +1744,10 @@ namespace { UNINTERESTING_ATTR(Safe) #undef UNINTERESTING_ATTR + void visitABIAttr(ABIAttr *attr) { + // TODO: Match or infer + } + void visitAvailableAttr(AvailableAttr *attr) { // FIXME: Check that this declaration is at least as available as the // one it overrides. diff --git a/lib/Sema/TypeCheckStmt.cpp b/lib/Sema/TypeCheckStmt.cpp index e31361e01cb6e..abf7da391a31e 100644 --- a/lib/Sema/TypeCheckStmt.cpp +++ b/lib/Sema/TypeCheckStmt.cpp @@ -2776,6 +2776,24 @@ BraceStmt *PreCheckClosureBodyRequest::evaluate(Evaluator &evaluator, return body; } + +/// Determine whether the given declaration should not have a definition. +static bool requiresNoDefinition(Decl *decl) { + if (auto func = dyn_cast(decl)) { + // Function with @_extern should not have a body. + if (func->getAttrs().hasAttribute()) + return true; + } + // FIXME: Should be able to write this more nicely + auto abiAttr = decl->getAttrs().getAttribute(); + if (abiAttr && abiAttr->isInverse()) + // Decls which merely define the ABI of another decl should not have a + // body. + return true; + // Everything else can have a definition. + return false; +} + /// Determine whether the given declaration requires a definition. /// /// Only valid for declarations that can have definitions, i.e., @@ -2793,7 +2811,6 @@ static bool requiresDefinition(Decl *decl) { // Functions can have _silgen_name, semantics, and NSManaged attributes. if (auto func = dyn_cast(decl)) { if (func->getAttrs().hasAttribute() || - func->getAttrs().hasAttribute() || func->getAttrs().hasAttribute() || func->getAttrs().hasAttribute()) return false; @@ -2820,20 +2837,15 @@ static bool requiresDefinition(Decl *decl) { if (fileUnit->getKind() == FileUnitKind::SerializedAST) return false; + // Declarations that cannot possibly have a definition definitely don't + // require one. + if (requiresNoDefinition(decl)) + return false; + // Everything else requires a definition. return true; } -/// Determine whether the given declaration should not have a definition. -static bool requiresNoDefinition(Decl *decl) { - if (auto func = dyn_cast(decl)) { - // Function with @_extern should not have a body. - return func->getAttrs().hasAttribute(); - } - // Everything else can have a definition. - return false; -} - BraceStmt * TypeCheckFunctionBodyRequest::evaluate(Evaluator &eval, AbstractFunctionDecl *AFD) const { diff --git a/lib/Serialization/DeclTypeRecordNodes.def b/lib/Serialization/DeclTypeRecordNodes.def index f4637fbbbb1b7..21197929ff32a 100644 --- a/lib/Serialization/DeclTypeRecordNodes.def +++ b/lib/Serialization/DeclTypeRecordNodes.def @@ -211,11 +211,12 @@ OTHER(CLANG_TYPE, 153) OTHER(DERIVATIVE_FUNCTION_CONFIGURATION, 154) OTHER(ERROR_FLAG, 155) +OTHER(ABI_ONLY_COUNTERPART, 156) TRAILING_INFO(CONDITIONAL_SUBSTITUTION) TRAILING_INFO(CONDITIONAL_SUBSTITUTION_COND) -OTHER(LIFETIME_DEPENDENCE, 158) +OTHER(LIFETIME_DEPENDENCE, 160) TRAILING_INFO(INHERITED_PROTOCOLS) diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index fb7258d3471cd..b996df4ad7dcb 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -3247,33 +3247,6 @@ static bool attributeChainContains(DeclAttribute *attr) { return tempAttrs.hasAttribute(); } -// Set original declaration and parameter indices in `@differentiable` -// attributes. -// -// Serializing/deserializing the original declaration DeclID in -// `@differentiable` attributes does not work because it causes -// `@differentiable` attribute deserialization to enter an infinite loop. -// -// Instead, call this ad-hoc function after deserializing a declaration to set -// the original declaration and parameter indices for its `@differentiable` -// attributes. -static void setOriginalDeclarationAndParameterIndicesInDifferentiableAttributes( - Decl *decl, DeclAttribute *attrs, - llvm::DenseMap - &diffAttrParamIndicesMap) { - DeclAttributes tempAttrs; - tempAttrs.setRawAttributeChain(attrs); - for (auto *attr : tempAttrs.getAttributes()) { - auto *diffAttr = const_cast(attr); - diffAttr->setOriginalDeclaration(decl); - diffAttr->setParameterIndices(diffAttrParamIndicesMap[diffAttr]); - } - for (auto *attr : tempAttrs.getAttributes()) { - auto *derAttr = const_cast(attr); - derAttr->setOriginalDeclaration(decl); - } -} - Decl *ModuleFile::getDecl(DeclID DID) { Expected deserialized = getDeclChecked(DID); if (!deserialized) { @@ -3306,6 +3279,11 @@ class DeclDeserializer { // Auxiliary map for deserializing `@differentiable` attributes. llvm::DenseMap diffAttrParamIndicesMap; + /// State for resolving the declaration in an ABIAttr. + std::optional> unresolvedABIAttr; + /// State for setting up an ABIAttr's counterpart relationship. + DeclID ABIDeclCounterpartID = 0; + void AddAttribute(DeclAttribute *Attr) { // Advance the linked list. // This isn't just using DeclAttributes because that would result in the @@ -3349,6 +3327,8 @@ class DeclDeserializer { decl.get()->setInherited(inherited); } + llvm::Error finishRecursiveAttrs(Decl *decl, DeclAttribute *attrs); + public: DeclDeserializer(ModuleFile &MF, Serialized &declOrOffset) : MF(MF), ctx(MF.getContext()), declOrOffset(declOrOffset) {} @@ -5737,10 +5717,31 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() { if (recordID == ERROR_FLAG) { assert(!IsInvalid && "Error flag written multiple times"); IsInvalid = true; + } else if (recordID == ABI_ONLY_COUNTERPART) { + assert(ABIDeclCounterpartID == 0 + && "ABI-only counterpart written multiple times"); + DeclID counterpartID; + serialization::decls_block::ABIOnlyCounterpartLayout::readRecord( + scratch, counterpartID); + // Defer resolving `ABIDeclCounterpartID` until `finishRecursiveAttrs()` + // because the two decls reference each other. + ABIDeclCounterpartID = counterpartID; } else if (isDeclAttrRecord(recordID)) { DeclAttribute *Attr = nullptr; bool skipAttr = false; switch (recordID) { + case decls_block::ABI_DECL_ATTR: { + bool isImplicit; + DeclID abiDeclID; + serialization::decls_block::ABIDeclAttrLayout::readRecord( + scratch, isImplicit, abiDeclID); + Attr = new (ctx) ABIAttr(nullptr, isImplicit); + // Defer resolving `abiDeclID` until `finishRecursiveAttrs()` because + // the two decls reference each other. + unresolvedABIAttr.emplace(cast(Attr), abiDeclID); + break; + } + case decls_block::SILGenName_DECL_ATTR: { bool isImplicit; serialization::decls_block::SILGenNameDeclAttrLayout::readRecord( @@ -6481,6 +6482,51 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() { } } +/// Complete attributes that contain recursive references to the decl being +/// deserialized or to other decls. This method is called after \p decl is +/// created and stored into the \c ModuleFile::Decls table, so any cycles +/// between mutually-referencing decls will be broken. +/// +/// Attributes handled here include: +/// +/// \li \c \@differentiable +/// \li \c \@derivative +/// \li \c \@abi +llvm::Error DeclDeserializer::finishRecursiveAttrs(Decl *decl, DeclAttribute *attrs) { + DeclAttributes tempAttrs; + tempAttrs.setRawAttributeChain(attrs); + + // @differentiable and @derivative + for (auto *attr : tempAttrs.getAttributes()) { + auto *diffAttr = const_cast(attr); + diffAttr->setOriginalDeclaration(decl); + diffAttr->setParameterIndices(diffAttrParamIndicesMap[diffAttr]); + } + for (auto *attr : tempAttrs.getAttributes()) { + auto *derAttr = const_cast(attr); + derAttr->setOriginalDeclaration(decl); + } + + // @abi + if (unresolvedABIAttr) { + auto abiDeclOrError = MF.getDeclChecked(unresolvedABIAttr->second); + if (!abiDeclOrError) + return abiDeclOrError.takeError(); + unresolvedABIAttr->first->abiDecl = abiDeclOrError.get(); + ctx.recordABIAttr(unresolvedABIAttr->first, decl); + } + if (ABIDeclCounterpartID != 0) { + // This decl is the `abiDecl` of an `ABIAttr`. Force the decl that `ABIAttr` + // belongs to so that `recordABIAttr()` will be called. + auto counterpartOrError = MF.getDeclChecked(ABIDeclCounterpartID); + if (!counterpartOrError) + return counterpartOrError.takeError(); + (void)counterpartOrError.get(); + } + + return llvm::Error::success(); +} + Expected DeclDeserializer::getDeclCheckedImpl( llvm::function_ref matchAttributes) { @@ -6525,17 +6571,11 @@ DeclDeserializer::getDeclCheckedImpl( #define CASE(RECORD_NAME) \ case decls_block::RECORD_NAME##Layout::Code: {\ auto declOrError = deserialize##RECORD_NAME(scratch, blobData); \ - if (declOrError) { \ - /* \ - // Set original declaration and parameter indices in `@differentiable` \ - // attributes. \ - */ \ - setOriginalDeclarationAndParameterIndicesInDifferentiableAttributes(\ - declOrError.get(), DAttrs, diffAttrParamIndicesMap); \ - } \ if (!declOrError) \ return declOrError; \ declOrOffset = declOrError.get(); \ + if (auto finishError = finishRecursiveAttrs(declOrError.get(), DAttrs)) \ + return finishError; \ break; \ } diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index e8de214f8294f..f73d94665bc62 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 908; // debug scopes and source locs +const uint16_t SWIFTMODULE_VERSION_MINOR = 909; // @abi attribute /// A standard hash seed used for all string hashes in a serialized module. /// @@ -1226,6 +1226,12 @@ namespace decls_block { ERROR_FLAG >; + /// A field marking a decl as being ABI-only and pointing to its API counterpart. + using ABIOnlyCounterpartLayout = BCRecordLayout< + ABI_ONLY_COUNTERPART, + DeclIDField // API decl + >; + /// A placeholder for invalid types TYPE_LAYOUT(ErrorTypeLayout, ERROR_TYPE, @@ -2347,6 +2353,12 @@ namespace decls_block { BCFixed<2> // exclusivity mode >; + using ABIDeclAttrLayout = BCRecordLayout< + ABI_DECL_ATTR, + BCFixed<1>, // implicit flag + DeclIDField // ABI decl + >; + using AvailableDeclAttrLayout = BCRecordLayout< Available_DECL_ATTR, BCFixed<1>, // implicit flag diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index 7fda736611c2b..508757e9f8ea0 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -2885,6 +2885,15 @@ class Serializer::DeclSerializer : public DeclVisitor { } #include "swift/AST/DeclAttr.def" + case DeclAttrKind::ABI: { + auto *theAttr = cast(DA); + auto abbrCode = S.DeclTypeAbbrCodes[ABIDeclAttrLayout::Code]; + auto abiDeclID = S.addDeclRef(theAttr->abiDecl); + ABIDeclAttrLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode, + theAttr->isImplicit(), abiDeclID); + return; + } + case DeclAttrKind::SILGenName: { auto *theAttr = cast(DA); auto abbrCode = S.DeclTypeAbbrCodes[SILGenNameDeclAttrLayout::Code]; @@ -4011,6 +4020,10 @@ class Serializer::DeclSerializer : public DeclVisitor { writeDeserializationSafety(D); + auto abiRole = ABIRoleInfo(D); + if (!abiRole.providesAPI()) + writeABIOnlyCounterpart(abiRole.getCounterpartUnchecked()); + // Emit attributes (if any). for (auto Attr : D->getAttrs()) writeDeclAttribute(D, Attr); @@ -4031,6 +4044,13 @@ class Serializer::DeclSerializer : public DeclVisitor { ErrorFlagLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode); } + void writeABIOnlyCounterpart(const Decl *counterpart) { + using namespace decls_block; + unsigned abbrCode = S.DeclTypeAbbrCodes[ABIOnlyCounterpartLayout::Code]; + ABIOnlyCounterpartLayout::emitRecord(S.Out, S.ScratchRecord, abbrCode, + S.addDeclRef(counterpart)); + } + void noteUseOfExportedPrespecialization(const AbstractFunctionDecl *afd) { bool hasNoted = false; for (auto *A : afd->getAttrs().getAttributes()) { @@ -6211,6 +6231,7 @@ void Serializer::writeAllDeclsAndTypes() { registerDeclTypeAbbr(); registerDeclTypeAbbr(); + registerDeclTypeAbbr(); registerDeclTypeAbbr(); diff --git a/test/attr/attr_abi.swift b/test/attr/attr_abi.swift new file mode 100644 index 0000000000000..ecfc5f06d7ec9 --- /dev/null +++ b/test/attr/attr_abi.swift @@ -0,0 +1,300 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-feature Extern -enable-experimental-feature ABIAttribute -parse-as-library + +// +// Same-kind checking +// + +@abi(func funcForFunc_abi()) +func funcForFunc() {} + +@abi(var varForVar_abi: Int) +var varForVar: Int = 0 + +@abi(func funcForVar_abi()) +var funcForVar: Int = 0 + +@abi(var varForFunc_abi: Int) +func varForFunc() {} + +// +// Function arity checking +// + +@abi(func param00_generic00() -> Int) +func param00_generic00() -> Int { fatalError() } + +@abi(func param10_generic00(_: Int) -> Int) +func param10_generic00() -> Int { fatalError() } + +@abi(func param01_generic00() -> Int) +func param01_generic00(_: Int) -> Int { fatalError() } + +@abi(func param11_generic00(_: Int) -> Int) +func param11_generic00(_: Int) -> Int { fatalError() } + + + +@abi(func param00_generic10() -> T) +func param00_generic10() -> Int { fatalError() } + +@abi(func param10_generic10(_: Int) -> T) +func param10_generic10() -> Int { fatalError() } + +@abi(func param01_generic10() -> T) +func param01_generic10(_: Int) -> Int { fatalError() } + +@abi(func param11_generic10(_: Int) -> T) +func param11_generic10(_: Int) -> Int { fatalError() } + + + +@abi(func param00_generic01() -> Int) +func param00_generic01() -> T { fatalError() } + +@abi(func param10_generic01(_: Int) -> Int) +func param10_generic01() -> T { fatalError() } + +@abi(func param01_generic01() -> Int) +func param01_generic01(_: Int) -> T { fatalError() } + +@abi(func param11_generic01(_: Int) -> Int) +func param11_generic01(_: Int) -> T { fatalError() } + + + +@abi(func param00_generic11() -> T) +func param00_generic11() -> T { fatalError() } + +@abi(func param10_generic11(_: Int) -> T) +func param10_generic11() -> T { fatalError() } + +@abi(func param01_generic11() -> T) +func param01_generic11(_: Int) -> T { fatalError() } + +@abi(func param11_generic11(_: Int) -> T) +func param11_generic11(_: Int) -> T { fatalError() } + +// +// Throws effect checking +// + +@abi(func throws00(_: () throws -> Void)) +func throws00(_: () throws -> Void) {} + +@abi(func throws10(_: () throws -> Void) throws) +func throws10(_: () throws -> Void) {} + +@abi(func throws20(_: () throws -> Void) rethrows) +func throws20(_: () throws -> Void) {} + +@abi(func throws01(_: () throws -> Void)) +func throws01(_: () throws -> Void) throws {} + +@abi(func throws11(_: () throws -> Void) throws) +func throws11(_: () throws -> Void) throws {} + +@abi(func throws21(_: () throws -> Void) rethrows) +func throws21(_: () throws -> Void) throws {} + +@abi(func throws02(_: () throws -> Void)) +func throws02(_: () throws -> Void) rethrows {} + +@abi(func throws12(_: () throws -> Void) throws) +func throws12(_: () throws -> Void) rethrows {} + +@abi(func throws22(_: () throws -> Void) rethrows) +func throws22(_: () throws -> Void) rethrows {} + +// +// Async effect checking +// + +@abi(func async00()) +func async00() {} + +@abi(func async10() async) +func async10() {} + +@abi(func async01()) +func async01() async {} + +@abi(func async11() async) +func async11() async {} + +// +// PBD shape checking +// + +@abi(var x1, y1: Int) +var x1: Int = 0 + +@abi(var x2: Int) +var x2 = 0, y2: Int = 0 + +@abi(var (x3, y3): (Int, Int), (a3, b3): (Int, Int)) +var (x3, y3): (Int, Int) = (0, 0), a3: Int = 0 + +@abi(var (x4, y4): (Int, Int), a4: Int) +var (x4, y4): (Int, Int) = (0, 0), (a4, b4): (Int, Int) = (0, 0) + +// +// Conflict diagnostics +// + +@abi(func noConflictWithSelf()) +func noConflictWithSelf() {} + +@abi(func noConflictWithMutualChanges1()) +func noConflictWithMutualChanges2() {} + +@abi(func noConflictWithMutualChanges2()) +func noConflictWithMutualChanges1() {} + +@abi(func conflictsWithOtherABI()) +func conflictsWithOtherABI1() {} + +@abi(func conflictsWithOtherABI()) +func conflictsWithOtherABI2() {} + +@abi(func conflictsWithNonABI()) +func conflictsWithNonABI_formal() {} + +func conflictsWithNonABI() {} + +@abi(var var_noConflictWithSelf: Int) +var var_noConflictWithSelf: Int = 0 + +@abi(var var_noConflictWithMutualChanges1: Int) +var var_noConflictWithMutualChanges2: Int = 0 + +@abi(var var_noConflictWithMutualChanges2: Int) +var var_noConflictWithMutualChanges1: Int = 0 + +@abi(var var_conflictsWithOtherABI: Int) +var var_conflictsWithOtherABI1: Int = 0 + +@abi(var var_conflictsWithOtherABI: Int) +var var_conflictsWithOtherABI2: Int = 0 + +@abi(var var_conflictsWithNonABI: Int) +var var_conflictsWithNonABI_formal: Int = 0 + +var var_conflictsWithNonABI: Int = 0 + +struct Foo { + @abi(func noConflictWithSelf()) + func noConflictWithSelf() {} + + @abi(func noConflictWithMutualChanges1()) + func noConflictWithMutualChanges2() {} + + @abi(func noConflictWithMutualChanges2()) + func noConflictWithMutualChanges1() {} + + @abi(func conflictsWithOtherABI()) + func conflictsWithOtherABI1() {} + + @abi(func conflictsWithOtherABI()) + func conflictsWithOtherABI2() {} + + @abi(func conflictsWithNonABI()) + func conflictsWithNonABI_formal() {} + + func conflictsWithNonABI() {} + + @abi(var var_noConflictWithSelf: Int) + var var_noConflictWithSelf: Int = 0 + + @abi(var var_noConflictWithMutualChanges1: Int) + var var_noConflictWithMutualChanges2: Int = 0 + + @abi(var var_noConflictWithMutualChanges2: Int) + var var_noConflictWithMutualChanges1: Int = 0 + + @abi(var var_conflictsWithOtherABI: Int) + var var_conflictsWithOtherABI1: Int = 0 + + @abi(var var_conflictsWithOtherABI: Int) + var var_conflictsWithOtherABI2: Int = 0 + + @abi(var var_conflictsWithNonABI: Int) + var var_conflictsWithNonABI_formal: Int = 0 + + var var_conflictsWithNonABI: Int = 0 +} + +func fn() { + @abi(func noConflictWithSelf()) + func noConflictWithSelf() {} + + @abi(func noConflictWithMutualChanges1()) + func noConflictWithMutualChanges2() {} + + @abi(func noConflictWithMutualChanges2()) + func noConflictWithMutualChanges1() {} + + @abi(func conflictsWithOtherABI()) + func conflictsWithOtherABI1() {} + + @abi(func conflictsWithOtherABI()) + func conflictsWithOtherABI2() {} + + @abi(func conflictsWithNonABI()) + func conflictsWithNonABI_formal() {} + + func conflictsWithNonABI() {} + + @abi(var var_noConflictWithSelf: Int) + var var_noConflictWithSelf: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + + @abi(var var_noConflictWithMutualChanges1: Int) + var var_noConflictWithMutualChanges2: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + + @abi(var var_noConflictWithMutualChanges2: Int) + var var_noConflictWithMutualChanges1: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + + @abi(var var_conflictsWithOtherABI: Int) + var var_conflictsWithOtherABI1: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + + @abi(var var_conflictsWithOtherABI: Int) + var var_conflictsWithOtherABI2: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + + @abi(var var_conflictsWithNonABI: Int) + var var_conflictsWithNonABI_formal: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + + var var_conflictsWithNonABI: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} +} + +// +// Incorrect usage +// + +@abi(func bar()) // expected-note {{attribute already specified here}} +@abi(func foo()) // expected-error {{duplicate attribute}} +func duplicateABIAttr() {} + +// Test parser recovery by having something that +// should parse fine. +func somethingThatShouldParseFine() {} + +func func_with_nested_abi() { + @abi(func _exit(_ code: UInt32) -> Void) + func exit(_ code : UInt32) -> Void {} + exit(0) +} + +// +// Examples of expected use cases +// + +@abi(func originallySendable() -> @Sendable () -> Void) +func originallySendable() -> sending () -> Void { fatalError() } + +@abi(func originallyGenericSendable() -> T) +func originallyGenericSendable() -> sending T { fatalError() } + +@abi(func originallyAnySendable() -> any Sendable) +func originallyAnySendable() -> sending Any { fatalError() } + +@abi(nonisolated func explicitIsolationChanged() -> @Sendable () -> Void) +@MainActor func explicitIsolationChanged() -> sending () -> Void { fatalError() } From 01b8bbea89afcf4da03831e162ca80517bfc1827 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Tue, 8 Oct 2024 14:35:50 -0700 Subject: [PATCH 07/15] Tie attributes to language features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new `DECL_ATTR_FEATURE_REQUIREMENT` macro in DeclAttr.def can be used to declare that an attribute should only be available when a related language feature is enabled. Effects: • `#if hasAttribute(someAttr)` will return `false` unless the required feature is enabled. • Code completion will not include the attribute unless the required feature is enabled. • `TypeChecker::checkDeclAttributes()` diagnoses non-implicit uses of the attribute. Add this mechanism and use it to tie @abi to the ABIAttribute feature. Also design tests for it. --- include/swift/AST/Attr.h | 3 + include/swift/AST/DeclAttr.def | 12 ++ include/swift/IDE/CompletionLookup.h | 2 +- lib/AST/Attr.cpp | 16 +++ lib/IDE/CodeCompletion.cpp | 16 ++- lib/IDE/CompletionLookup.cpp | 11 +- lib/Sema/TypeCheckAttr.cpp | 12 ++ ...e_decl_attribute_feature_requirement.swift | 126 ++++++++++++++++++ .../Misc/verify-swift-feature-testing.test-sh | 4 + test/attr/feature_requirement.swift | 16 +++ tools/swift-ide-test/swift-ide-test.cpp | 32 ++++- 11 files changed, 236 insertions(+), 14 deletions(-) create mode 100644 test/IDE/complete_decl_attribute_feature_requirement.swift create mode 100644 test/attr/feature_requirement.swift diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 66c25d8d712b6..aeed6619fb640 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -32,6 +32,7 @@ #include "swift/AST/StorageImpl.h" #include "swift/Basic/Debug.h" #include "swift/Basic/EnumTraits.h" +#include "swift/Basic/Feature.h" #include "swift/Basic/InlineBitfield.h" #include "swift/Basic/Located.h" #include "swift/Basic/OptimizationMode.h" @@ -491,6 +492,8 @@ class DeclAttribute : public AttributeBase { LLVM_READNONE static bool canAttributeAppearOnDeclKind(DeclAttrKind DAK, DeclKind DK); + static std::optional getRequiredFeature(DeclAttrKind DK); + /// Returns the source name of the attribute, without the @ or any arguments. StringRef getAttrName() const; diff --git a/include/swift/AST/DeclAttr.def b/include/swift/AST/DeclAttr.def index d8f7a80e494d0..12d7048e5c093 100644 --- a/include/swift/AST/DeclAttr.def +++ b/include/swift/AST/DeclAttr.def @@ -42,6 +42,16 @@ DECL_ATTR_ALIAS(SPELLING, CLASS) #endif +// Diagnose any use of the attribute CLASS without FEATURE_NAME enabled, +// and also enable other special behavior. If you use this for an experimental +// feature, please add test cases to: +// +// * test/attr/feature_requirement.swift +// * test/IDE/complete_decl_attribute_feature_requirement.swift +#ifndef DECL_ATTR_FEATURE_REQUIREMENT +#define DECL_ATTR_FEATURE_REQUIREMENT(CLASS, FEATURE_NAME) +#endif + #ifndef LAST_DECL_ATTR #define LAST_DECL_ATTR(CLASS) #endif @@ -526,6 +536,7 @@ DECL_ATTR(safe, Safe, DECL_ATTR(abi, ABI, OnAbstractFunction | OnVar /* will eventually add types */ | LongAttribute | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove, 164) +DECL_ATTR_FEATURE_REQUIREMENT(ABI, ABIAttribute) LAST_DECL_ATTR(ABI) @@ -535,4 +546,5 @@ LAST_DECL_ATTR(ABI) #undef CONTEXTUAL_SIMPLE_DECL_ATTR #undef DECL_ATTR #undef CONTEXTUAL_DECL_ATTR +#undef DECL_ATTR_FEATURE_REQUIREMENT #undef LAST_DECL_ATTR diff --git a/include/swift/IDE/CompletionLookup.h b/include/swift/IDE/CompletionLookup.h index a61b4515cadfe..4d42cbcb21925 100644 --- a/include/swift/IDE/CompletionLookup.h +++ b/include/swift/IDE/CompletionLookup.h @@ -600,7 +600,7 @@ class CompletionLookup final : public swift::VisibleDeclConsumer { SourceLoc CodeCompletionLoc); static bool canUseAttributeOnDecl(DeclAttrKind DAK, bool IsInSil, - bool IsConcurrencyEnabled, + const LangOptions &langOpts, std::optional DK, StringRef Name); void getAttributeDeclCompletions(bool IsInSil, std::optional DK); diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index c1aef83673d07..eeee0d0e0370e 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -1694,6 +1694,18 @@ uint64_t DeclAttribute::getOptions(DeclAttrKind DK) { llvm_unreachable("bad DeclAttrKind"); } +std::optional DeclAttribute::getRequiredFeature(DeclAttrKind DK) { + switch (DK) { +#define DECL_ATTR_FEATURE_REQUIREMENT(CLASS, FEATURE_NAME) \ + case DeclAttrKind::CLASS: \ + return Feature::FEATURE_NAME; +#include "swift/AST/DeclAttr.def" + default: + return std::nullopt; + } + llvm_unreachable("bad DeclAttrKind"); +} + StringRef DeclAttribute::getAttrName() const { switch (getKind()) { #define SIMPLE_DECL_ATTR(NAME, CLASS, ...) \ @@ -2928,6 +2940,10 @@ static bool hasDeclAttribute(const LangOptions &langOpts, if (DeclAttribute::isConcurrencyOnly(*kind)) return false; + if (auto feature = DeclAttribute::getRequiredFeature(*kind)) + if (!langOpts.hasFeature(*feature)) + return false; + return true; } diff --git a/lib/IDE/CodeCompletion.cpp b/lib/IDE/CodeCompletion.cpp index 81dd43885a5fe..d5934f15bd8b1 100644 --- a/lib/IDE/CodeCompletion.cpp +++ b/lib/IDE/CodeCompletion.cpp @@ -686,7 +686,7 @@ static void addKeyword(CodeCompletionResultSink &Sink, StringRef Name, } static void addDeclKeywords(CodeCompletionResultSink &Sink, DeclContext *DC, - bool IsConcurrencyEnabled) { + const LangOptions &langOpts) { auto isTypeDeclIntroducer = [](CodeCompletionKeywordKind Kind, std::optional DAK) -> bool { switch (Kind) { @@ -799,10 +799,16 @@ static void addDeclKeywords(CodeCompletionResultSink &Sink, DeclContext *DC, return; // Remove keywords only available when concurrency is enabled. - if (DAK.has_value() && !IsConcurrencyEnabled && + if (DAK.has_value() && !langOpts.EnableExperimentalConcurrency && DeclAttribute::isConcurrencyOnly(*DAK)) return; + // Remove experimental keywords. + if (DAK.has_value()) + if (auto feature = DeclAttribute::getRequiredFeature(*DAK)) + if (!langOpts.hasFeature(*feature)) + return; + CodeCompletionFlair flair = getFlair(Kind, DAK); // Special case for 'actor'. Get the same flair with 'kw_class'. @@ -1019,8 +1025,7 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink, LLVM_FALLTHROUGH; } case CompletionKind::StmtOrExpr: - addDeclKeywords(Sink, CurDeclContext, - Context.LangOpts.EnableExperimentalConcurrency); + addDeclKeywords(Sink, CurDeclContext, Context.LangOpts); addStmtKeywords(Sink, CurDeclContext, MaybeFuncBody); addClosureSignatureKeywordsIfApplicable(Sink, CurDeclContext); @@ -1122,8 +1127,7 @@ void CodeCompletionCallbacksImpl::addKeywords(CodeCompletionResultSink &Sink, .Default(false); }) != ParsedKeywords.end(); if (!HasDeclIntroducer) { - addDeclKeywords(Sink, CurDeclContext, - Context.LangOpts.EnableExperimentalConcurrency); + addDeclKeywords(Sink, CurDeclContext, Context.LangOpts); addLetVarKeywords(Sink); } break; diff --git a/lib/IDE/CompletionLookup.cpp b/lib/IDE/CompletionLookup.cpp index b9f11ee573621..a6e0d6726fdf9 100644 --- a/lib/IDE/CompletionLookup.cpp +++ b/lib/IDE/CompletionLookup.cpp @@ -3023,7 +3023,7 @@ void CompletionLookup::getGenericRequirementCompletions( } bool CompletionLookup::canUseAttributeOnDecl(DeclAttrKind DAK, bool IsInSil, - bool IsConcurrencyEnabled, + const LangOptions &langOpts, std::optional DK, StringRef Name) { if (DeclAttribute::isUserInaccessible(DAK)) @@ -3034,8 +3034,12 @@ bool CompletionLookup::canUseAttributeOnDecl(DeclAttrKind DAK, bool IsInSil, return false; if (!IsInSil && DeclAttribute::isSilOnly(DAK)) return false; - if (!IsConcurrencyEnabled && DeclAttribute::isConcurrencyOnly(DAK)) + if (!langOpts.EnableExperimentalConcurrency + && DeclAttribute::isConcurrencyOnly(DAK)) return false; + if (auto feature = DeclAttribute::getRequiredFeature(DAK)) + if (!langOpts.hasFeature(*feature)) + return false; if (!DK.has_value()) return true; // Hide underscored attributes even if they are not marked as user @@ -3059,11 +3063,10 @@ void CompletionLookup::getAttributeDeclCompletions(bool IsInSil, #include "swift/AST/DeclNodes.def" } } - bool IsConcurrencyEnabled = Ctx.LangOpts.EnableExperimentalConcurrency; std::string Description = TargetName.str() + " Attribute"; #define DECL_ATTR_ALIAS(KEYWORD, NAME) DECL_ATTR(KEYWORD, NAME, 0, 0) #define DECL_ATTR(KEYWORD, NAME, ...) \ - if (canUseAttributeOnDecl(DeclAttrKind::NAME, IsInSil, IsConcurrencyEnabled, \ + if (canUseAttributeOnDecl(DeclAttrKind::NAME, IsInSil, Ctx.LangOpts, \ DK, #KEYWORD)) \ addDeclAttrKeyword(#KEYWORD, Description); #include "swift/AST/DeclAttr.def" diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index de0ed02228ac2..3d422fd18fcf1 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -1789,6 +1789,18 @@ void TypeChecker::checkDeclAttributes(Decl *D) { for (auto attr : D->getExpandedAttrs()) { if (!attr->isValid()) continue; + // If the attribute requires a feature that is not enabled, and it is not + // an implicit attribute, diagnose and disable it. + if (auto feature = DeclAttribute::getRequiredFeature(attr->getKind())) { + if (!attr->isImplicit() + && !D->getASTContext().LangOpts.hasFeature(*feature)) { + Checker.diagnoseAndRemoveAttr(attr, diag::requires_experimental_feature, + attr->getAttrName(), false, + getFeatureName(*feature)); + continue; + } + } + // If Attr.def says that the attribute cannot appear on this kind of // declaration, diagnose it and disable it. if (attr->canAppearOnDecl(D)) { diff --git a/test/IDE/complete_decl_attribute_feature_requirement.swift b/test/IDE/complete_decl_attribute_feature_requirement.swift new file mode 100644 index 0000000000000..adeb35c9ed7af --- /dev/null +++ b/test/IDE/complete_decl_attribute_feature_requirement.swift @@ -0,0 +1,126 @@ +// This contains code completion test cases for features covered by experimental +// feature flags, and tests both the case when the feature is disabled and when +// it's enabled. When a feature becomes non-experimental, move its test cases +// into the normal complete_decl_attribute.swift test file. + +// REQUIRES: asserts + +// RUN: %batch-code-completion -filecheck-additional-suffix _DISABLED +// RUN: %batch-code-completion -filecheck-additional-suffix _ENABLED -enable-experimental-feature ABIAttribute + +// NOTE: Please do not include the ", N items" after "Begin completions". The +// item count creates needless merge conflicts given that an "End completions" +// line exists for each test. + +@#^KEYWORD2^# func method(){} + +// KEYWORD2: Begin completions +// KEYWORD2_ENABLED-DAG: Keyword/None: abi[#Func Attribute#]; name=abi +// KEYWORD2_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD2: End completions + +@#^KEYWORD3^# class C {} + +// KEYWORD3: Begin completions +// KEYWORD3_ENABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD3_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD3: End completions + +@#^KEYWORD3_2?check=KEYWORD3^#IB class C2 {} +// Same as KEYWORD3. + +@#^KEYWORD4^# enum E {} +// KEYWORD4: Begin completions +// KEYWORD4_ENABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD4_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD4: End completions + +@#^KEYWORD5^# struct S{} +// KEYWORD5: Begin completions +// KEYWORD5_ENABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD5_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// KEYWORD5: End completions + +@#^ON_GLOBALVAR^# var globalVar +// ON_GLOBALVAR: Begin completions +// ON_GLOBALVAR_ENABLED-DAG: Keyword/None: abi[#Var Attribute#]; name=abi +// ON_GLOBALVAR_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_GLOBALVAR: End completions + +struct _S { + @#^ON_INIT^# init() +// ON_INIT: Begin completions +// ON_INIT_ENABLED-DAG: Keyword/None: abi[#Constructor Attribute#]; name=abi +// ON_INIT_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_INIT: End completions + + @#^ON_PROPERTY^# var foo +// ON_PROPERTY: Begin completions +// ON_PROPERTY_ENABLED-DAG: Keyword/None: abi[#Var Attribute#]; name=abi +// ON_PROPERTY_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_PROPERTY: End completions + + @#^ON_METHOD^# private + func foo() +// ON_METHOD: Begin completions +// ON_METHOD_ENABLED-DAG: Keyword/None: abi[#Func Attribute#]; name=abi +// ON_METHOD_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_METHOD: End completions + + + func bar(@#^ON_PARAM_1?check=ON_PARAM^#) +// ON_PARAM: Begin completions +// ON_PARAM_ENABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_PARAM_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_PARAM: End completions + + func bar( + @#^ON_PARAM_2?check=ON_PARAM^# + + arg: Int + ) +// Same as ON_PARAM. + + @#^ON_MEMBER_INDEPENDENT_1?check=ON_MEMBER_LAST^# + + func dummy1() {} +// Same as ON_MEMBER_LAST. + + @#^ON_MEMBER_INDEPENDENT_2?check=ON_MEMBER_LAST^# + func dummy2() {} +// Same as ON_MEMBER_LAST. + + + @#^ON_MEMBER_LAST^# +// ON_MEMBER_LAST: Begin completions +// ON_MEMBER_LAST_ENABLED-DAG: Keyword/None: abi[#Declaration Attribute#]; name=abi +// ON_MEMBER_LAST_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// ON_MEMBER_LAST: End completions +} + +func takeClosure(_: () -> Void) { + takeClosure { @#^IN_CLOSURE^# in + print("x") + } +} +// IN_CLOSURE: Begin completions +// FIXME: Not valid in this position (but CompletionLookup can't tell that) +// IN_CLOSURE_ENABLED-DAG: Keyword/None: abi[#Declaration Attribute#]; name=abi +// IN_CLOSURE_DISABLED-NOT: Keyword/None: abi[#{{.*}} Attribute#]; name=abi +// IN_CLOSURE: End completions + +@#^KEYWORD_INDEPENDENT_1?check=KEYWORD_LAST^# + +func dummy1() {} +// Same as KEYWORD_LAST. + +@#^KEYWORD_INDEPENDENT_2?check=KEYWORD_LAST^# +func dummy2() {} +// Same as KEYWORD_LAST. + +@#^KEYWORD_LAST^# + +// KEYWORD_LAST: Begin completions +// KEYWORD_LAST_ENABLED-DAG: Keyword/None: abi[#Declaration Attribute#]; name=abi +// KEYWORD_LAST_DISABLED-NOT: Keyword/None: abi[#Declaration Attribute#]; name=abi +// KEYWORD_LAST: End completions diff --git a/test/Misc/verify-swift-feature-testing.test-sh b/test/Misc/verify-swift-feature-testing.test-sh index 9edfd59fc302f..acfcc0e305f9a 100755 --- a/test/Misc/verify-swift-feature-testing.test-sh +++ b/test/Misc/verify-swift-feature-testing.test-sh @@ -20,6 +20,10 @@ EXCEPTIONAL_FILES = [ pathlib.Path("test/ModuleInterface/swift-export-as.swift"), # Uses the pseudo-feature AvailabilityMacro= pathlib.Path("test/Sema/availability_define.swift"), + # Tests behavior when you try to use a feature without enabling it + pathlib.Path("test/attr/feature_requirement.swift"), + # Tests completion with features both enabled and disabled + pathlib.Path("test/IDE/complete_decl_attribute_feature_requirement.swift"), ] FEATURE_USAGE_RE = re.compile( diff --git a/test/attr/feature_requirement.swift b/test/attr/feature_requirement.swift new file mode 100644 index 0000000000000..f0f0d7d413d5f --- /dev/null +++ b/test/attr/feature_requirement.swift @@ -0,0 +1,16 @@ +// RUN: %target-typecheck-verify-swift -parse-as-library -verify-additional-prefix disabled- +// RUN: %target-typecheck-verify-swift -parse-as-library -verify-additional-prefix enabled- -enable-experimental-feature ABIAttribute + +// REQUIRES: asserts + +// This test checks whether DECL_ATTR_FEATURE_REQUIREMENT is being applied correctly. +// It is expected to need occasional edits as experimental features are stabilized. + +@abi(func fn()) +func fn() {} // expected-disabled-error@-1 {{'abi' attribute is only valid when experimental feature ABIAttribute is enabled}} + +#if hasAttribute(abi) + #error("does have @abi") // expected-enabled-error {{does have @abi}} +#else + #error("doesn't have @abi") // expected-disabled-error {{doesn't have @abi}} +#endif diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index c7be87d30625a..37df87bbdab03 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -457,6 +457,11 @@ FileCheckPath("filecheck", llvm::cl::value_desc("path"), static llvm::cl::opt SkipFileCheck("skip-filecheck", llvm::cl::desc("Skip 'FileCheck' checking"), llvm::cl::cat(Category)); +static llvm::cl::opt +FileCheckSuffix("filecheck-additional-suffix", + llvm::cl::value_desc("check-prefix-suffix"), + llvm::cl::desc("Additional suffix to add to check prefixes as an alternative"), + llvm::cl::cat(Category)); // '-code-completion' options. @@ -1537,11 +1542,32 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok, options::CodeCompletionToken != Token.Name) continue; + SmallVector expandedCheckPrefixes; + llvm::errs() << "----\n"; llvm::errs() << "Token: " << Token.Name << "; offset=" << Token.Offset << "; pos=" << Token.Line << ":" << Token.Column; - for (auto Prefix : Token.CheckPrefixes) { - llvm::errs() << "; check=" << Prefix; + for (auto joinedPrefix : Token.CheckPrefixes) { + if (options::FileCheckSuffix.empty()) { + // Simple case: just copy what we have + expandedCheckPrefixes.push_back(joinedPrefix.str()); + } else { + // For each comma-separated prefix, insert a variant with the suffix + // added to it: "X,Y" with suffix "_FOO" -> "X,X_FOO,Y,Y_FOO" + std::string expandedPrefix; + llvm::raw_string_ostream os(expandedPrefix); + + SmallVector splitPrefix; + joinedPrefix.split(splitPrefix, ','); + + llvm::interleaveComma(splitPrefix, os, [&](StringRef prefix) { + os << prefix << ',' << prefix << options::FileCheckSuffix; + }); + + expandedCheckPrefixes.push_back(expandedPrefix); + } + + llvm::errs() << "; check=" << expandedCheckPrefixes.back(); } llvm::errs() << "\n"; @@ -1662,7 +1688,7 @@ static int doBatchCodeCompletion(const CompilerInvocation &InitInvok, assert(!options::FileCheckPath.empty()); bool isFileCheckFailed = false; - for (auto Prefix : Token.CheckPrefixes) { + for (auto Prefix : expandedCheckPrefixes) { StringRef FileCheckArgs[] = {options::FileCheckPath, SourceFilename, "--check-prefixes", Prefix, "--input-file", resultFilename}; From 08e2a4ddaee55dcb141b8dab7975793cdc45678f Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Thu, 26 Sep 2024 15:20:32 -0700 Subject: [PATCH 08/15] Type check ABI decls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sema now type-checks the alternate ABI-providing decls inside of @abi attributes. Making this work—particularly, making redeclaration checking work—required making name lookup aware of ABI decls. Name lookup now evaluates both API-providing and ABI-providing declarations. In most cases, it will filter ABI-only decls out unless a specific flag is passed, in which case it will filter API-only decls out instead. Calls that simply retrieve a list of declarations, like `IterableDeclContext::getMembers()` and friends, typically only return API-providing decls; you have to access the ABI-providing ones through those. As part of that work, I have also added some basic compiler interfaces for working with the API-providing and ABI-providing variants. `ABIRole` encodes whether a declaration provides only API, only ABI, or both, and `ABIRoleInfo` combines that with a pointer to the counterpart providing the other role (for a declaration that provides both, that’ll just be a pointer to `this`). Decl checking of behavior specific to @abi will come in a future commit. Note that this probably doesn’t properly exercise some of the new code (ASTScope::lookupEnclosingABIAttributeScope(), for instance); I expect that to happen only once we can rename types using an @abi attribute, since that will create distinguishable behavior differences when resolving TypeReprs in other @abi attributes. --- include/swift/AST/ASTScope.h | 38 ++++++ include/swift/AST/ASTScopeNodes.def | 1 + include/swift/AST/Decl.h | 124 ++++++++++++++++++ include/swift/AST/LookupKinds.h | 7 + include/swift/AST/NameLookup.h | 26 ++++ lib/AST/ASTDumper.cpp | 3 + lib/AST/ASTScope.cpp | 5 + lib/AST/ASTScopeCreation.cpp | 44 ++++++- lib/AST/ASTScopeLookup.cpp | 26 +++- lib/AST/ASTScopeSourceRange.cpp | 5 + lib/AST/ASTVerifier.cpp | 25 ++++ lib/AST/Decl.cpp | 31 +++++ lib/AST/Module.cpp | 32 +++-- lib/AST/ModuleNameLookup.cpp | 14 +- lib/AST/NameLookup.cpp | 71 +++++++--- lib/AST/NameLookupRequests.cpp | 77 ++++++----- lib/AST/UnqualifiedLookup.cpp | 33 ++++- lib/Sema/TypeCheckAttr.cpp | 15 +++ lib/Sema/TypeCheckDeclPrimary.cpp | 77 ++++++++--- lib/Sema/TypeCheckExpr.cpp | 3 + lib/Sema/TypeCheckNameLookup.cpp | 7 + lib/Sema/TypeCheckStmt.cpp | 3 +- lib/Sema/TypeChecker.h | 3 + lib/Serialization/ModuleFile.cpp | 16 ++- lib/Serialization/ModuleFile.h | 3 +- lib/Serialization/SerializedModuleLoader.cpp | 2 +- test/attr/attr_abi.swift | 73 ++++++----- .../lib/SwiftLang/SwiftDocSupport.cpp | 5 + 28 files changed, 629 insertions(+), 140 deletions(-) diff --git a/include/swift/AST/ASTScope.h b/include/swift/AST/ASTScope.h index fc7ae83327096..6620970c9e881 100644 --- a/include/swift/AST/ASTScope.h +++ b/include/swift/AST/ASTScope.h @@ -142,6 +142,7 @@ class ASTScopeImpl : public ASTAllocated { friend class IterableTypeBodyPortion; friend class ScopeCreator; friend class ASTSourceFileScope; + friend class ABIAttributeScope; friend class Lowering::SILGenFunction; #pragma mark - tree state @@ -305,6 +306,9 @@ class ASTScopeImpl : public ASTAllocated { SourceFile *sourceFile, SourceLoc loc, llvm::function_ref consume); + static ABIAttr *lookupEnclosingABIAttributeScope( + SourceFile *sourceFile, SourceLoc loc); + static CatchNode lookupCatchNode(ModuleDecl *module, SourceLoc loc); /// Scopes that cannot bind variables may set this to true to create more @@ -948,6 +952,40 @@ class CustomAttributeScope final : public ASTScopeImpl { } }; +/// The scope for ABI attributes and their arguments. +/// +/// Source locations for the attribute name and its arguments are in the +/// custom attribute, so lookup is invoked from within the attribute +/// itself. +class ABIAttributeScope final : public ASTScopeImpl { +public: + ABIAttr *attr; + Decl *decl; + + ABIAttributeScope(ABIAttr *attr, Decl *decl) + : ASTScopeImpl(ScopeKind::ABIAttribute), attr(attr), decl(decl) {} + virtual ~ABIAttributeScope() {} + +protected: + ASTScopeImpl *expandSpecifically(ScopeCreator &) override; + +public: + SourceRange + getSourceRangeOfThisASTNode(bool omitAssertions = false) const override; + NullablePtr addressForPrinting() const override { return decl; } + + bool ignoreInDebugInfo() const override { return true; } + +private: + AnnotatedInsertionPoint + expandAScopeThatCreatesANewInsertionPoint(ScopeCreator &); + +public: + static bool classof(const ASTScopeImpl *scope) { + return scope->getKind() == ScopeKind::ABIAttribute; + } +}; + /// PatternBindingDecl's (PBDs) are tricky (See the comment for \c /// PatternBindingDecl): /// diff --git a/include/swift/AST/ASTScopeNodes.def b/include/swift/AST/ASTScopeNodes.def index 79f2ed0d8b13c..0c8137021c32a 100644 --- a/include/swift/AST/ASTScopeNodes.def +++ b/include/swift/AST/ASTScopeNodes.def @@ -89,6 +89,7 @@ SCOPE_NODE(CaseLabelItem) SCOPE_NODE(CaseStmtBody) STMT_SCOPE_NODE(BraceStmt) EXPR_SCOPE_NODE(Try) +DECL_ATTRIBUTE_SCOPE_NODE(ABIAttribute) #undef DECL_ATTRIBUTE_SCOPE_NODE #undef EXPR_SCOPE_NODE diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index bbb5545dfb237..2d0b3321d5650 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -95,6 +95,7 @@ namespace swift { class MacroDefinition; class ModuleDecl; class NamedPattern; + enum NLOptions : unsigned; class EnumCaseDecl; class EnumElementDecl; class ParameterList; @@ -4354,6 +4355,9 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext { IncludeAttrImplements = 1 << 0, /// Whether to exclude members of macro expansions. ExcludeMacroExpansions = 1 << 1, + /// If @abi attributes are present, return the decls representing the ABI, + /// not the API. + ABIProviding = 1 << 2, }; /// Find all of the declarations with the given name within this nominal type @@ -9788,6 +9792,126 @@ const ParamDecl *getParameterAt(const ValueDecl *source, unsigned index); /// nullptr if the source does not have a parameter list. const ParamDecl *getParameterAt(const DeclContext *source, unsigned index); +class ABIRole { +public: + enum Value : uint8_t { + Neither = 0, + ProvidesABI = 1 << 0, + ProvidesAPI = 1 << 1, + Either = ProvidesABI | ProvidesAPI, + }; + + Value value; + + ABIRole(Value value) + : value(value) + { } + + ABIRole() + : ABIRole(Neither) + { } + + explicit ABIRole(NLOptions opts); + + template + explicit ABIRole(OptionSet flags) + : value(flags.contains(FlagType::ABIProviding) ? ProvidesABI : ProvidesAPI) + { } + + inline ABIRole operator|(ABIRole rhs) const { + return ABIRole(ABIRole::Value(value | rhs.value)); + } + inline ABIRole &operator|=(ABIRole rhs) { + value = ABIRole::Value(value | rhs.value); + return *this; + } + inline ABIRole operator&(ABIRole rhs) const { + return ABIRole(ABIRole::Value(value & rhs.value)); + } + inline ABIRole &operator&=(ABIRole rhs) { + value = ABIRole::Value(value & rhs.value); + return *this; + } + inline ABIRole operator~() const { + return ABIRole(ABIRole::Value(~value)); + } + + operator bool() const { + return value != Neither; + } +}; + +namespace abi_role_detail { + +using Storage = llvm::PointerIntPair; +Storage computeStorage(Decl *decl); + +} + +/// Specifies the \c ABIAttr -related behavior of this declaration +/// and provides access to its counterpart. +/// +/// A given declaration may provide the API, the ABI, or both. If it provides +/// API, the counterpart is the matching ABI-providing decl; if it provides +/// ABI, the countepart is the matching API-providing decl. A declaration +/// which provides both API and ABI is its own counterpart. +/// +/// If the counterpart is \c nullptr , this indicates a fundamental mismatch +/// between decl and counterpart. Sometimes this mismatch is a difference in +/// decl kind; in these cases, \c getCounterpartUnchecked() will return the +/// incorrect counterpart. +template +class ABIRoleInfo { + friend abi_role_detail::Storage abi_role_detail::computeStorage(Decl *); + + abi_role_detail::Storage counterpartAndFlags; + + ABIRoleInfo(abi_role_detail::Storage storage) + : counterpartAndFlags(storage) + { } + +public: + explicit ABIRoleInfo(const SpecificDecl *decl) + : ABIRoleInfo(abi_role_detail::computeStorage(const_cast(decl))) + { } + + Decl *getCounterpartUnchecked() const { + return counterpartAndFlags.getPointer(); + } + + SpecificDecl *getCounterpart() const { + return dyn_cast_or_null(getCounterpartUnchecked()); + } + + ABIRole getRole() const { + return ABIRole(ABIRole::Value(counterpartAndFlags.getInt())); + } + + bool matches(ABIRole desiredRole) const { + return getRole() & desiredRole; + } + + template + bool matchesOptions(Options opts) const { + return matches(ABIRole(opts)); + } + + bool providesABI() const { + return matches(ABIRole::ProvidesABI); + } + + bool providesAPI() const { + return matches(ABIRole::ProvidesAPI); + } + + bool hasABIAttr() const { + return !providesABI(); + } +}; + +template +ABIRoleInfo(const SpecificDecl *decl) -> ABIRoleInfo; + StringRef getAccessorNameForDiagnostic(AccessorDecl *accessor, bool article, std::optional underscored = std::nullopt); diff --git a/include/swift/AST/LookupKinds.h b/include/swift/AST/LookupKinds.h index e697cc95299b6..03356ec51fda8 100644 --- a/include/swift/AST/LookupKinds.h +++ b/include/swift/AST/LookupKinds.h @@ -64,6 +64,10 @@ enum NLOptions : unsigned { /// from a module that has not been imported. NL_IgnoreMissingImports = 1 << 9, + /// If @abi attributes are present, return the decls representing the ABI, + /// not the API. + NL_ABIProviding = 1 << 10, + /// The default set of options used for qualified name lookup. /// /// FIXME: Eventually, add NL_ProtocolMembers to this, once all of the @@ -97,6 +101,9 @@ void simple_display(llvm::raw_ostream &out, NLOptions options); enum class ModuleLookupFlags : unsigned { /// Exclude names introduced by macro expansions in the top-level module. ExcludeMacroExpansions = 1 << 0, + /// If @abi attributes are present, return the decls representing the ABI, + /// not the API. + ABIProviding = 1 << 1, }; } // end namespace swift diff --git a/include/swift/AST/NameLookup.h b/include/swift/AST/NameLookup.h index 6d69c40b8a6af..6c85c51827ddf 100644 --- a/include/swift/AST/NameLookup.h +++ b/include/swift/AST/NameLookup.h @@ -253,6 +253,9 @@ enum class UnqualifiedLookupFlags { /// This lookup should include members that would otherwise be filtered out /// because they come from a module that has not been imported. IgnoreMissingImports = 1 << 10, + /// If @abi attributes are present, return the decls representing the ABI, + /// not the API. + ABIProviding = 1 << 11, // Reminder: If you add a flag, make sure you update simple_display() below }; @@ -564,6 +567,9 @@ template void filterForDiscriminator(SmallVectorImpl &results, DebuggerClient *debugClient); +/// \returns Whether the given source location is inside an \@abi attribute. +bool isInABIAttr(SourceFile *sourceFile, SourceLoc loc); + /// \returns The set of macro declarations with the given name that /// fulfill any of the given macro roles. SmallVector lookupMacros(DeclContext *dc, @@ -758,8 +764,16 @@ class ASTScope : public ASTAllocated { /// local declarations inside the innermost syntactic scope only. static void lookupLocalDecls(SourceFile *, DeclName, SourceLoc, bool stopAfterInnermostBraceStmt, + ABIRole roleFilter, SmallVectorImpl &); + static void lookupLocalDecls(SourceFile *sf, DeclName name, SourceLoc loc, + bool stopAfterInnermostBraceStmt, + SmallVectorImpl &results) { + lookupLocalDecls(sf, name, loc, stopAfterInnermostBraceStmt, + ABIRole::ProvidesAPI, results); + } + /// Returns the result if there is exactly one, nullptr otherwise. static ValueDecl *lookupSingleLocalDecl(SourceFile *, DeclName, SourceLoc); @@ -804,6 +818,18 @@ class ASTScope : public ASTAllocated { SourceFile *sourceFile, SourceLoc loc, llvm::function_ref consume); + /// Look up the scope tree for the nearest enclosing ABI attribute at + /// the given source location. + /// + /// \param sourceFile The source file containing the given location. + /// \param loc The source location to start lookup from. + /// \param consume A function that is called when an ABI attribute + /// scope is found. If \c consume returns \c true, lookup + /// will stop. If \c consume returns \c false, lookup will + /// continue up the scope tree. + static ABIAttr *lookupEnclosingABIAttributeScope( + SourceFile *sourceFile, SourceLoc loc); + /// Look up the scope tree for the nearest point at which an error thrown from /// this location can be caught or rethrown. /// diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 7caf34efc2263..4fccbee25b386 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -991,6 +991,7 @@ namespace { TerminalColor Color = DeclColor) { printFieldQuotedRaw([&](raw_ostream &OS) { declRef.dump(OS); }, label, Color); + printFlag(!ABIRoleInfo(declRef.getDecl()).providesAPI(), "abi_only_decl"); } void printThrowDest(ThrownErrorDestination throws, bool wantNothrow) { @@ -1186,6 +1187,8 @@ namespace { printFieldQuoted(implAttr->CategoryName.str(), label); } + printFlag(!ABIRoleInfo(D).providesAPI(), "abi_only"); + printSourceRange(D->getSourceRange(), &D->getASTContext()); printFlag(D->TrailingSemiLoc.isValid(), "trailing_semi", DeclModifierColor); diff --git a/lib/AST/ASTScope.cpp b/lib/AST/ASTScope.cpp index 9a44b6fdaf86b..20d1abde72285 100644 --- a/lib/AST/ASTScope.cpp +++ b/lib/AST/ASTScope.cpp @@ -67,6 +67,11 @@ void ASTScope::lookupEnclosingMacroScope( return ASTScopeImpl::lookupEnclosingMacroScope(sourceFile, loc, body); } +ABIAttr *ASTScope::lookupEnclosingABIAttributeScope( + SourceFile *sourceFile, SourceLoc loc) { + return ASTScopeImpl::lookupEnclosingABIAttributeScope(sourceFile, loc); +} + CatchNode ASTScope::lookupCatchNode(ModuleDecl *module, SourceLoc loc) { return ASTScopeImpl::lookupCatchNode(module, loc); } diff --git a/lib/AST/ASTScopeCreation.cpp b/lib/AST/ASTScopeCreation.cpp index 2adb187178263..2ddca3e9fb2be 100644 --- a/lib/AST/ASTScopeCreation.cpp +++ b/lib/AST/ASTScopeCreation.cpp @@ -484,17 +484,24 @@ class NodeAdder SmallVector localFuncsAndTypes; SmallVector localVars; + auto addDecl = [&](ValueDecl *vd) { + if (isa(vd) || isa(vd)) { + localFuncsAndTypes.push_back(vd); + } else if (auto *var = dyn_cast(vd)) { + localVars.push_back(var); + } + }; + // All types and functions are visible anywhere within a brace statement // scope. When ordering matters (i.e. var decl) we will have split the brace // statement into nested scopes. for (auto braceElement : bs->getElements()) { if (auto localBinding = braceElement.dyn_cast()) { if (auto *vd = dyn_cast(localBinding)) { - if (isa(vd) || isa(vd)) { - localFuncsAndTypes.push_back(vd); - } else if (auto *var = dyn_cast(localBinding)) { - localVars.push_back(var); - } + addDecl(vd); + auto abiRole = ABIRoleInfo(vd); + if (!abiRole.providesABI()) + addDecl(abiRole.getCounterpart()); } } } @@ -616,6 +623,9 @@ void ScopeCreator::addChildrenForKnownAttributes(Decl *decl, if (isa(attr)) relevantAttrs.push_back(attr); + + if (isa(attr)) + relevantAttrs.push_back(attr); } // Decl::getAttrs() is a linked list with head insertion, so the @@ -634,6 +644,9 @@ void ScopeCreator::addChildrenForKnownAttributes(Decl *decl, } else if (auto *customAttr = dyn_cast(attr)) { constructExpandAndInsert( parent, customAttr, decl); + } else if (auto *abiAttr = dyn_cast(attr)) { + constructExpandAndInsert( + parent, abiAttr, decl); } } } @@ -742,6 +755,7 @@ CREATES_NEW_INSERTION_POINT(GenericTypeOrExtensionScope) CREATES_NEW_INSERTION_POINT(BraceStmtScope) CREATES_NEW_INSERTION_POINT(TopLevelCodeScope) CREATES_NEW_INSERTION_POINT(ConditionalClausePatternUseScope) +CREATES_NEW_INSERTION_POINT(ABIAttributeScope) NO_NEW_INSERTION_POINT(FunctionBodyScope) NO_NEW_INSERTION_POINT(AbstractFunctionDeclScope) @@ -972,6 +986,26 @@ TopLevelCodeScope::expandAScopeThatCreatesANewInsertionPoint(ScopeCreator & "code scope"}; } +AnnotatedInsertionPoint +ABIAttributeScope::expandAScopeThatCreatesANewInsertionPoint( + ScopeCreator &scopeCreator) { + SourceLoc endLoc; + // Get the decl we're attached to. + if (auto parent = getParent().getPtrOrNull()) + // Get the enclosing scope for that decl. + if (auto grandparent = parent->getParent().getPtrOrNull()) + // If we're lexically scoped, that's what defines the end of the child's + // scope. + endLoc = grandparent->getSourceRangeOfThisASTNode().End; + + auto child = scopeCreator.addToScopeTreeAndReturnInsertionPoint(attr->abiDecl, + this, endLoc); + return {child, "@abi attribute can contain scopes which create new insertion " + "points; any such scopes should have the same scope they " + "would have if they were siblings of the decl the attribute " + "is attached to"}; +} + #pragma mark expandAScopeThatDoesNotCreateANewInsertionPoint // Create child scopes for every declaration in a body. diff --git a/lib/AST/ASTScopeLookup.cpp b/lib/AST/ASTScopeLookup.cpp index 10b182a04e25e..ed80e1440e30e 100644 --- a/lib/AST/ASTScopeLookup.cpp +++ b/lib/AST/ASTScopeLookup.cpp @@ -489,8 +489,13 @@ bool ASTScopeImpl::lookupLocalBindingsInPattern(const Pattern *p, return false; bool isDone = false; p->forEachVariable([&](VarDecl *var) { - if (!isDone) - isDone = consumer.consume({var}); + if (!isDone) { + SmallVector vars = { var }; + auto abiRole = ABIRoleInfo(var); + if (!abiRole.providesABI()) + vars.push_back(abiRole.getCounterpart()); + isDone = consumer.consume(vars); + } }); return isDone; } @@ -681,6 +686,23 @@ void ASTScopeImpl::lookupEnclosingMacroScope( } while ((scope = scope->getParent().getPtrOrNull())); } +ABIAttr *ASTScopeImpl:: +lookupEnclosingABIAttributeScope(SourceFile *sourceFile, SourceLoc loc) { + if (!sourceFile || loc.isInvalid()) + return nullptr; + + auto *fileScope = sourceFile->getScope().impl; + auto *scope = fileScope->findInnermostEnclosingScope( + sourceFile->getParentModule(), loc, nullptr); + do { + if (auto abiAttrScope = dyn_cast(scope)) { + return abiAttrScope->attr; + } + } while ((scope = scope->getParent().getPtrOrNull())); + + return nullptr; +} + static std::pair getCatchNodeBody(const ASTScopeImpl *scope, CatchNode node) { const auto &children = scope->getChildren(); diff --git a/lib/AST/ASTScopeSourceRange.cpp b/lib/AST/ASTScopeSourceRange.cpp index 117a6b7bec576..6fad78c64c43e 100644 --- a/lib/AST/ASTScopeSourceRange.cpp +++ b/lib/AST/ASTScopeSourceRange.cpp @@ -353,6 +353,11 @@ SourceRange CustomAttributeScope::getSourceRangeOfThisASTNode( return attr->getRange(); } +SourceRange ABIAttributeScope::getSourceRangeOfThisASTNode( + const bool omitAssertions) const { + return attr->getRange(); +} + SourceRange GuardStmtScope::getSourceRangeOfThisASTNode( const bool omitAssertions) const { return SourceRange(getStmt()->getStartLoc(), endLoc); diff --git a/lib/AST/ASTVerifier.cpp b/lib/AST/ASTVerifier.cpp index ced7f38bae67a..0e9530f37fd2b 100644 --- a/lib/AST/ASTVerifier.cpp +++ b/lib/AST/ASTVerifier.cpp @@ -1219,9 +1219,34 @@ class Verifier : public ASTWalker { Out << "\n"; abort(); } + if (E->getDecl() && !ABIRoleInfo(E->getDecl()).providesAPI()) { + PrettyStackTraceExpr debugStack(Ctx, "verifying decl reference", E); + Out << "reference to ABI-only decl in user code\n"; + E->dump(Out); + Out << "\n"; + E->getDecl()->dump(Out); + Out << "\n"; + abort(); + } verifyCheckedBase(E); } + void verifyParsed(OverloadedDeclRefExpr *E) { + for (auto D : E->getDecls()) { + if (!ABIRoleInfo(D).providesAPI()) { + PrettyStackTraceExpr debugStack(Ctx, + "verifying overloaded decl reference", E); + Out << "reference to ABI-only decl in user code\n"; + E->dump(Out); + Out << "\n"; + D->dump(Out); + Out << "\n"; + abort(); + } + } + verifyParsedBase(E); + } + void verifyChecked(AssignExpr *S) { Type lhsTy = checkAssignDest(S->getDest()); checkSameType(lhsTy, S->getSrc()->getType(), "assignment operands"); diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 90ac999688eec..6088c17d96343 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -35,6 +35,7 @@ #include "swift/AST/ImportCache.h" #include "swift/AST/Initializer.h" #include "swift/AST/LazyResolver.h" +#include "swift/AST/LookupKinds.h" #include "swift/AST/MacroDefinition.h" #include "swift/AST/MacroDiscriminatorContext.h" #include "swift/AST/Module.h" @@ -4270,6 +4271,36 @@ void ValueDecl::setRenamedDecl(const AvailableAttr *attr, std::move(renameDecl)); } +abi_role_detail::Storage abi_role_detail::computeStorage(Decl *decl) { + ASSERT(decl); + + auto &ctx = decl->getASTContext(); + + Decl *counterpartDecl = decl; + ABIRole::Value flags = ABIRole::Either; + + if (auto attr = decl->getAttrs().getAttribute()) { + flags = ABIRole::ProvidesAPI; + counterpartDecl = attr->abiDecl; + } else if (auto inverse = ctx.ABIDeclCounterparts.lookup(decl)) { + flags = ABIRole::ProvidesABI; + counterpartDecl = inverse; + } + + // If we did find an `@abi` attribute, resolve PBD pointers to their VarDecl. + if (flags != ABIRole::Either) { + if (auto VD = dyn_cast(decl)) + if (auto PBD = dyn_cast_or_null(counterpartDecl)) + counterpartDecl = PBD->getVarAtSimilarStructuralPosition(VD); + } + + return Storage(counterpartDecl, flags); +} + +ABIRole::ABIRole(NLOptions opts) + : value(opts & NL_ABIProviding ? ProvidesABI : ProvidesAPI) +{ } + VarDecl *PatternBindingDecl:: getVarAtSimilarStructuralPosition(VarDecl *otherVar) { auto otherPBD = otherVar->getParentPatternBinding(); diff --git a/lib/AST/Module.cpp b/lib/AST/Module.cpp index e0a49b57916d4..ce05eb6511026 100644 --- a/lib/AST/Module.cpp +++ b/lib/AST/Module.cpp @@ -150,6 +150,11 @@ class swift::SourceLookupCache { void add(ValueDecl *VD) { if (!VD->hasName()) return; VD->getName().addToLookupTable(Members, VD); + + auto abiRole = ABIRoleInfo(VD); + if (!abiRole.providesABI() && abiRole.getCounterpart()) + abiRole.getCounterpart()->getName() + .addToLookupTable(Members, abiRole.getCounterpart()); } void clear() { @@ -546,7 +551,8 @@ void SourceLookupCache::lookupValue(DeclName Name, NLKind LookupKind, if (I != TopLevelValues.end()) { Result.reserve(I->second.size()); for (ValueDecl *Elt : I->second) - Result.push_back(Elt); + if (ABIRoleInfo(Elt).matchesOptions(Flags)) + Result.push_back(Elt); } // If we aren't supposed to find names introduced by macros, we're done. @@ -639,7 +645,8 @@ void SourceLookupCache::lookupVisibleDecls(ImportPath::Access AccessPath, if (I == TopLevelValues.end()) return; for (auto vd : I->second) - Consumer.foundDecl(vd, DeclVisibilityKind::VisibleAtTopLevel); + if (ABIRoleInfo(vd).matchesOptions(OptionSet())) // FIXME: figure this out + Consumer.foundDecl(vd, DeclVisibilityKind::VisibleAtTopLevel); return; } @@ -649,7 +656,8 @@ void SourceLookupCache::lookupVisibleDecls(ImportPath::Access AccessPath, // entry for the simple name so that we report each declaration once. if (tlv.first.isSimpleName() && !vd->getName().isSimpleName()) continue; - Consumer.foundDecl(vd, DeclVisibilityKind::VisibleAtTopLevel); + if (ABIRoleInfo(vd).matchesOptions(OptionSet())) // FIXME: figure this out + Consumer.foundDecl(vd, DeclVisibilityKind::VisibleAtTopLevel); } } @@ -674,7 +682,8 @@ void SourceLookupCache::lookupVisibleDecls(ImportPath::Access AccessPath, }); } for (auto *vd : macroExpandedDecls) { - Consumer.foundDecl(vd, DeclVisibilityKind::VisibleAtTopLevel); + if (ABIRoleInfo(vd).matchesOptions(OptionSet())) // FIXME: figure this out + Consumer.foundDecl(vd, DeclVisibilityKind::VisibleAtTopLevel); } } @@ -692,8 +701,9 @@ void SourceLookupCache::lookupClassMembers(ImportPath::Access accessPath, for (ValueDecl *vd : member.second) { auto *nominal = vd->getDeclContext()->getSelfNominalTypeDecl(); if (nominal && nominal->getName() == accessPath.front().Item) - consumer.foundDecl(vd, DeclVisibilityKind::DynamicLookup, - DynamicLookupInfo::AnyObject); + if (ABIRoleInfo(vd).matchesOptions(OptionSet())) // FIXME: figure this out + consumer.foundDecl(vd, DeclVisibilityKind::DynamicLookup, + DynamicLookupInfo::AnyObject); } } return; @@ -706,8 +716,9 @@ void SourceLookupCache::lookupClassMembers(ImportPath::Access accessPath, continue; for (ValueDecl *vd : member.second) - consumer.foundDecl(vd, DeclVisibilityKind::DynamicLookup, - DynamicLookupInfo::AnyObject); + if (ABIRoleInfo(vd).matchesOptions(OptionSet())) // FIXME: figure this out + consumer.foundDecl(vd, DeclVisibilityKind::DynamicLookup, + DynamicLookupInfo::AnyObject); } } @@ -724,7 +735,8 @@ void SourceLookupCache::lookupClassMember(ImportPath::Access accessPath, for (ValueDecl *vd : iter->second) { auto *nominal = vd->getDeclContext()->getSelfNominalTypeDecl(); if (nominal && nominal->getName() == accessPath.front().Item) - results.push_back(vd); + if (ABIRoleInfo(vd).matchesOptions(OptionSet())) // FIXME: figure this out + results.push_back(vd); } return; } @@ -3848,7 +3860,7 @@ void SynthesizedFileUnit::lookupValue( SmallVectorImpl &result) const { for (auto *decl : TopLevelDecls) { if (auto VD = dyn_cast(decl)) { - if (VD->getName().matchesRef(name)) { + if (VD->getName().matchesRef(name) && ABIRoleInfo(VD).matchesOptions(Flags)) { result.push_back(VD); } } diff --git a/lib/AST/ModuleNameLookup.cpp b/lib/AST/ModuleNameLookup.cpp index 3379f1188e6ca..288625bb5ab20 100644 --- a/lib/AST/ModuleNameLookup.cpp +++ b/lib/AST/ModuleNameLookup.cpp @@ -27,7 +27,9 @@ namespace { /// Encapsulates the work done for a recursive qualified lookup into a module. /// /// The \p LookupStrategy handles the non-recursive part of the lookup. It -/// must be a subclass of ModuleNameLookup. +/// must be a subclass of ModuleNameLookup. It should provide a +/// \c doLocalLookup() method; this method will be passed appropriate +/// \c NLOptions but does not necessarily need to honor them. template class ModuleNameLookup { ASTContext &ctx; @@ -183,6 +185,8 @@ void ModuleNameLookup::lookupInModule( if (respectAccessControl && !declIsVisibleToNameLookup(VD, moduleScopeContext, options)) return true; + if (!ABIRoleInfo(VD).matchesOptions(options)) + return true; return false; }); decls.erase(newEnd, decls.end()); @@ -193,6 +197,8 @@ void ModuleNameLookup::lookupInModule( OptionSet currentModuleLookupFlags = {}; if (options & NL_ExcludeMacroExpansions) currentModuleLookupFlags |= ModuleLookupFlags::ExcludeMacroExpansions; + if (options & NL_ABIProviding) + currentModuleLookupFlags |= ModuleLookupFlags::ABIProviding; // Do the lookup into the current module. auto *module = moduleOrFile->getParentModule(); @@ -217,6 +223,10 @@ void ModuleNameLookup::lookupInModule( if (!canReturnEarly) { auto &imports = ctx.getImportCache().getImportSet(moduleOrFile); + OptionSet importedModuleLookupFlags = {}; + if (options & NL_ABIProviding) + currentModuleLookupFlags |= ModuleLookupFlags::ABIProviding; + auto visitImport = [&](ImportedModule import, const DeclContext *moduleScopeContext) { if (import.accessPath.empty()) @@ -226,7 +236,7 @@ void ModuleNameLookup::lookupInModule( return; getDerived()->doLocalLookup(import.importedModule, import.accessPath, - { }, decls); + importedModuleLookupFlags, decls); updateNewDecls(moduleScopeContext); }; diff --git a/lib/AST/NameLookup.cpp b/lib/AST/NameLookup.cpp index c8ed48f4fb508..051187d8e8e4e 100644 --- a/lib/AST/NameLookup.cpp +++ b/lib/AST/NameLookup.cpp @@ -120,6 +120,7 @@ void swift::simple_display(llvm::raw_ostream &out, {UnqualifiedLookupFlags::ModuleLookup, "ModuleLookup"}, {UnqualifiedLookupFlags::DisregardSelfBounds, "DisregardSelfBounds"}, {UnqualifiedLookupFlags::IgnoreMissingImports, "IgnoreMissingImports"}, + {UnqualifiedLookupFlags::ABIProviding, "ABIProviding"}, }; auto flagsToPrint = llvm::make_filter_range( @@ -298,6 +299,10 @@ void LookupResultEntry::print(llvm::raw_ostream& out) const { out << "\n"; } else out << "\n(no-base)\n"; + + auto abiRole = ABIRoleInfo(getValueDecl()); + out << "provides API=" << abiRole.providesAPI() + << ", provides ABI=" << abiRole.providesABI() << "\n"; } @@ -1512,6 +1517,10 @@ void MemberLookupTable::addMember(Decl *member) { // And if given a synonym, under that name too. if (A) A->getMemberName().addToLookupTable(Lookup, vd); + + auto abiRole = ABIRoleInfo(vd); + if (!abiRole.providesABI()) + addMember(abiRole.getCounterpart()); } void MemberLookupTable::addMembers(DeclRange members) { @@ -2064,29 +2073,34 @@ void NominalTypeDecl::prepareLookupTable() { LookupTable.setInt(true); } -static TinyPtrVector -maybeFilterOutUnwantedDecls(TinyPtrVector decls, - DeclName name, - bool includeAttrImplements, - bool excludeMacroExpansions) { - if (includeAttrImplements && !excludeMacroExpansions) - return decls; +static TinyPtrVector maybeFilterOutUnwantedDecls( + TinyPtrVector decls, + DeclName name, + OptionSet flags) { + using Flags = NominalTypeDecl::LookupDirectFlags; + TinyPtrVector result; for (auto V : decls) { // If we're supposed to exclude anything that comes from a macro expansion, // check whether the source location of the declaration is in a macro // expansion, and skip this declaration if it does. - if (excludeMacroExpansions) { + if (flags.contains(Flags::ExcludeMacroExpansions)) { auto sourceFile = V->getModuleContext()->getSourceFileContainingLocation(V->getLoc()); if (sourceFile && sourceFile->Kind == SourceFileKind::MacroExpansion) continue; } + // If this decl is on either side of an @abi attribute, make sure it's on + // the side we want. + if (!ABIRoleInfo(V).matchesOptions(flags)) + continue; + // Filter-out any decl that doesn't have the name we're looking for // (asserting as a consistency-check that such entries all have // @_implements attrs for the name!) - if (V->getName().matchesRef(name)) { + if (flags.contains(Flags::IncludeAttrImplements) + || V->getName().matchesRef(name)) { result.push_back(V); } else { auto A = V->getAttrs().getAttribute(); @@ -2112,8 +2126,6 @@ DirectLookupRequest::evaluate(Evaluator &evaluator, auto *decl = desc.DC; ASTContext &ctx = decl->getASTContext(); - const bool includeAttrImplements = - flags.contains(NominalTypeDecl::LookupDirectFlags::IncludeAttrImplements); const bool excludeMacroExpansions = flags.contains(NominalTypeDecl::LookupDirectFlags::ExcludeMacroExpansions); @@ -2149,9 +2161,8 @@ DirectLookupRequest::evaluate(Evaluator &evaluator, if (!allFound.empty()) { auto known = Table.find(name); if (known != Table.end()) { - auto swiftLookupResult = maybeFilterOutUnwantedDecls( - known->second, name, includeAttrImplements, - excludeMacroExpansions); + auto swiftLookupResult = + maybeFilterOutUnwantedDecls(known->second, name, flags); for (auto foundSwiftDecl : swiftLookupResult) { allFound.push_back(foundSwiftDecl); } @@ -2197,9 +2208,7 @@ DirectLookupRequest::evaluate(Evaluator &evaluator, } // We found something; return it. - return maybeFilterOutUnwantedDecls(known->second, name, - includeAttrImplements, - excludeMacroExpansions); + return maybeFilterOutUnwantedDecls(known->second, name, flags); } bool NominalTypeDecl::createObjCMethodLookup() { @@ -2379,9 +2388,26 @@ static bool isAcceptableLookupResult(const DeclContext *dc, return false; } + // Check that it has the appropriate ABI role. + if (!ABIRoleInfo(decl).matchesOptions(options)) + return false; + return true; } +bool namelookup::isInABIAttr(SourceFile *sourceFile, SourceLoc loc) { + // Make sure that the source location is actually within the given source + // file. + if (sourceFile && loc.isValid()) { + sourceFile = + sourceFile->getParentModule()->getSourceFileContainingLocation(loc); + if (!sourceFile) + return false; + } + + return ASTScope::lookupEnclosingABIAttributeScope(sourceFile, loc) != nullptr; +} + void namelookup::pruneLookupResultSet(const DeclContext *dc, NLOptions options, SmallVectorImpl &decls) { // If we're supposed to remove overridden declarations, do so now. @@ -2611,6 +2637,8 @@ QualifiedLookupRequest::evaluate(Evaluator &eval, const DeclContext *DC, flags |= NominalTypeDecl::LookupDirectFlags::IncludeAttrImplements; if (options & NL_ExcludeMacroExpansions) flags |= NominalTypeDecl::LookupDirectFlags::ExcludeMacroExpansions; + if (options & NL_ABIProviding) + flags |= NominalTypeDecl::LookupDirectFlags::ABIProviding; // Note that the source loc argument doesn't matter, because excluding // macro expansions is already propagated through the lookup flags above. @@ -2753,7 +2781,8 @@ AnyObjectLookupRequest::evaluate(Evaluator &evaluator, const DeclContext *dc, QualifiedLookupResult decls; // Type-only and macro lookup won't find anything on AnyObject. - if (options & (NL_OnlyTypes | NL_OnlyMacros)) + // AnyObject doesn't provide ABI. + if (options & (NL_OnlyTypes | NL_OnlyMacros | NL_ABIProviding)) return decls; // Collect all of the visible declarations. @@ -2947,6 +2976,11 @@ static DirectlyReferencedTypeDecls directReferencesForUnqualifiedTypeLookup( if (namelookup::isInMacroArgument(dc->getParentSourceFile(), loc)) options |= UnqualifiedLookupFlags::ExcludeMacroExpansions; + // If the source location is located anywhere within an `@abi` attribute, look + // up using ABI names. + if (namelookup::isInABIAttr(dc->getParentSourceFile(), loc)) + options |= UnqualifiedLookupFlags::ABIProviding; + // In a protocol or protocol extension, the 'where' clause can refer to // associated types without 'Self' qualification: // @@ -4133,6 +4167,7 @@ void swift::simple_display(llvm::raw_ostream &out, NLOptions options) { FLAG(NL_ExcludeMacroExpansions) FLAG(NL_OnlyMacros) FLAG(NL_IgnoreMissingImports) + FLAG(NL_ABIProviding) #undef FLAG }; diff --git a/lib/AST/NameLookupRequests.cpp b/lib/AST/NameLookupRequests.cpp index cd3dd1af60019..ca9d9ec3f9e5f 100644 --- a/lib/AST/NameLookupRequests.cpp +++ b/lib/AST/NameLookupRequests.cpp @@ -583,60 +583,57 @@ swift::extractNearestSourceLoc(CustomRefCountingOperationDescriptor desc) { // so it doesn't break caching for those immediate requests. /// Exclude macros in the unqualified lookup descriptor if we need to. -static UnqualifiedLookupDescriptor excludeMacrosIfNeeded( +static UnqualifiedLookupDescriptor contextualizeOptions( UnqualifiedLookupDescriptor descriptor) { - if (descriptor.Options.contains( - UnqualifiedLookupFlags::ExcludeMacroExpansions)) - return descriptor; + if (!descriptor.Options.contains( + UnqualifiedLookupFlags::ExcludeMacroExpansions) + && namelookup::isInMacroArgument( + descriptor.DC->getParentSourceFile(), descriptor.Loc)) + descriptor.Options |= UnqualifiedLookupFlags::ExcludeMacroExpansions; + if (!descriptor.Options.contains(UnqualifiedLookupFlags::ABIProviding) + && namelookup::isInABIAttr( + descriptor.DC->getParentSourceFile(), descriptor.Loc)) + descriptor.Options |= UnqualifiedLookupFlags::ABIProviding; - auto isInMacroArgument = namelookup::isInMacroArgument( - descriptor.DC->getParentSourceFile(), descriptor.Loc); - - if (!isInMacroArgument) - return descriptor; - - descriptor.Options |= UnqualifiedLookupFlags::ExcludeMacroExpansions; return descriptor; } /// Exclude macros in the direct lookup descriptor if we need to. -static DirectLookupDescriptor excludeMacrosIfNeeded( +static DirectLookupDescriptor contextualizeOptions( DirectLookupDescriptor descriptor, SourceLoc loc) { - if (descriptor.Options.contains( - NominalTypeDecl::LookupDirectFlags::ExcludeMacroExpansions)) - return descriptor; - - auto isInMacroArgument = namelookup::isInMacroArgument( - descriptor.DC->getParentSourceFile(), loc); - - if (!isInMacroArgument) - return descriptor; - - descriptor.Options |= - NominalTypeDecl::LookupDirectFlags::ExcludeMacroExpansions; + if (!descriptor.Options.contains( + NominalTypeDecl::LookupDirectFlags::ExcludeMacroExpansions) + && namelookup::isInMacroArgument( + descriptor.DC->getParentSourceFile(), loc)) + descriptor.Options |= + NominalTypeDecl::LookupDirectFlags::ExcludeMacroExpansions; + if (!descriptor.Options.contains( + NominalTypeDecl::LookupDirectFlags::ABIProviding) + && namelookup::isInABIAttr( + descriptor.DC->getParentSourceFile(), loc)) + descriptor.Options |= + NominalTypeDecl::LookupDirectFlags::ABIProviding; return descriptor; } /// Exclude macros in the name lookup options if we need to. static NLOptions -excludeMacrosIfNeeded(const DeclContext *dc, SourceLoc loc, - NLOptions options) { - if (options & NL_ExcludeMacroExpansions) - return options; - - auto isInMacroArgument = namelookup::isInMacroArgument( - dc->getParentSourceFile(), loc); - - if (!isInMacroArgument) - return options; +contextualizeOptions(const DeclContext *dc, SourceLoc loc, + NLOptions options) { + if (!(options & NL_ExcludeMacroExpansions) + && namelookup::isInMacroArgument(dc->getParentSourceFile(), loc)) + options |= NL_ExcludeMacroExpansions; + if (!(options & NL_ABIProviding) + && namelookup::isInABIAttr(dc->getParentSourceFile(), loc)) + options |= NL_ABIProviding; - return options | NL_ExcludeMacroExpansions; + return options; } UnqualifiedLookupRequest::UnqualifiedLookupRequest( UnqualifiedLookupDescriptor descriptor -) : SimpleRequest(excludeMacrosIfNeeded(descriptor)) { } +) : SimpleRequest(contextualizeOptions(descriptor)) { } LookupInModuleRequest::LookupInModuleRequest( const DeclContext *moduleOrFile, DeclName name, NLKind lookupKind, @@ -645,13 +642,13 @@ LookupInModuleRequest::LookupInModuleRequest( SourceLoc loc, NLOptions options ) : SimpleRequest(moduleOrFile, name, lookupKind, resolutionKind, moduleScopeContext, - excludeMacrosIfNeeded(moduleOrFile, loc, options)) { } + contextualizeOptions(moduleOrFile, loc, options)) { } ModuleQualifiedLookupRequest::ModuleQualifiedLookupRequest( const DeclContext *dc, ModuleDecl *module, DeclNameRef name, SourceLoc loc, NLOptions options ) : SimpleRequest(dc, module, name, - excludeMacrosIfNeeded(dc, loc, options)) { } + contextualizeOptions(dc, loc, options)) { } QualifiedLookupRequest::QualifiedLookupRequest( const DeclContext *dc, @@ -659,10 +656,10 @@ QualifiedLookupRequest::QualifiedLookupRequest( DeclNameRef name, SourceLoc loc, NLOptions options ) : SimpleRequest(dc, std::move(decls), name, - excludeMacrosIfNeeded(dc, loc, options)) { } + contextualizeOptions(dc, loc, options)) { } DirectLookupRequest::DirectLookupRequest(DirectLookupDescriptor descriptor, SourceLoc loc) - : SimpleRequest(excludeMacrosIfNeeded(descriptor, loc)) { } + : SimpleRequest(contextualizeOptions(descriptor, loc)) { } // Implement the clang importer type zone. #define SWIFT_TYPEID_ZONE ClangImporter diff --git a/lib/AST/UnqualifiedLookup.cpp b/lib/AST/UnqualifiedLookup.cpp index 85b5bbf59684e..6daacec7252fc 100644 --- a/lib/AST/UnqualifiedLookup.cpp +++ b/lib/AST/UnqualifiedLookup.cpp @@ -481,6 +481,8 @@ void UnqualifiedLookupFactory::addImportedResults(const DeclContext *const dc) { nlOptions |= NL_IncludeUsableFromInline; if (options.contains(Flags::ExcludeMacroExpansions)) nlOptions |= NL_ExcludeMacroExpansions; + if (options.contains(Flags::ABIProviding)) + nlOptions |= NL_ABIProviding; lookupInModule(dc, Name.getFullName(), CurModuleResults, NLKind::UnqualifiedLookup, resolutionKind, dc, Loc, nlOptions); @@ -595,6 +597,8 @@ NLOptions UnqualifiedLookupFactory::computeBaseNLOptions( baseNLOptions |= NL_IgnoreAccessControl; if (options.contains(Flags::IgnoreMissingImports)) baseNLOptions |= NL_IgnoreMissingImports; + if (options.contains(Flags::ABIProviding)) + baseNLOptions |= NL_ABIProviding; return baseNLOptions; } @@ -701,9 +705,12 @@ bool ASTScopeDeclConsumerForUnqualifiedLookup::consume( continue; } + if (!ABIRoleInfo(value).matchesOptions(factory.options)) + continue; + factory.Results.push_back(LookupResultEntry(value)); #ifndef NDEBUG - factory.stopForDebuggingIfAddingTargetLookupResult(factory.Results.back()); + factory.addedResult(factory.Results.back()); #endif } factory.recordCompletionOfAScope(); @@ -716,10 +723,14 @@ bool ASTScopeDeclConsumerForUnqualifiedLookup::consumePossiblyNotInScope( return false; for (auto *var : vars) { - if (!factory.Name.getFullName().isSimpleName(var->getName())) + if (!factory.Name.getFullName().isSimpleName(var->getName()) + || !ABIRoleInfo(var).matchesOptions(factory.options)) continue; factory.Results.push_back(LookupResultEntry(var)); +#ifndef NDEBUG + factory.addedResult(factory.Results.back()); +#endif } return false; @@ -872,14 +883,19 @@ class ASTScopeDeclConsumerForLocalLookup : public AbstractASTScopeDeclConsumer { DeclName name; bool stopAfterInnermostBraceStmt; + ABIRole roleFilter; SmallVectorImpl &results; public: ASTScopeDeclConsumerForLocalLookup( DeclName name, bool stopAfterInnermostBraceStmt, - SmallVectorImpl &results) + ABIRole roleFilter, SmallVectorImpl &results) : name(name), stopAfterInnermostBraceStmt(stopAfterInnermostBraceStmt), - results(results) {} + roleFilter(roleFilter), results(results) {} + + bool hasCorrectABIRole(ValueDecl *vd) const { + return ABIRoleInfo(vd).matches(roleFilter); + } bool consume(ArrayRef values, NullablePtr baseDC) override { @@ -888,14 +904,16 @@ class ASTScopeDeclConsumerForLocalLookup if (auto *varDecl = dyn_cast(value)) { // Check if the name matches any auxiliary decls not in the AST varDecl->visitAuxiliaryDecls([&](VarDecl *auxiliaryVar) { - if (name.isSimpleName(auxiliaryVar->getName())) { + if (name.isSimpleName(auxiliaryVar->getName()) + && hasCorrectABIRole(auxiliaryVar)) { results.push_back(auxiliaryVar); foundMatch = true; } }); } - if (!foundMatch && value->getName().matchesRef(name)) + if (!foundMatch && value->getName().matchesRef(name) + && hasCorrectABIRole(value)) results.push_back(value); } @@ -923,9 +941,10 @@ class ASTScopeDeclConsumerForLocalLookup /// interface type computation. void ASTScope::lookupLocalDecls(SourceFile *sf, DeclName name, SourceLoc loc, bool stopAfterInnermostBraceStmt, + ABIRole roleFilter, SmallVectorImpl &results) { ASTScopeDeclConsumerForLocalLookup consumer(name, stopAfterInnermostBraceStmt, - results); + roleFilter, results); ASTScope::unqualifiedLookup(sf, loc, consumer); } diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 3d422fd18fcf1..f726f77a6510c 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -191,6 +191,21 @@ class AttributeChecker : public AttributeVisitor { #undef IGNORED_ATTR void visitABIAttr(ABIAttr *attr) { + Decl *AD = attr->abiDecl; + if (isa(D) && isa(AD)) { + AD = cast(AD) + ->getVarAtSimilarStructuralPosition(cast(D)); + } + // TODO: EnumElementDecl? + + if (!AD) + return; + + // Check the ABI decl and bail if there was a problem with it. + TypeChecker::typeCheckDecl(AD); + if (AD->isInvalid()) + return; + // TODO: Validate more } diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index 920d7df17fcbd..34d504f543d4d 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -605,6 +605,20 @@ static void checkGenericParams(GenericContext *ownerCtx) { TypeChecker::checkShadowedGenericParams(ownerCtx); } +/// Returns \c true if \p current and \p other are in the same source file +/// \em and \c current appears before \p other in that file. +static bool isBeforeInSameFile(Decl *current, Decl *other) { + if (current->getDeclContext()->getParentSourceFile() != + other->getDeclContext()->getParentSourceFile()) + return false; + + if (!current->getLoc().isValid()) + return false; + + return current->getASTContext().SourceMgr + .isBeforeInBuffer(current->getLoc(), other->getLoc()); +} + template static void checkOperatorOrPrecedenceGroupRedeclaration( T *decl, Diag<> diagID, Diag<> noteID, @@ -624,16 +638,8 @@ static void checkOperatorOrPrecedenceGroupRedeclaration( if (other == decl || other->isInvalid()) continue; - bool shouldDiagnose = false; - if (currentFile == other->getDeclContext()->getParentSourceFile()) { - // For a same-file redeclaration, make sure we get the diagnostic ordering - // to be sensible. - if (decl->getLoc().isValid() && other->getLoc().isValid() && - ctx.SourceMgr.isBeforeInBuffer(decl->getLoc(), other->getLoc())) { - std::swap(decl, other); - } - shouldDiagnose = true; - } else { + bool shouldDiagnose = true; + if (currentFile != other->getDeclContext()->getParentSourceFile()) { // If the declarations are in different files, only diagnose if we've // enabled the new operator lookup behaviour where decls in the current // module are now favored over imports. @@ -641,6 +647,12 @@ static void checkOperatorOrPrecedenceGroupRedeclaration( } if (shouldDiagnose) { + // For a same-file redeclaration, make sure we get the diagnostic ordering + // to be sensible. + if (isBeforeInSameFile(decl, other)) { + std::swap(decl, other); + } + ctx.Diags.diagnose(decl, diagID); ctx.Diags.diagnose(other, noteID); decl->setInvalid(); @@ -689,28 +701,49 @@ CheckRedeclarationRequest::evaluate(Evaluator &eval, ValueDecl *current, } auto &ctx = current->getASTContext(); + bool currentIsABIOnly = !ABIRoleInfo(current).providesAPI(); + + // If true, two conflicting decls may not be in scope at the same time, so + // we might only detect a redeclaration from one and not the other. + bool partialScopes = currentDC->isLocalContext() && isa(current); // Find other potential definitions. SmallVector otherDefinitions; if (currentDC->isTypeContext()) { // Look within a type context. if (auto nominal = currentDC->getSelfNominalTypeDecl()) { - auto found = nominal->lookupDirect(current->getBaseName()); + OptionSet flags; + if (currentIsABIOnly) + flags |= NominalTypeDecl::LookupDirectFlags::ABIProviding; + auto found = nominal->lookupDirect(current->getBaseName(), SourceLoc(), + flags); otherDefinitions.append(found.begin(), found.end()); } } else if (currentDC->isLocalContext()) { if (!current->isImplicit()) { + ABIRole roleFilter; + if (partialScopes) + // We don't know if conflicts will be detected when the other decl is + // `current`, so if this decl has `ABIRole::Both`, we need this lookup + // to include both ProvidesABI *and* ProvidesAPI decls. + roleFilter = ABIRoleInfo(current).getRole(); + else + roleFilter = currentIsABIOnly ? ABIRole::ProvidesABI + : ABIRole::ProvidesAPI; ASTScope::lookupLocalDecls(currentFile, current->getBaseName(), current->getLoc(), /*stopAfterInnermostBraceStmt=*/true, - otherDefinitions); + roleFilter, otherDefinitions); } } else { assert(currentDC->isModuleScopeContext()); // Look within a module context. + OptionSet flags; + if (currentIsABIOnly) + flags |= ModuleLookupFlags::ABIProviding; currentFile->getParentModule()->lookupValue(current->getBaseName(), NLKind::QualifiedLookup, - otherDefinitions); + flags, otherDefinitions); } // Compare this signature against the signature of other @@ -731,12 +764,17 @@ CheckRedeclarationRequest::evaluate(Evaluator &eval, ValueDecl *current, if (currentModule != otherDC->getParentModule()) continue; - // If both declarations are in the same file, only diagnose the second one. - if (currentFile == otherDC->getParentSourceFile()) - if (current->getLoc().isValid() && - ctx.SourceMgr.isBeforeInBuffer( - current->getLoc(), other->getLoc())) + bool otherIsABIOnly = !ABIRoleInfo(other).providesAPI(); + + if (currentIsABIOnly == otherIsABIOnly || partialScopes) { + // If both declarations are in the same file, only diagnose the second one + if (isBeforeInSameFile(current, other)) continue; + } + else if (!currentIsABIOnly && otherIsABIOnly) + // Don't diagnose a non-ABI-only decl as conflicting with an ABI-only + // decl. (We'll diagnose it in the other direction instead.) + continue; // Don't compare methods vs. non-methods (which only happens with // operators). @@ -2749,7 +2787,8 @@ class DeclChecker : public DeclVisitor { if (!var->hasStorage()) return; - if (var->getAttrs().hasAttribute()) + if (var->getAttrs().hasAttribute() + || !ABIRoleInfo(var).providesAPI()) return; if (var->isInvalid() || PBD->isInvalid()) diff --git a/lib/Sema/TypeCheckExpr.cpp b/lib/Sema/TypeCheckExpr.cpp index acfba6a64bfe3..bc85910503b71 100644 --- a/lib/Sema/TypeCheckExpr.cpp +++ b/lib/Sema/TypeCheckExpr.cpp @@ -515,6 +515,9 @@ Expr *TypeChecker::buildRefExpr(ArrayRef Decls, DeclContext *UseDC, DeclNameLoc NameLoc, bool Implicit, FunctionRefInfo functionRefInfo) { assert(!Decls.empty() && "Must have at least one declaration"); + ASSERT(llvm::any_of(Decls, [](ValueDecl *VD) { + return ABIRoleInfo(VD).providesAPI(); + }) && "DeclRefExpr can't refer to ABI-only decl"); auto &Context = UseDC->getASTContext(); diff --git a/lib/Sema/TypeCheckNameLookup.cpp b/lib/Sema/TypeCheckNameLookup.cpp index 366cf2b01c111..85d24c446c8b2 100644 --- a/lib/Sema/TypeCheckNameLookup.cpp +++ b/lib/Sema/TypeCheckNameLookup.cpp @@ -40,6 +40,7 @@ void swift::simple_display(llvm::raw_ostream &out, NameLookupOptions options) { {NameLookupFlags::IncludeUsableFromInline, "IncludeUsableFromInline"}, {NameLookupFlags::ExcludeMacroExpansions, "ExcludeMacroExpansions"}, {NameLookupFlags::IgnoreMissingImports, "IgnoreMissingImports"}, + {NameLookupFlags::ABIProviding, "ABIProviding"}, }; auto flagsToPrint = llvm::make_filter_range( @@ -245,6 +246,8 @@ convertToUnqualifiedLookupOptions(NameLookupOptions options) { newOptions |= UnqualifiedLookupFlags::ExcludeMacroExpansions; if (options.contains(NameLookupFlags::IgnoreMissingImports)) newOptions |= UnqualifiedLookupFlags::IgnoreMissingImports; + if (options.contains(NameLookupFlags::ABIProviding)) + newOptions |= UnqualifiedLookupFlags::ABIProviding; return newOptions; } @@ -350,6 +353,8 @@ LookupResult TypeChecker::lookupMember(DeclContext *dc, subOptions |= NL_IgnoreAccessControl; if (options.contains(NameLookupFlags::IgnoreMissingImports)) subOptions |= NL_IgnoreMissingImports; + if (options.contains(NameLookupFlags::ABIProviding)) + subOptions |= NL_ABIProviding; // We handle our own overriding/shadowing filtering. subOptions &= ~NL_RemoveOverridden; @@ -450,6 +455,8 @@ LookupTypeResult TypeChecker::lookupMemberType(DeclContext *dc, subOptions |= NL_IgnoreMissingImports; if (options.contains(NameLookupFlags::IncludeUsableFromInline)) subOptions |= NL_IncludeUsableFromInline; + if (options.contains(NameLookupFlags::ABIProviding)) + subOptions |= NL_ABIProviding; // Make sure we've resolved implicit members, if we need them. namelookup::installSemanticMembersIfNeeded(type, name); diff --git a/lib/Sema/TypeCheckStmt.cpp b/lib/Sema/TypeCheckStmt.cpp index abf7da391a31e..e1539a622a3f0 100644 --- a/lib/Sema/TypeCheckStmt.cpp +++ b/lib/Sema/TypeCheckStmt.cpp @@ -2785,8 +2785,7 @@ static bool requiresNoDefinition(Decl *decl) { return true; } // FIXME: Should be able to write this more nicely - auto abiAttr = decl->getAttrs().getAttribute(); - if (abiAttr && abiAttr->isInverse()) + if (!ABIRoleInfo(decl).providesAPI()) // Decls which merely define the ABI of another decl should not have a // body. return true; diff --git a/lib/Sema/TypeChecker.h b/lib/Sema/TypeChecker.h index 0e08c07f22812..a7bd754d87be7 100644 --- a/lib/Sema/TypeChecker.h +++ b/lib/Sema/TypeChecker.h @@ -161,6 +161,9 @@ enum class NameLookupFlags { /// Whether to include members that would otherwise be filtered out because /// they come from a module that has not been imported. IgnoreMissingImports = 1 << 4, + /// If @abi attributes are present, return the decls representing the ABI, + /// not the API. + ABIProviding = 1 << 5, // Reminder: If you add a flag, make sure you update simple_display() below }; diff --git a/lib/Serialization/ModuleFile.cpp b/lib/Serialization/ModuleFile.cpp index 7090f42db9990..eacba1282582b 100644 --- a/lib/Serialization/ModuleFile.cpp +++ b/lib/Serialization/ModuleFile.cpp @@ -330,6 +330,7 @@ bool ModuleFile::mayHaveDiagnosticsPointingAtBuffer() const { ModuleFile::~ModuleFile() { } void ModuleFile::lookupValue(DeclName name, + OptionSet flags, SmallVectorImpl &results) { PrettyStackTraceModuleFile stackEntry(*this); @@ -350,7 +351,8 @@ void ModuleFile::lookupValue(DeclName name, } auto VD = cast(declOrError.get()); if (name.isSimpleName() || VD->getName().matchesRef(name)) - results.push_back(VD); + if (ABIRoleInfo(VD).matchesOptions(flags)) + results.push_back(VD); } } } @@ -368,7 +370,8 @@ void ModuleFile::lookupValue(DeclName name, continue; } auto VD = cast(declOrError.get()); - results.push_back(VD); + if (ABIRoleInfo(VD).matchesOptions(flags)) + results.push_back(VD); } } } @@ -453,7 +456,8 @@ TypeDecl *ModuleFile::lookupNestedType(Identifier name, diagnoseAndConsumeError(typeOrErr.takeError()); continue; } - return cast(typeOrErr.get()); + if (ABIRoleInfo(typeOrErr.get()).providesAPI()) // FIXME: flags? + return cast(typeOrErr.get()); } } } @@ -622,6 +626,8 @@ void ModuleFile::lookupVisibleDecls(ImportPath::Access accessPath, diagnoseAndConsumeError(declOrError.takeError()); return; } + if (!ABIRoleInfo(declOrError.get()).providesAPI()) // FIXME: flags? + return; consumer.foundDecl(cast(declOrError.get()), DeclVisibilityKind::VisibleAtTopLevel); }; @@ -1005,6 +1011,8 @@ void ModuleFile::getTopLevelDecls( consumeError(declOrError.takeError()); continue; } + if (!ABIRoleInfo(declOrError.get()).providesAPI()) // FIXME: flags + continue; results.push_back(declOrError.get()); } } @@ -1053,6 +1061,8 @@ ModuleFile::getLocalTypeDecls(SmallVectorImpl &results) { for (auto DeclID : Core->LocalTypeDecls->data()) { auto TD = cast(getDecl(DeclID)); + if (!ABIRoleInfo(TD).providesAPI()) // FIXME: flags + continue; results.push_back(TD); } } diff --git a/lib/Serialization/ModuleFile.h b/lib/Serialization/ModuleFile.h index 8cc54e47e6cd5..031a7270ded1e 100644 --- a/lib/Serialization/ModuleFile.h +++ b/lib/Serialization/ModuleFile.h @@ -763,7 +763,8 @@ class ModuleFile ModuleDecl *getUnderlyingModule() const { return UnderlyingModule; } /// Searches the module's top-level decls for the given identifier. - void lookupValue(DeclName name, SmallVectorImpl &results); + void lookupValue(DeclName name, OptionSet flags, + SmallVectorImpl &results); /// Searches the module's local type decls for the given mangled name. TypeDecl *lookupLocalType(StringRef MangledName); diff --git a/lib/Serialization/SerializedModuleLoader.cpp b/lib/Serialization/SerializedModuleLoader.cpp index 0ad9b6cba8f08..d807628d30231 100644 --- a/lib/Serialization/SerializedModuleLoader.cpp +++ b/lib/Serialization/SerializedModuleLoader.cpp @@ -1851,7 +1851,7 @@ bool SerializedASTFile::isSystemModule() const { void SerializedASTFile::lookupValue(DeclName name, NLKind lookupKind, OptionSet Flags, SmallVectorImpl &results) const{ - File.lookupValue(name, results); + File.lookupValue(name, Flags, results); } StringRef diff --git a/test/attr/attr_abi.swift b/test/attr/attr_abi.swift index ecfc5f06d7ec9..7e53b2a502a0b 100644 --- a/test/attr/attr_abi.swift +++ b/test/attr/attr_abi.swift @@ -1,5 +1,8 @@ // RUN: %target-typecheck-verify-swift -enable-experimental-feature Extern -enable-experimental-feature ABIAttribute -parse-as-library +// REQUIRES: swift_feature_ABIAttribute +// REQUIRES: swift_feature_Extern + // // Same-kind checking // @@ -150,16 +153,16 @@ func noConflictWithMutualChanges2() {} @abi(func noConflictWithMutualChanges2()) func noConflictWithMutualChanges1() {} -@abi(func conflictsWithOtherABI()) +@abi(func conflictsWithOtherABI()) // expected-note {{'conflictsWithOtherABI()' previously declared here}} func conflictsWithOtherABI1() {} -@abi(func conflictsWithOtherABI()) +@abi(func conflictsWithOtherABI()) // expected-error {{invalid redeclaration of 'conflictsWithOtherABI()'}} func conflictsWithOtherABI2() {} -@abi(func conflictsWithNonABI()) +@abi(func conflictsWithNonABI()) // expected-error {{invalid redeclaration of 'conflictsWithNonABI()'}} func conflictsWithNonABI_formal() {} -func conflictsWithNonABI() {} +func conflictsWithNonABI() {} // expected-note {{'conflictsWithNonABI()' previously declared here}} @abi(var var_noConflictWithSelf: Int) var var_noConflictWithSelf: Int = 0 @@ -170,16 +173,16 @@ var var_noConflictWithMutualChanges2: Int = 0 @abi(var var_noConflictWithMutualChanges2: Int) var var_noConflictWithMutualChanges1: Int = 0 -@abi(var var_conflictsWithOtherABI: Int) +@abi(var var_conflictsWithOtherABI: Int) // expected-note {{'var_conflictsWithOtherABI' previously declared here}} var var_conflictsWithOtherABI1: Int = 0 -@abi(var var_conflictsWithOtherABI: Int) +@abi(var var_conflictsWithOtherABI: Int) // expected-error {{invalid redeclaration of 'var_conflictsWithOtherABI'}} var var_conflictsWithOtherABI2: Int = 0 -@abi(var var_conflictsWithNonABI: Int) +@abi(var var_conflictsWithNonABI: Int) // expected-error {{invalid redeclaration of 'var_conflictsWithNonABI'}} var var_conflictsWithNonABI_formal: Int = 0 -var var_conflictsWithNonABI: Int = 0 +var var_conflictsWithNonABI: Int = 0 // expected-note {{'var_conflictsWithNonABI' previously declared here}} struct Foo { @abi(func noConflictWithSelf()) @@ -191,16 +194,16 @@ struct Foo { @abi(func noConflictWithMutualChanges2()) func noConflictWithMutualChanges1() {} - @abi(func conflictsWithOtherABI()) + @abi(func conflictsWithOtherABI()) // expected-note {{'conflictsWithOtherABI()' previously declared here}} func conflictsWithOtherABI1() {} - @abi(func conflictsWithOtherABI()) + @abi(func conflictsWithOtherABI()) // expected-error {{invalid redeclaration of 'conflictsWithOtherABI()'}} func conflictsWithOtherABI2() {} - @abi(func conflictsWithNonABI()) + @abi(func conflictsWithNonABI()) // expected-error {{invalid redeclaration of 'conflictsWithNonABI()'}} func conflictsWithNonABI_formal() {} - func conflictsWithNonABI() {} + func conflictsWithNonABI() {} // expected-note {{'conflictsWithNonABI()' previously declared here}} @abi(var var_noConflictWithSelf: Int) var var_noConflictWithSelf: Int = 0 @@ -211,19 +214,22 @@ struct Foo { @abi(var var_noConflictWithMutualChanges2: Int) var var_noConflictWithMutualChanges1: Int = 0 - @abi(var var_conflictsWithOtherABI: Int) + @abi(var var_conflictsWithOtherABI: Int) // expected-note {{'var_conflictsWithOtherABI' previously declared here}} var var_conflictsWithOtherABI1: Int = 0 - @abi(var var_conflictsWithOtherABI: Int) + @abi(var var_conflictsWithOtherABI: Int) // expected-error {{invalid redeclaration of 'var_conflictsWithOtherABI'}} var var_conflictsWithOtherABI2: Int = 0 - @abi(var var_conflictsWithNonABI: Int) + @abi(var var_conflictsWithNonABI: Int) // expected-error {{invalid redeclaration of 'var_conflictsWithNonABI'}} var var_conflictsWithNonABI_formal: Int = 0 - var var_conflictsWithNonABI: Int = 0 + var var_conflictsWithNonABI: Int = 0 // expected-note {{'var_conflictsWithNonABI' previously declared here}} } func fn() { + // TODO: Figure out if @abi makes sense in local scope and, if so, when we + // should complain about redecls. + @abi(func noConflictWithSelf()) func noConflictWithSelf() {} @@ -233,36 +239,40 @@ func fn() { @abi(func noConflictWithMutualChanges2()) func noConflictWithMutualChanges1() {} - @abi(func conflictsWithOtherABI()) + @abi(func conflictsWithOtherABI()) // expected-note {{'conflictsWithOtherABI()' previously declared here}} func conflictsWithOtherABI1() {} - @abi(func conflictsWithOtherABI()) + @abi(func conflictsWithOtherABI()) // expected-error {{invalid redeclaration of 'conflictsWithOtherABI()'}} func conflictsWithOtherABI2() {} - @abi(func conflictsWithNonABI()) + @abi(func conflictsWithNonABI()) // expected-error {{invalid redeclaration of 'conflictsWithNonABI()'}} func conflictsWithNonABI_formal() {} - func conflictsWithNonABI() {} + func conflictsWithNonABI() {} // expected-note {{'conflictsWithNonABI()' previously declared here}} + + var a = 1 // expected-note {{'a' previously declared here}} + var a = 1 // expected-error {{invalid redeclaration of 'a'}} @abi(var var_noConflictWithSelf: Int) - var var_noConflictWithSelf: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + var var_noConflictWithSelf: Int = 0 @abi(var var_noConflictWithMutualChanges1: Int) - var var_noConflictWithMutualChanges2: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + var var_noConflictWithMutualChanges2: Int = 0 @abi(var var_noConflictWithMutualChanges2: Int) - var var_noConflictWithMutualChanges1: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + var var_noConflictWithMutualChanges1: Int = 0 - @abi(var var_conflictsWithOtherABI: Int) - var var_conflictsWithOtherABI1: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + @abi(var var_conflictsWithOtherABI: Int) // expected-note {{'var_conflictsWithOtherABI' previously declared here}} + var var_conflictsWithOtherABI1: Int = 0 - @abi(var var_conflictsWithOtherABI: Int) - var var_conflictsWithOtherABI2: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + @abi(var var_conflictsWithOtherABI: Int) // expected-error {{invalid redeclaration of 'var_conflictsWithOtherABI'}} + var var_conflictsWithOtherABI2: Int = 0 - @abi(var var_conflictsWithNonABI: Int) - var var_conflictsWithNonABI_formal: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + // Diagnosed in the opposite order from usual due to being in a local scope where visibility may be limited: + @abi(var var_conflictsWithNonABI: Int) // expected-note {{'var_conflictsWithNonABI' previously declared here}} + var var_conflictsWithNonABI_formal: Int = 0 - var var_conflictsWithNonABI: Int = 0 // expected-warning {{was never used; consider replacing with '_' or removing it}} + var var_conflictsWithNonABI: Int = 0 // expected-error {{invalid redeclaration of 'var_conflictsWithNonABI'}} } // @@ -283,6 +293,9 @@ func func_with_nested_abi() { exit(0) } +@abi(var x = 1) // expected-error {{initial value is not allowed here}} expected-error {{type annotation missing in pattern}} +var x = 1 + // // Examples of expected use cases // diff --git a/tools/SourceKit/lib/SwiftLang/SwiftDocSupport.cpp b/tools/SourceKit/lib/SwiftLang/SwiftDocSupport.cpp index 111e6f1b9ac5a..a6448f90a7676 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftDocSupport.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftDocSupport.cpp @@ -219,6 +219,11 @@ class AnnotatingPrinter : public StreamPrinter { assert(!EntitiesStack.empty()); TextEntity Entity = std::move(EntitiesStack.back()); EntitiesStack.pop_back(); + + // We only care about API for documentation purposes. + if (!ABIRoleInfo(D).providesAPI()) + return; + unsigned EndOffset = OS.tell(); Entity.Range.Length = EndOffset - Entity.Range.Offset; if (EntitiesStack.empty()) { From c91cf69f2f8d8208119efb9771c0ecee73e8bb8c Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Thu, 26 Sep 2024 17:16:12 -0700 Subject: [PATCH 09/15] Add some basic validation of vars and funcs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check for: • Matching decl kinds • Matching PBD shapes (does every VarDecl on both sides have a counterpart?) • Matching function effects • Matching function arity (roughly) --- include/swift/AST/DiagnosticsSema.def | 30 +++++++ lib/Sema/TypeCheckAttr.cpp | 119 +++++++++++++++++++++++++- test/attr/attr_abi.swift | 44 +++++----- 3 files changed, 169 insertions(+), 24 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index acc4944aff20d..c237545a2809b 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8135,5 +8135,35 @@ ERROR(availability_value_generic_type_only_version_newer, none, ERROR(invalid_value_for_type_same_type,none, "cannot constrain type parameter %0 to be integer %1", (Type, Type)) +//===----------------------------------------------------------------------===// +// MARK: @abi Attribute +//===----------------------------------------------------------------------===// + +ERROR(attr_abi_mismatched_kind,none, + "cannot give %kind0 the ABI of a %1", + (Decl *, DescriptiveDeclKind)) + +ERROR(attr_abi_mismatched_arity,none, + "cannot give %kind0 the ABI of a %kindonly0 with a different number of " + "low-level parameters", + (ValueDecl *)) + +ERROR(attr_abi_mismatched_throws,none, + "cannot give %0 the ABI of a %kindonly0 which %select{cannot|can}1 throw", + (ValueDecl *, /*abiCanThrow=*/bool)) + +ERROR(attr_abi_mismatched_async,none, + "cannot give %0 the ABI of %select{a non-async|an async}1 %kindonly0", + (ValueDecl *, /*abiIsAsync=*/bool)) + +ERROR(attr_abi_mismatched_pbd_size,none, + "cannot give pattern binding the ABI of a binding with " + "%select{more|fewer}0 patterns", + (/*abiHasExtra=*/bool)) + +ERROR(attr_abi_mismatched_var,none, + "no match for %select{%kind0 in the ABI|ABI %kind0}1", + (ValueDecl *, /*isABI=*/bool)) + #define UNDEFINE_DIAGNOSTIC_MACROS #include "DefineDiagnosticMacros.h" diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index f726f77a6510c..42cc8b1e29faa 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -190,11 +190,81 @@ class AttributeChecker : public AttributeVisitor { IGNORED_ATTR(PreInverseGenerics) #undef IGNORED_ATTR +private: + static unsigned getABIArity(AbstractFunctionDecl *afd) { + unsigned arity = afd->getParameters()->size(); + arity += afd->getGenericSignature().getGenericParams().size(); + if (afd->hasImplicitSelfDecl()) + arity += 1; + return arity; + } + + void checkABIAttrPBD(PatternBindingDecl *APBD, VarDecl *VD) { + auto PBD = VD->getParentPatternBinding(); + + // To make sure we only diagnose this stuff once, check that VD is the first + // anchoring variable in the PBD. + bool isFirstAnchor = false; + for (auto i : range(PBD->getNumPatternEntries())) { + auto anchorVD = PBD->getAnchoringVarDecl(i); + if (anchorVD) { + isFirstAnchor = (anchorVD == VD); + break; + } + } + + if (!isFirstAnchor) + return; + + // Check that the PBDs have the same number of patterns. + if (PBD->getNumPatternEntries() < APBD->getNumPatternEntries()) { + diagnose(APBD->getPattern(PBD->getNumPatternEntries())->getLoc(), + diag::attr_abi_mismatched_pbd_size, /*abiHasExtra=*/false); + return; + } + if (PBD->getNumPatternEntries() > APBD->getNumPatternEntries()) { + diagnose(PBD->getPattern(APBD->getNumPatternEntries())->getLoc(), + diag::attr_abi_mismatched_pbd_size, /*abiHasExtra=*/true); + return; + } + + // Check that each pattern has the same number of variables. + for (auto i : range(PBD->getNumPatternEntries())) { + SmallVector VDs; + SmallVector AVDs; + + PBD->getPattern(i)->collectVariables(VDs); + APBD->getPattern(i)->collectVariables(AVDs); + + if (VDs.size() < AVDs.size()) { + for (auto AVD : drop_begin(AVDs, VDs.size())) { + AVD->diagnose(diag::attr_abi_mismatched_var, + AVD, /*isABI=*/true); + } + } + else if (VDs.size() > AVDs.size()) { + for (auto VD : drop_begin(VDs, AVDs.size())) { + VD->diagnose(diag::attr_abi_mismatched_var, + VD, /*isABI=*/false); + } + } + } + } + +public: void visitABIAttr(ABIAttr *attr) { Decl *AD = attr->abiDecl; if (isa(D) && isa(AD)) { - AD = cast(AD) - ->getVarAtSimilarStructuralPosition(cast(D)); + auto VD = cast(D); + auto APBD = cast(AD); + + // Diagnose dissimilar PBD structures. + checkABIAttrPBD(APBD, VD); + + // Do the rest of this checking on the corresponding VarDecl, not the + // PBD that's actually in the attribute. Note that `AD` will become null + // if they're too dissimilar to match up. + AD = APBD->getVarAtSimilarStructuralPosition(VD); } // TODO: EnumElementDecl? @@ -206,7 +276,52 @@ class AttributeChecker : public AttributeVisitor { if (AD->isInvalid()) return; + // Do the declarations have the same kind, broadly speaking? Many kinds have + // special mangling behavior (e.g. inits vs normal funcs) that make it + // unrealistic to treat one kind as though it were another. + if (D->getKind() != AD->getKind()) { + // FIXME: DescriptiveDeclKind is overly specific; we really just want to + // say that e.g. a `func` can't have the ABI of a `var`. + diagnoseAndRemoveAttr(attr, diag::attr_abi_mismatched_kind, + D, AD->getDescriptiveKind()); + return; + } + + if (isa(D)) { + auto AFD = cast(D); + auto AAFD = cast(AD); + + // FIXME: How much should we diagnose in IRGen for more precise ABI info? + + // Do the declarations have roughly the same number of parameters? We'll + // allow some fuzziness for what these parameters *are*, since there isn't + // always an ABI difference between e.g. a free function with N parameters + // and an instance method with N-1 parameters (plus an implicit `self`). + if (getABIArity(AFD) != getABIArity(AAFD)) { + diagnoseAndRemoveAttr(attr, diag::attr_abi_mismatched_arity, + AFD); + } + + // Do the declarations match in throwing behavior? We don't care about + // `throws` vs. `rethrows` here, just whether callers will account for an + // error return. + // FIXME: Typed throws? + if (AFD->hasThrows() != AAFD->hasThrows()) { + diagnoseAndRemoveAttr(attr, diag::attr_abi_mismatched_throws, + AFD, /*abiCanThrow=*/AAFD->hasThrows()); + } + + // Do the declarations match in async-ness? + if (AFD->hasAsync() != AAFD->hasAsync()) { + diagnoseAndRemoveAttr(attr, diag::attr_abi_mismatched_async, + AFD, /*abiHasAsync=*/AAFD->hasAsync()); + } + } + // TODO: Validate more + // FIXME: The list of properties that have to match is practically endless + // and will grow as new features are added to the compiler. We might want to + // write an AttributeVisitor just to help us catch omissions over time. } void visitAlignmentAttr(AlignmentAttr *attr) { diff --git a/test/attr/attr_abi.swift b/test/attr/attr_abi.swift index 7e53b2a502a0b..e0fb3d63c3e63 100644 --- a/test/attr/attr_abi.swift +++ b/test/attr/attr_abi.swift @@ -13,10 +13,10 @@ func funcForFunc() {} @abi(var varForVar_abi: Int) var varForVar: Int = 0 -@abi(func funcForVar_abi()) +@abi(func funcForVar_abi()) // expected-error {{cannot give var 'funcForVar' the ABI of a global function}} var funcForVar: Int = 0 -@abi(var varForFunc_abi: Int) +@abi(var varForFunc_abi: Int) // expected-error {{cannot give global function 'varForFunc()' the ABI of a pattern binding}} func varForFunc() {} // @@ -26,10 +26,10 @@ func varForFunc() {} @abi(func param00_generic00() -> Int) func param00_generic00() -> Int { fatalError() } -@abi(func param10_generic00(_: Int) -> Int) +@abi(func param10_generic00(_: Int) -> Int) // expected-error {{cannot give global function 'param10_generic00()' the ABI of a global function with a different number of low-level parameters}} func param10_generic00() -> Int { fatalError() } -@abi(func param01_generic00() -> Int) +@abi(func param01_generic00() -> Int) // expected-error {{cannot give global function 'param01_generic00' the ABI of a global function with a different number of low-level parameters}} func param01_generic00(_: Int) -> Int { fatalError() } @abi(func param11_generic00(_: Int) -> Int) @@ -37,30 +37,30 @@ func param11_generic00(_: Int) -> Int { fatalError() } -@abi(func param00_generic10() -> T) +@abi(func param00_generic10() -> T) // expected-error {{cannot give global function 'param00_generic10()' the ABI of a global function with a different number of low-level parameters}} func param00_generic10() -> Int { fatalError() } -@abi(func param10_generic10(_: Int) -> T) +@abi(func param10_generic10(_: Int) -> T) // expected-error {{cannot give global function 'param10_generic10()' the ABI of a global function with a different number of low-level parameters}} func param10_generic10() -> Int { fatalError() } @abi(func param01_generic10() -> T) func param01_generic10(_: Int) -> Int { fatalError() } -@abi(func param11_generic10(_: Int) -> T) +@abi(func param11_generic10(_: Int) -> T) // expected-error {{cannot give global function 'param11_generic10' the ABI of a global function with a different number of low-level parameters}} func param11_generic10(_: Int) -> Int { fatalError() } -@abi(func param00_generic01() -> Int) +@abi(func param00_generic01() -> Int) // expected-error {{cannot give global function 'param00_generic01()' the ABI of a global function with a different number of low-level parameters}} func param00_generic01() -> T { fatalError() } @abi(func param10_generic01(_: Int) -> Int) func param10_generic01() -> T { fatalError() } -@abi(func param01_generic01() -> Int) +@abi(func param01_generic01() -> Int) // expected-error {{cannot give global function 'param01_generic01' the ABI of a global function with a different number of low-level parameters}} func param01_generic01(_: Int) -> T { fatalError() } -@abi(func param11_generic01(_: Int) -> Int) +@abi(func param11_generic01(_: Int) -> Int) // expected-error {{cannot give global function 'param11_generic01' the ABI of a global function with a different number of low-level parameters}} func param11_generic01(_: Int) -> T { fatalError() } @@ -68,10 +68,10 @@ func param11_generic01(_: Int) -> T { fatalError() } @abi(func param00_generic11() -> T) func param00_generic11() -> T { fatalError() } -@abi(func param10_generic11(_: Int) -> T) +@abi(func param10_generic11(_: Int) -> T) // expected-error {{cannot give global function 'param10_generic11()' the ABI of a global function with a different number of low-level parameters}} func param10_generic11() -> T { fatalError() } -@abi(func param01_generic11() -> T) +@abi(func param01_generic11() -> T) // expected-error {{cannot give global function 'param01_generic11' the ABI of a global function with a different number of low-level parameters}} func param01_generic11(_: Int) -> T { fatalError() } @abi(func param11_generic11(_: Int) -> T) @@ -84,13 +84,13 @@ func param11_generic11(_: Int) -> T { fatalError() } @abi(func throws00(_: () throws -> Void)) func throws00(_: () throws -> Void) {} -@abi(func throws10(_: () throws -> Void) throws) +@abi(func throws10(_: () throws -> Void) throws) // expected-error {{cannot give 'throws10' the ABI of a global function which can throw}} func throws10(_: () throws -> Void) {} -@abi(func throws20(_: () throws -> Void) rethrows) +@abi(func throws20(_: () throws -> Void) rethrows) // expected-error {{cannot give 'throws20' the ABI of a global function which can throw}} func throws20(_: () throws -> Void) {} -@abi(func throws01(_: () throws -> Void)) +@abi(func throws01(_: () throws -> Void)) // expected-error {{cannot give 'throws01' the ABI of a global function which cannot throw}} func throws01(_: () throws -> Void) throws {} @abi(func throws11(_: () throws -> Void) throws) @@ -99,7 +99,7 @@ func throws11(_: () throws -> Void) throws {} @abi(func throws21(_: () throws -> Void) rethrows) func throws21(_: () throws -> Void) throws {} -@abi(func throws02(_: () throws -> Void)) +@abi(func throws02(_: () throws -> Void)) // expected-error {{cannot give 'throws02' the ABI of a global function which cannot throw}} func throws02(_: () throws -> Void) rethrows {} @abi(func throws12(_: () throws -> Void) throws) @@ -115,10 +115,10 @@ func throws22(_: () throws -> Void) rethrows {} @abi(func async00()) func async00() {} -@abi(func async10() async) +@abi(func async10() async) // expected-error {{cannot give 'async10()' the ABI of an async global function}} func async10() {} -@abi(func async01()) +@abi(func async01()) // expected-error {{cannot give 'async01()' the ABI of a non-async global function}} func async01() async {} @abi(func async11() async) @@ -128,17 +128,17 @@ func async11() async {} // PBD shape checking // -@abi(var x1, y1: Int) +@abi(var x1, y1: Int) // expected-error {{cannot give pattern binding the ABI of a binding with more patterns}} var x1: Int = 0 @abi(var x2: Int) -var x2 = 0, y2: Int = 0 +var x2 = 0, y2: Int = 0 // expected-error {{cannot give pattern binding the ABI of a binding with fewer patterns}} -@abi(var (x3, y3): (Int, Int), (a3, b3): (Int, Int)) +@abi(var (x3, y3): (Int, Int), (a3, b3): (Int, Int)) // expected-error {{no match for ABI var 'b3'}} var (x3, y3): (Int, Int) = (0, 0), a3: Int = 0 @abi(var (x4, y4): (Int, Int), a4: Int) -var (x4, y4): (Int, Int) = (0, 0), (a4, b4): (Int, Int) = (0, 0) +var (x4, y4): (Int, Int) = (0, 0), (a4, b4): (Int, Int) = (0, 0) // expected-error {{no match for var 'b4' in the ABI}} // // Conflict diagnostics From 0f3cf62c55a00fc62e22172c8e51bb1f730b3245 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Mon, 30 Sep 2024 17:10:44 -0700 Subject: [PATCH 10/15] Use @abi attribute in mangling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What’s implemented now is actually *far* more thorough than what the surface syntax can currently express, mainly because I can’t apply @abi to nominal types yet. --- include/swift/AST/ASTMangler.h | 8 ++ lib/AST/ASTMangler.cpp | 151 ++++++++++++++++++++++++++++++++- lib/Sema/TypeCheckAttr.cpp | 3 + test/IRGen/asmname.swift | 52 +++++++++++- 4 files changed, 208 insertions(+), 6 deletions(-) diff --git a/include/swift/AST/ASTMangler.h b/include/swift/AST/ASTMangler.h index 650bdf98fe945..36f4d54409b47 100644 --- a/include/swift/AST/ASTMangler.h +++ b/include/swift/AST/ASTMangler.h @@ -209,6 +209,14 @@ class ASTMangler : public Mangler { return tryMangleSubstitution(type.getPointer()); } +protected: + using Mangler::addSubstitution; + void addSubstitution(const Decl *decl); + + using Mangler::tryMangleSubstitution; + bool tryMangleSubstitution(const Decl *decl); + +public: std::string mangleClosureEntity(const AbstractClosureExpr *closure, SymbolKind SKind); diff --git a/lib/AST/ASTMangler.cpp b/lib/AST/ASTMangler.cpp index 1b5c03833ec19..1c04e4868039c 100644 --- a/lib/AST/ASTMangler.cpp +++ b/lib/AST/ASTMangler.cpp @@ -66,6 +66,54 @@ using namespace swift; using namespace swift::Mangle; +template +static DeclType *getABIDecl(DeclType *D) { + if (!D) + return nullptr; + + auto abiRole = ABIRoleInfo(D); + if (!abiRole.providesABI()) + return abiRole.getCounterpart(); + return nullptr; +} + +static std::optional +getABIDecl(ASTMangler::SymbolicReferent ref) { + switch (ref.getKind()) { + case ASTMangler::SymbolicReferent::NominalType: + if (auto abiTypeDecl = getABIDecl(ref.getNominalType())) { + return ASTMangler::SymbolicReferent(abiTypeDecl); + } + break; + + case ASTMangler::SymbolicReferent::OpaqueType: + if (auto abiTypeDecl = getABIDecl(ref.getOpaqueType())) { + return ASTMangler::SymbolicReferent(abiTypeDecl); + } + break; + + case ASTMangler::SymbolicReferent::ExtendedExistentialTypeShape: + // Do nothing; mangling will use the underlying ABI decls in the end. + break; + } + + return std::nullopt; +} + +void ASTMangler::addSubstitution(const Decl *decl) { + if (auto abiDecl = getABIDecl(decl)) { + return addSubstitution(abiDecl); + } + return Mangler::addSubstitution(decl); +} + +bool ASTMangler::tryMangleSubstitution(const Decl *decl) { + if (auto abiDecl = getABIDecl(decl)) { + return tryMangleSubstitution(abiDecl); + } + return Mangler::tryMangleSubstitution(decl); +} + bool ASTMangler::inversesAllowed(const Decl *decl) { if (!decl) return true; @@ -302,6 +350,10 @@ std::string ASTMangler::mangleClosureWitnessThunk( } std::string ASTMangler::mangleGlobalVariableFull(const VarDecl *decl) { + if (auto abiDecl = getABIDecl(decl)) { + return mangleGlobalVariableFull(abiDecl); + } + // Clang globals get mangled using Clang's mangler. if (auto clangDecl = dyn_cast_or_null(decl->getClangDecl())) { @@ -431,6 +483,10 @@ std::string ASTMangler::mangleGlobalInit(const PatternBindingDecl *pd, Pattern *pattern = pd->getPattern(pbdEntry); bool first = true; pattern->forEachVariable([&](VarDecl *D) { + if (auto abiD = getABIDecl(D)) { + D = abiD; + } + if (first) { BaseEntitySignature base(D); appendContextOf(D, base); @@ -519,6 +575,10 @@ std::string ASTMangler::mangleAutoDiffLinearMap( void ASTMangler::beginManglingWithAutoDiffOriginalFunction( const AbstractFunctionDecl *afd) { + if (auto abiAFD = getABIDecl(afd)) { + return beginManglingWithAutoDiffOriginalFunction(abiAFD); + } + if (auto *attr = afd->getAttrs().getAttribute()) { beginManglingWithoutPrefix(); appendOperator(attr->Name); @@ -852,6 +912,10 @@ void ASTMangler::appendAnyDecl(const ValueDecl *Decl) { } else if (auto GTD = dyn_cast(Decl)) { appendAnyGenericType(GTD); } else if (isa(Decl)) { + if (auto abiDecl = getABIDecl(Decl)) { + return appendAnyDecl(abiDecl); + } + BaseEntitySignature base(Decl); appendContextOf(Decl, base); appendDeclName(Decl); @@ -904,6 +968,10 @@ std::string ASTMangler::mangleAccessorEntityAsUSR(AccessorKind kind, } std::string ASTMangler::mangleLocalTypeDecl(const TypeDecl *type) { + if (auto abiType = getABIDecl(type)) { + return mangleLocalTypeDecl(abiType); + } + beginManglingWithoutPrefix(); AllowNamelessEntities = true; OptimizeProtocolNames = false; @@ -954,6 +1022,10 @@ std::string ASTMangler::mangleHasSymbolQuery(const ValueDecl *Decl) { } else if (auto GTD = dyn_cast(Decl)) { appendAnyGenericType(GTD); } else if (isa(Decl)) { + if (auto abiDecl = getABIDecl(Decl)) { + Decl = abiDecl; + } + BaseEntitySignature nullBase(nullptr); appendContextOf(Decl, nullBase); appendDeclName(Decl); @@ -1061,6 +1133,7 @@ getOverriddenSwiftProtocolObjCName(const ValueDecl *decl, } void ASTMangler::appendDeclName(const ValueDecl *decl, DeclBaseName name) { + ASSERT(!getABIDecl(decl) && "caller should make sure we get ABI decls"); if (name.empty()) name = decl->getBaseName(); assert(!name.isSpecial() && "Cannot print special names"); @@ -1196,6 +1269,10 @@ void ASTMangler::appendExistentialLayout( bool DroppedRequiresClass = false; bool SawRequiresClass = false; for (auto proto : layout.getProtocols()) { + if (auto abiProto = getABIDecl(proto)) { + proto = abiProto; + } + // Skip requirements to conform to an invertible protocols. // We only mangle inverse requirements, but as a constrained existential. if (proto->getInvertibleProtocolKind()) @@ -1553,6 +1630,9 @@ void ASTMangler::appendType(Type type, GenericSignature sig, Decl = typeAlias->getDecl(); else Decl = type->getAnyGeneric(); + if (auto abiDecl = getABIDecl(Decl)) { + Decl = abiDecl; + } if (shouldMangleAsGeneric(type)) { // Try to mangle the entire name as a substitution. if (tryMangleTypeSubstitution(tybase, sig)) @@ -2050,6 +2130,11 @@ void ASTMangler::appendSymbolicExtendedExistentialType( Type type, GenericSignature sig, const ValueDecl *forDecl) { + if (auto abiShapeReferent = getABIDecl(shapeReferent)) { + return appendSymbolicExtendedExistentialType(abiShapeReferent.value(), type, + sig, forDecl); + } + assert(shapeReferent.getKind() == SymbolicReferent::ExtendedExistentialTypeShape); assert(canSymbolicReference(shapeReferent)); @@ -2581,6 +2666,7 @@ void ASTMangler::appendContext(const DeclContext *ctx, void ASTMangler::appendModule(const ModuleDecl *module, StringRef useModuleName) { assert(!module->getParent() && "cannot mangle nested modules!"); + ASSERT(!getABIDecl(module)); // Use the module real name in mangling; this is the physical name // of the module on-disk, which can be different if -module-alias is @@ -2639,6 +2725,10 @@ void ASTMangler::appendProtocolName(const ProtocolDecl *protocol, bool allowStandardSubstitution) { assert(AllowMarkerProtocols || !protocol->isMarkerProtocol()); + if (auto abiProtocol = getABIDecl(protocol)) { + return appendProtocolName(abiProtocol, allowStandardSubstitution); + } + if (allowStandardSubstitution && tryAppendStandardSubstitution(protocol)) return; @@ -2700,6 +2790,10 @@ ASTMangler::getClangDeclForMangling(const ValueDecl *vd) { } void ASTMangler::appendSymbolicReference(SymbolicReferent referent) { + if (auto abiReferent = getABIDecl(referent)) { + return appendSymbolicReference(abiReferent.value()); + } + // Drop in a placeholder. The real reference value has to be filled in during // lowering to IR. auto offset = Buffer.str().size(); @@ -2833,6 +2927,10 @@ void ASTMangler::appendContextualInverses(const GenericTypeDecl *contextDecl, void ASTMangler::appendExtension(const ExtensionDecl* ext, BaseEntitySignature &base, StringRef useModuleName) { + if (auto abiExt = getABIDecl(ext)) { + return appendExtension(abiExt, base, useModuleName); + } + auto decl = ext->getExtendedNominal(); // Recover from erroneous extension. if (!decl) @@ -2883,6 +2981,10 @@ void ASTMangler::appendAnyGenericType(const GenericTypeDecl *decl) { void ASTMangler::appendAnyGenericType(const GenericTypeDecl *decl, BaseEntitySignature &base) { + if (auto abiDecl = getABIDecl(decl)) { + return appendAnyGenericType(abiDecl); + } + auto *nominal = dyn_cast(decl); if (nominal && isa(nominal)) @@ -3795,6 +3897,10 @@ ASTMangler::dropProtocolsFromAssociatedTypes(Type type, void ASTMangler::appendAssociatedTypeName(DependentMemberType *dmt, GenericSignature sig) { if (auto assocTy = dmt->getAssocType()) { + if (auto abiAssocTy = getABIDecl(assocTy)) { + assocTy = abiAssocTy; + } + appendIdentifier(assocTy->getName().str()); // If the base type is known to have a single protocol conformance @@ -3901,6 +4007,10 @@ CanType ASTMangler::getDeclTypeForMangling( const ValueDecl *decl, GenericSignature &genericSig, GenericSignature &parentGenericSig) { + if (auto abiDecl = getABIDecl(decl)) { + return getDeclTypeForMangling(abiDecl, genericSig, parentGenericSig); + } + genericSig = GenericSignature(); parentGenericSig = GenericSignature(); @@ -4000,6 +4110,10 @@ bool ASTMangler::tryAppendStandardSubstitution(const GenericTypeDecl *decl) { void ASTMangler::appendConstructorEntity(const ConstructorDecl *ctor, bool isAllocating) { + if (auto abiCtor = getABIDecl(ctor)) { + return appendConstructorEntity(abiCtor, isAllocating); + } + BaseEntitySignature base(ctor); appendContextOf(ctor, base); appendDeclType(ctor, base); @@ -4013,6 +4127,10 @@ void ASTMangler::appendConstructorEntity(const ConstructorDecl *ctor, void ASTMangler::appendDestructorEntity(const DestructorDecl *dtor, DestructorKind kind) { + if (auto abiDtor = getABIDecl(dtor)) { + return appendDestructorEntity(abiDtor, kind); + } + BaseEntitySignature base(dtor); appendContextOf(dtor, base); switch (kind) { @@ -4031,6 +4149,10 @@ void ASTMangler::appendDestructorEntity(const DestructorDecl *dtor, void ASTMangler::appendAccessorEntity(StringRef accessorKindCode, const AbstractStorageDecl *decl, bool isStatic) { + if (auto abiDecl = getABIDecl(decl)) { + return appendAccessorEntity(accessorKindCode, abiDecl, isStatic); + } + BaseEntitySignature base(decl); appendContextOf(decl, base); if (auto *varDecl = dyn_cast(decl)) { @@ -4058,6 +4180,10 @@ void ASTMangler::appendEntity(const ValueDecl *decl, BaseEntitySignature &base, StringRef EntityOp, bool isStatic) { + if (auto abiDecl = getABIDecl(decl)) { + return appendEntity(abiDecl, base, EntityOp, isStatic); + } + appendContextOf(decl, base); appendDeclName(decl); appendDeclType(decl, base); @@ -4069,7 +4195,11 @@ void ASTMangler::appendEntity(const ValueDecl *decl, void ASTMangler::appendEntity(const ValueDecl *decl) { assert(!isa(decl)); assert(!isa(decl)); - + + if (auto abiDecl = getABIDecl(decl)) { + return appendEntity(abiDecl); + } + // Handle accessors specially, they are mangled as modifiers on the accessed // declaration. if (auto accessor = dyn_cast(decl)) { @@ -4424,6 +4554,10 @@ ASTMangler::mangleOpaqueTypeDescriptorRecord(const OpaqueTypeDecl *decl) { void ASTMangler::appendDistributedThunk( const AbstractFunctionDecl *thunk, bool asReference) { + if (auto abiThunk = getABIDecl(thunk)) { + return appendDistributedThunk(abiThunk, asReference); + } + // Marker protocols cannot be checked at runtime, so there is no point // in recording them for distributed thunks. llvm::SaveAndRestore savedAllowMarkerProtocols(AllowMarkerProtocols, @@ -4486,6 +4620,9 @@ void ASTMangler::appendDistributedThunk( if (stubClassLookupResults.size() > 0) { stubActorDecl = dyn_cast_or_null(stubClassLookupResults.front()); + if (auto abiStub = getABIDecl(stubActorDecl)) { + stubActorDecl = abiStub; + } } } @@ -4505,7 +4642,11 @@ void ASTMangler::appendDistributedThunk( "mangled as thunks"); // A distributed getter is mangled as the name of its storage (i.e. "the // var") - appendIdentifier(accessor->getStorage()->getBaseIdentifier().str()); + auto storage = accessor->getStorage(); + if (auto abiStorage = getABIDecl(storage)) { + storage = abiStorage; + } + appendIdentifier(storage->getBaseIdentifier().str()); } else { appendIdentifier(thunk->getBaseIdentifier().str()); } @@ -4812,6 +4953,10 @@ getPrecheckedLocalContextDiscriminator(const Decl *decl, Identifier name) { std::string ASTMangler::mangleAttachedMacroExpansion( const Decl *decl, CustomAttr *attr, MacroRole role) { + if (auto abiDecl = getABIDecl(decl)) { + return mangleAttachedMacroExpansion(decl, attr, role); + } + // FIXME(kavon): using the decl causes a cycle. Is a null base fine? BaseEntitySignature nullBase(nullptr); @@ -4926,6 +5071,7 @@ std::string ASTMangler::mangleAttachedMacroExpansion( static void gatherExistentialRequirements(SmallVectorImpl &reqs, ParameterizedProtocolType *PPT) { auto protoTy = PPT->getBaseType(); + ASSERT(!getABIDecl(protoTy->getDecl()) && "need to figure out behavior"); PPT->getRequirements(protoTy->getDecl()->getSelfInterfaceType(), reqs); } @@ -4946,6 +5092,7 @@ static void extractExistentialInverseRequirements( for (auto ip : PCT->getInverses()) { auto *proto = ctx.getProtocol(getKnownProtocolKind(ip)); assert(proto); + ASSERT(!getABIDecl(proto) && "can't use @abi on inverse protocols"); inverses.push_back({existentialSelf, proto, SourceLoc()}); } } diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 42cc8b1e29faa..01b091d8a0eaf 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -318,6 +318,9 @@ class AttributeChecker : public AttributeVisitor { } } + // TODO: Diagnose if Protocol::isMarkerProtocol() - contradiction in terms + // (and mangler can't handle invertible protocols with @abi) + // TODO: Validate more // FIXME: The list of properties that have to match is practically endless // and will grow as new features are added to the compiler. We might want to diff --git a/test/IRGen/asmname.swift b/test/IRGen/asmname.swift index c5543a364dc0e..b86cc485723c1 100644 --- a/test/IRGen/asmname.swift +++ b/test/IRGen/asmname.swift @@ -1,7 +1,9 @@ -// RUN: %target-swift-frontend %s -emit-ir | %FileCheck %s - -// REQUIRES: CPU=i386 || CPU=x86_64 +// RUN: %target-swift-frontend -enable-experimental-feature ABIAttribute %s -emit-ir > %t.ir +// RUN: %FileCheck --input-file %t.ir %s +// RUN: %FileCheck --check-prefix NEGATIVE --input-file %t.ir %s +// REQUIRES: CPU=i386 || CPU=x86_64 || CPU=arm64 +// REQUIRES: swift_feature_ABIAttribute // Non-Swift _silgen_name definitions @@ -35,7 +37,7 @@ private func PlainPrivate() { } // CHECK: define{{( dllexport)?}}{{( protected)?}} swiftcc void @silgen_name_public // CHECK: define hidden swiftcc void @silgen_name_internal // CHECK: define internal swiftcc void @silgen_name_private -// CHECK-NOT: SilgenName +// NEGATIVE-NOT: define {{.*}}SilgenName // Swift cdecl definitions @@ -52,6 +54,8 @@ private func PlainPrivate() { } // CHECK: define hidden swiftcc void @"$s7asmname13CDeclInternal // CHECK: define internal void @cdecl_private() + + // silgen_name on enum constructors public enum X { case left(Int64) @@ -65,3 +69,43 @@ extension X { self = .left(blah) } } + + + +// Swift abi definitions + +@abi(func abi_ABIAttrPublic()) public func api_ABIAttrPublic() { } +@abi(func abi_ABIAttrInternal()) internal func api_ABIAttrInternal() { } +@abi(func abi_ABIAttrPrivate()) private func api_ABIAttrPrivate() { } +// CHECK: define{{( dllexport)?}}{{( protected)?}} swiftcc void @"$s7asmname17abi_ABIAttrPublic +// CHECK: define hidden swiftcc void @"$s7asmname19abi_ABIAttrInternal +// CHECK: define internal swiftcc void @"$s7asmname18abi_ABIAttrPrivate + +@abi(var abi_ABIAttrPublic_var: Int64) public var api_ABIAttrPublic_var: Int64 { get { 1 } set {} } +@abi(var abi_ABIAttrInternal_var: Int64) internal var api_ABIAttrInternal_var: Int64 { get { 1 } set {} } +@abi(var abi_ABIAttrPrivate_var: Int64) private var api_ABIAttrPrivate_var: Int64 { get { 1 } set {} } +// CHECK: define{{( dllexport)?}}{{( protected)?}} swiftcc i64 @"$s7asmname21abi_ABIAttrPublic_vars5Int64Vvg" +// CHECK: define{{( dllexport)?}}{{( protected)?}} swiftcc void @"$s7asmname21abi_ABIAttrPublic_vars5Int64Vvs" +// CHECK: define hidden swiftcc i64 @"$s7asmname23abi_ABIAttrInternal_vars5Int64Vvg" +// CHECK: define hidden swiftcc void @"$s7asmname23abi_ABIAttrInternal_vars5Int64Vvs" +// CHECK: define internal swiftcc i64 @"$s7asmname22abi_ABIAttrPrivate_vars5Int64Vvg" +// CHECK: define internal swiftcc void @"$s7asmname22abi_ABIAttrPrivate_vars5Int64Vvs" + +@abi(public func abi_ABIAttrGenericNonSendableToSendable(_ value: T) -> T) +public func api_ABIAttrGenericNonSendableToSendable(_ value: T) -> T { return value } +// CHECK: define{{( dllexport)?}}{{( protected)?}} swiftcc void @"$s7asmname031abi_ABIAttrGenericNonSendableToF0yxxlF" +// NEGATIVE-NOT: @"$s7asmname031abi_ABIAttrGenericNonSendableToF0yxxs0F0RzlF" + +// Similar to hack applied to `AsyncStream.init(unfolding:onCancel:)` +@abi(public func abi_ABIAttrPreconcurrencyToNotPreconcurrency(_ c1: () -> Void, _ c2: @Sendable () -> Void)) +@preconcurrency public func api_ABIAttrPreconcurrencyToNotPreconcurrency(_ c1: () -> Void, _ c2: @Sendable () -> Void) {} +// CHECK: define{{( dllexport)?}}{{( protected)?}} swiftcc void @"$s7asmname030abi_ABIAttrPreconcurrencyToNotD0yyyyXE_yyYbXEtF" + +extension X { +// CHECK: define{{( dllexport)?}}{{( protected)?}} swiftcc { i64, i8 } @"$s7asmname1XO8abi_blahACs5Int64V_tcfC" + @abi(init(abi_blah: Int64)) + public init(api_blah: Int64) { + self = .left(api_blah) + } +} +// NEGATIVE-NOT: define {{.*}}api_ From 0d34b4da5b00dc3548e5498f49323e55ddbf5b2f Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Fri, 4 Oct 2024 20:25:25 -0700 Subject: [PATCH 11/15] Print @abi into module interface (suppressibly) --- include/swift/AST/PrintOptions.h | 4 +++ include/swift/Basic/Features.def | 2 +- lib/AST/ASTPrinter.cpp | 45 ++++++++++++++++++++++++++++++++ lib/AST/Attr.cpp | 12 ++++++--- lib/AST/FeatureSet.cpp | 35 ++++++++++++++++++++----- test/ModuleInterface/attrs.swift | 26 ++++++++++++++++-- 6 files changed, 111 insertions(+), 13 deletions(-) diff --git a/include/swift/AST/PrintOptions.h b/include/swift/AST/PrintOptions.h index 1dd5af359e2d6..2b630e2272284 100644 --- a/include/swift/AST/PrintOptions.h +++ b/include/swift/AST/PrintOptions.h @@ -237,6 +237,10 @@ struct PrintOptions { /// Use the original module name to qualify a symbol. bool UseOriginallyDefinedInModuleNames = false; + /// Add a `@_silgen_name` attribute to each function that + /// is compatible with one that specifies its mangled name. + bool PrintSyntheticSILGenName = false; + /// Print Swift.Array and Swift.Optional with sugared syntax /// ([] and ?), even if there are no sugar type nodes. bool SynthesizeSugarOnTypes = false; diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 8ab9f8cea9666..65c70df097738 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -437,7 +437,7 @@ EXPERIMENTAL_FEATURE(GenerateForceToMainActorThunks, false) EXPERIMENTAL_FEATURE(AddressableParameters, true) /// Allow the @abi attribute. -EXPERIMENTAL_FEATURE(ABIAttribute, true) +SUPPRESSIBLE_EXPERIMENTAL_FEATURE(ABIAttribute, true) #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 90bfe802a248f..b9f3d692c5a19 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -1230,6 +1230,18 @@ static bool hasLessAccessibleSetter(const AbstractStorageDecl *ASD) { return ASD->getSetterFormalAccess() < ASD->getFormalAccess(); } +bool canPrintSyntheticSILGenName(const Decl *D) { + ASSERT(!D->getAttrs().hasAttribute()); + + // Only print on functions, and only those functions with no barriers to use. + if (auto FD = dyn_cast(D); !FD + || FD->getAttrs().hasAttribute() + || FD->getOpaqueResultTypeDecl()) + return false; + + return true; +} + void PrintAST::printAttributes(const Decl *D) { if (Options.SkipAttributes) return; @@ -1247,6 +1259,27 @@ void PrintAST::printAttributes(const Decl *D) { // for each decl They cannot be shared across different decls. assert(Options.ExcludeCustomAttrList.empty()); + if (Options.PrintSyntheticSILGenName + && !D->getAttrs().hasAttribute()) { + if (canPrintSyntheticSILGenName(D)) { + auto mangledName = Mangle::ASTMangler(D->getASTContext()) + .mangleAnyDecl(cast(D), /*prefix=*/true, + /*respectOriginallyDefinedIn=*/true); + Printer.printAttrName("@_silgen_name"); + Printer << "("; + Printer.printEscapedStringLiteral(mangledName); + Printer << ")\n"; + } + else { + Printer.printAttrName("@available"); + Printer << "(*, unavailable, message: "; + Printer.printEscapedStringLiteral( + "this compiler cannot match the ABI specified by the @abi attribute"); + Printer << ")\n"; + Options.ExcludeAttrList.push_back(DeclAttrKind::Available); + } + } + if (Options.PrintImplicitAttrs) { // Don't print a redundant 'final' if we are printing a 'static' decl. @@ -3139,6 +3172,16 @@ suppressingFeatureCoroutineAccessors(PrintOptions &options, action(); } +static void +suppressingFeatureABIAttribute(PrintOptions &options, + llvm::function_ref action) { + llvm::SaveAndRestore scope(options.PrintSyntheticSILGenName, true); + unsigned originalExcludeAttrCount = options.ExcludeAttrList.size(); + options.ExcludeAttrList.push_back(DeclAttrKind::ABI); + action(); + options.ExcludeAttrList.resize(originalExcludeAttrCount); +} + /// Suppress the printing of a particular feature. static void suppressingFeature(PrintOptions &options, Feature feature, llvm::function_ref action) { @@ -3910,6 +3953,8 @@ void PrintAST::printOneParameter(const ParamDecl *param, void PrintAST::printParameterList(ParameterList *PL, ArrayRef params, bool isAPINameByDefault) { + llvm::SaveAndRestore scope(Options.PrintSyntheticSILGenName, false); + Printer.printStructurePre(PrintStructureKind::FunctionParameterList); SWIFT_DEFER { Printer.printStructurePost(PrintStructureKind::FunctionParameterList); diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index eeee0d0e0370e..8e761d400b52f 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -785,7 +785,7 @@ void DeclAttributes::print(ASTPrinter &Printer, const PrintOptions &Options, AttributeVector attributes; AttributeVector modifiers; bool libraryLevelAPI = - D->getASTContext().LangOpts.LibraryLevel == LibraryLevel::API; + D && D->getASTContext().LangOpts.LibraryLevel == LibraryLevel::API; for (auto DA : llvm::reverse(FlattenedAttrs)) { // Don't skip implicit custom attributes. Custom attributes like global @@ -1648,8 +1648,14 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options, case DeclAttrKind::ABI: { auto *attr = cast(this); Printer << "@abi("; - if (attr->abiDecl) - attr->abiDecl->print(Printer, Options); + Decl *abiDecl = attr->abiDecl; + if (abiDecl && Options.ExplodePatternBindingDecls + && isa(abiDecl) && D && isa(D)) + abiDecl = cast(abiDecl) + ->getVarAtSimilarStructuralPosition( + const_cast(cast(D))); + if (abiDecl) + abiDecl->print(Printer, Options); Printer << ")"; break; diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 94a35fe0c2404..98a2044ec7a41 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -323,9 +323,18 @@ static bool usesFeatureAllowUnsafeAttribute(Decl *decl) { return decl->getAttrs().hasAttribute(); } +static ABIAttr *getABIAttr(Decl *decl) { + if (auto pbd = dyn_cast(decl)) + for (auto i : range(pbd->getNumPatternEntries())) + if (auto anchorVar = pbd->getAnchoringVarDecl(i)) + return getABIAttr(anchorVar); + // FIXME: EnumCaseDecl/EnumElementDecl + + return decl->getAttrs().getAttribute(); +} + static bool usesFeatureABIAttribute(Decl *decl) { - auto abiAttr = decl->getAttrs().getAttribute(); - return abiAttr && !abiAttr->isInverse(); + return getABIAttr(decl) != nullptr; } UNINTERESTING_FEATURE(WarnUnsafe) @@ -430,26 +439,38 @@ static bool allowFeatureSuppression(StringRef featureName, Decl *decl) { /// Go through all the features used by the given declaration and /// either add or remove them to this set. void FeatureSet::collectFeaturesUsed(Decl *decl, InsertOrRemove operation) { + // Count feature usage in an ABI decl as feature usage by the API, not itself, + // since we can't use `#if` inside an @abi attribute. + Decl *abiDecl = nullptr; + if (auto abiAttr = getABIAttr(decl)) { + abiDecl = abiAttr->abiDecl; + } + +#define CHECK(Function) (Function(decl) || (abiDecl && Function(abiDecl))) +#define CHECK_ARG(Function, Arg) (Function(Arg, decl) || (abiDecl && Function(Arg, abiDecl))) + // Go through each of the features, checking whether the // declaration uses that feature. #define LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ - if (usesFeature##FeatureName(decl)) \ + if (CHECK(usesFeature##FeatureName)) \ collectRequiredFeature(Feature::FeatureName, operation); #define SUPPRESSIBLE_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ - if (usesFeature##FeatureName(decl)) { \ - if (disallowFeatureSuppression(#FeatureName, decl)) \ + if (CHECK(usesFeature##FeatureName)) { \ + if (CHECK_ARG(disallowFeatureSuppression, #FeatureName)) \ collectRequiredFeature(Feature::FeatureName, operation); \ else \ collectSuppressibleFeature(Feature::FeatureName, operation); \ } #define CONDITIONALLY_SUPPRESSIBLE_LANGUAGE_FEATURE(FeatureName, SENumber, Description) \ - if (usesFeature##FeatureName(decl)) { \ - if (allowFeatureSuppression(#FeatureName, decl)) \ + if (CHECK(usesFeature##FeatureName)) { \ + if (CHECK_ARG(allowFeatureSuppression, #FeatureName)) \ collectSuppressibleFeature(Feature::FeatureName, operation); \ else \ collectRequiredFeature(Feature::FeatureName, operation); \ } #include "swift/Basic/Features.def" +#undef CHECK +#undef CHECK_ARG } FeatureSet swift::getUniqueFeaturesUsed(Decl *decl) { diff --git a/test/ModuleInterface/attrs.swift b/test/ModuleInterface/attrs.swift index f63db11059cb0..2e33b59183b78 100644 --- a/test/ModuleInterface/attrs.swift +++ b/test/ModuleInterface/attrs.swift @@ -1,6 +1,8 @@ -// RUN: %target-swift-emit-module-interface(%t.swiftinterface) %s -module-name attrs +// RUN: %target-swift-emit-module-interface(%t.swiftinterface) %s -module-name attrs -enable-experimental-feature ABIAttribute // RUN: %target-swift-typecheck-module-from-interface(%t.swiftinterface) -module-name attrs -// RUN: %FileCheck %s < %t.swiftinterface +// RUN: %FileCheck %s --input-file %t.swiftinterface + +// REQUIRES: swift_feature_ABIAttribute // CHECK: @_transparent public func glass() -> Swift.Int { return 0 }{{$}} @_transparent public func glass() -> Int { return 0 } @@ -27,3 +29,23 @@ public func someGenericFunction(_ t: T) -> Int { return 0 } internal func __specialize_someGenericFunction(_ t: T) -> Int { fatalError("don't call") } + +@abi(public func __abi__abiAttrOnFunction(param: Int)) +public func abiAttrOnFunction(param: Int) {} +// CHECK: #if {{.*}} $ABIAttribute +// CHECK: @abi(public func __abi__abiAttrOnFunction(param: Swift.Int)) +// CHECK: public func abiAttrOnFunction(param: Swift.Int) +// CHECK: #else +// CHECK: @_silgen_name("$s5attrs07__abi__B14AttrOnFunction5paramySi_tF") +// CHECK: public func abiAttrOnFunction(param: Swift.Int) +// CHECK: #endif + +@abi(public let __abi__abiAttrOnVar: Int) +public var abiAttrOnVar: Int = 42 +// CHECK: #if {{.*}} $ABIAttribute +// CHECK: @abi(public var __abi__abiAttrOnVar: Swift.Int) +// CHECK: public var abiAttrOnVar: Swift.Int +// CHECK: #else +// CHECK: @available(*, unavailable, message: "this compiler cannot match the ABI specified by the @abi attribute") +// CHECK: public var abiAttrOnVar: Swift.Int +// CHECK: #endif From 443bd5d4ad7758bf8890581cab38cc482eec8f7a Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Tue, 8 Oct 2024 11:44:12 -0700 Subject: [PATCH 12/15] Diagnose use of @_silgen_name and @abi together --- include/swift/AST/DiagnosticsSema.def | 5 +++++ lib/Sema/TypeCheckAttr.cpp | 5 +++++ test/attr/attr_abi.swift | 8 ++++++++ 3 files changed, 18 insertions(+) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index c237545a2809b..2b488f5f129bb 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8165,5 +8165,10 @@ ERROR(attr_abi_mismatched_var,none, "no match for %select{%kind0 in the ABI|ABI %kind0}1", (ValueDecl *, /*isABI=*/bool)) +ERROR(attr_abi_incompatible_with_silgen_name,none, + "cannot use '@_silgen_name' and '@abi' on the same %0 because they serve " + "the same purpose", + (DescriptiveDeclKind)) + #define UNDEFINE_DIAGNOSTIC_MACROS #include "DefineDiagnosticMacros.h" diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 01b091d8a0eaf..9431384a75bcc 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -2659,6 +2659,11 @@ void AttributeChecker::visitSILGenNameAttr(SILGenNameAttr *A) { diagnose(A->getLocation(), diag::reserved_runtime_symbol_name, A->Name); } + + if (D->getAttrs().hasAttribute()) { + diagnoseAndRemoveAttr(A, diag::attr_abi_incompatible_with_silgen_name, + D->getDescriptiveKind()); + } } void AttributeChecker::visitUsedAttr(UsedAttr *attr) { diff --git a/test/attr/attr_abi.swift b/test/attr/attr_abi.swift index e0fb3d63c3e63..a0f8a8e21ea8b 100644 --- a/test/attr/attr_abi.swift +++ b/test/attr/attr_abi.swift @@ -124,6 +124,14 @@ func async01() async {} @abi(func async11() async) func async11() async {} +// +// Miscellaneous function checking +// + +@_silgen_name("conflictingAttrsSilgenName") +@abi(func conflictingAttrsABI()) +func conflictingAttrsAPI() {} // expected-error@-2 {{cannot use '@_silgen_name' and '@abi' on the same global function because they serve the same purpose}} {{1-44=}} + // // PBD shape checking // From 201ccaf0539883d034026ef6c592c7945df9eee3 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Thu, 12 Dec 2024 17:39:41 -0800 Subject: [PATCH 13/15] [NFC] [ASTGen] Refactor attribute attachment Use `Decl::attachParsedAttrs()` instead of `Decl::setAttrs()` to attach attributes to a declaration in ASTGen. This causes the common attribute-setup logic there to be run. NFC in this commit because none of the attributes that have special setup logic are currently implemented in ASTGen. Prepares to add support for `@abi` in a future commit. --- include/swift/AST/ASTBridging.h | 4 +-- lib/AST/Bridging/DeclBridging.cpp | 7 +++--- lib/ASTGen/Sources/ASTGen/Decls.swift | 36 +++++++++++++-------------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/include/swift/AST/ASTBridging.h b/include/swift/AST/ASTBridging.h index 972b22ff99c0e..a31d33f45a214 100644 --- a/include/swift/AST/ASTBridging.h +++ b/include/swift/AST/ASTBridging.h @@ -918,8 +918,8 @@ BridgedUnavailableFromAsyncAttr BridgedUnavailableFromAsyncAttr_createParsed( struct BridgedFingerprint; -SWIFT_NAME("BridgedDecl.setAttrs(self:_:)") -void BridgedDecl_setAttrs(BridgedDecl decl, BridgedDeclAttributes attrs); +SWIFT_NAME("BridgedDecl.attachParsedAttrs(self:_:)") +void BridgedDecl_attachParsedAttrs(BridgedDecl decl, BridgedDeclAttributes attrs); enum ENUM_EXTENSIBILITY_ATTR(closed) BridgedStaticSpelling { BridgedStaticSpellingNone, diff --git a/lib/AST/Bridging/DeclBridging.cpp b/lib/AST/Bridging/DeclBridging.cpp index f1ed286f34b17..3716c01eeddf3 100644 --- a/lib/AST/Bridging/DeclBridging.cpp +++ b/lib/AST/Bridging/DeclBridging.cpp @@ -115,8 +115,9 @@ static AccessorKind unbridged(BridgedAccessorKind kind) { return static_cast(kind); } -void BridgedDecl_setAttrs(BridgedDecl decl, BridgedDeclAttributes attrs) { - decl.unbridged()->getAttrs() = attrs.unbridged(); +void BridgedDecl_attachParsedAttrs(BridgedDecl decl, + BridgedDeclAttributes attrs) { + decl.unbridged()->attachParsedAttrs(attrs.unbridged()); } BridgedAccessorDecl BridgedAccessorDecl_createParsed( @@ -146,7 +147,7 @@ BridgedPatternBindingDecl BridgedPatternBindingDecl_createParsed( // Configure all vars. pattern->forEachVariable([&](VarDecl *VD) { - VD->getAttrs() = cAttrs.unbridged(); + VD->attachParsedAttrs(cAttrs.unbridged()); VD->setStatic(isStatic); VD->setIntroducer(introducer); }); diff --git a/lib/ASTGen/Sources/ASTGen/Decls.swift b/lib/ASTGen/Sources/ASTGen/Decls.swift index 19abd40645b06..166648045c774 100644 --- a/lib/ASTGen/Sources/ASTGen/Decls.swift +++ b/lib/ASTGen/Sources/ASTGen/Decls.swift @@ -105,7 +105,7 @@ extension ASTGenVisitor { underlyingType: self.generate(type: node.initializer.value), genericWhereClause: self.generate(genericWhereClause: node.genericWhereClause) ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) return decl } @@ -129,7 +129,7 @@ extension ASTGenVisitor { end: node.memberBlock.rightBrace ) ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) let members = self.withDeclContext(decl.asDeclContext) { self.generate(memberBlockItemList: node.memberBlock.members) @@ -163,7 +163,7 @@ extension ASTGenVisitor { end: node.memberBlock.rightBrace ) ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) let members = self.withDeclContext(decl.asDeclContext) { self.generate(memberBlockItemList: node.memberBlock.members) @@ -198,7 +198,7 @@ extension ASTGenVisitor { ), isActor: false ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) let members = self.withDeclContext(decl.asDeclContext) { self.generate(memberBlockItemList: node.memberBlock.members) @@ -233,7 +233,7 @@ extension ASTGenVisitor { ), isActor: true ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) let members = self.withDeclContext(decl.asDeclContext) { self.generate(memberBlockItemList: node.memberBlock.members) @@ -270,7 +270,7 @@ extension ASTGenVisitor { end: node.memberBlock.rightBrace ) ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) let members = self.withDeclContext(decl.asDeclContext) { self.generate(memberBlockItemList: node.memberBlock.members) @@ -300,7 +300,7 @@ extension ASTGenVisitor { defaultType: self.generate(type: node.initializer?.value), genericWhereClause: self.generate(genericWhereClause: node.genericWhereClause) ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) return decl } } @@ -322,7 +322,7 @@ extension ASTGenVisitor { end: node.memberBlock.rightBrace ) ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) let members = self.withDeclContext(decl.asDeclContext) { self.generate(memberBlockItemList: node.memberBlock.members) @@ -364,7 +364,7 @@ extension ASTGenVisitor { guard let elemDecl = self.generate(enumCaseElement: elem) else { return nil } - elemDecl.asDecl.setAttrs(attrs.attributes) + elemDecl.asDecl.attachParsedAttrs(attrs.attributes) return elemDecl }) return .createParsed( @@ -439,7 +439,7 @@ extension ASTGenVisitor { throwsSpecifierLoc: self.generateSourceLoc(node.effectSpecifiers?.throwsClause), thrownType: self.generate(type: node.effectSpecifiers?.thrownError) ) - accessor.asDecl.setAttrs(attrs) + accessor.asDecl.attachParsedAttrs(attrs) if let body = node.body { self.withDeclContext(accessor.asDeclContext) { accessor.setParsedBody(self.generate(codeBlock: body)) @@ -600,7 +600,7 @@ extension ASTGenVisitor { arrowLoc: self.generateSourceLoc(node.returnClause.arrow), returnType: self.generate(type: node.returnClause.type) ) - subscriptDecl.asDecl.setAttrs(attrs.attributes) + subscriptDecl.asDecl.attachParsedAttrs(attrs.attributes) if let accessors = node.accessorBlock { let storage = subscriptDecl.asAbstractStorageDecl @@ -635,7 +635,7 @@ extension ASTGenVisitor { returnType: self.generate(type: node.signature.returnClause?.type), genericWhereClause: self.generate(genericWhereClause: node.genericWhereClause) ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) if let body = node.body { self.withDeclContext(decl.asDeclContext) { @@ -662,7 +662,7 @@ extension ASTGenVisitor { thrownType: self.generate(type: node.signature.effectSpecifiers?.thrownError), genericWhereClause: self.generate(genericWhereClause: node.genericWhereClause) ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) if let body = node.body { self.withDeclContext(decl.asDeclContext) { @@ -681,7 +681,7 @@ extension ASTGenVisitor { declContext: self.declContext, deinitKeywordLoc: self.generateSourceLoc(node.deinitKeyword) ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) if let body = node.body { self.withDeclContext(decl.asDeclContext) { @@ -711,7 +711,7 @@ extension ASTGenVisitor { resultType: self.generate(type: node.signature.returnClause?.type), definition: self.generate(expr: node.definition?.value) ) - decl.asDecl.setAttrs(attrs.attributes); + decl.asDecl.attachParsedAttrs(attrs.attributes) return decl; } } @@ -733,7 +733,7 @@ extension ASTGenVisitor { rightAngleLoc: info.rightAngleLoc, args: info.arguments ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) return decl } @@ -890,7 +890,7 @@ extension ASTGenVisitor { lowerThanNames: self.generate(precedenceGroupNameList: body.lowerThanRelation?.precedenceGroups), rightBraceLoc: self.generateSourceLoc(node.rightBrace) ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) return decl } } @@ -937,7 +937,7 @@ extension ASTGenVisitor { self.generateLocatedIdentifier($0.name) }.bridgedArray(in: self) ) - decl.asDecl.setAttrs(attrs.attributes) + decl.asDecl.attachParsedAttrs(attrs.attributes) return decl } } From 9c1e00ce52ecb23b6282ff90ccb39b6fb377dd5d Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Wed, 11 Dec 2024 17:47:07 -0800 Subject: [PATCH 14/15] [ASTGen] Lower `@abi` in ASTGen --- include/swift/AST/ASTBridging.h | 5 +++ include/swift/AST/ASTBridgingWrappers.def | 2 +- lib/AST/Bridging/DeclAttributeBridging.cpp | 10 +++++ lib/ASTGen/Sources/ASTGen/Bridge.swift | 4 ++ lib/ASTGen/Sources/ASTGen/DeclAttrs.swift | 43 ++++++++++++++++++++-- lib/ASTGen/Sources/ASTGen/SourceFile.swift | 1 + test/ASTGen/attrs.swift | 12 ++++-- test/attr/feature_requirement.swift | 2 +- 8 files changed, 70 insertions(+), 9 deletions(-) diff --git a/include/swift/AST/ASTBridging.h b/include/swift/AST/ASTBridging.h index a31d33f45a214..4785653b6b689 100644 --- a/include/swift/AST/ASTBridging.h +++ b/include/swift/AST/ASTBridging.h @@ -581,6 +581,11 @@ BridgedDeclAttribute BridgedDeclAttribute_createSimple( BridgedASTContext cContext, BridgedDeclAttrKind cKind, BridgedSourceLoc cAtLoc, BridgedSourceLoc cNameLoc); +SWIFT_NAME("BridgedABIAttr.createParsed(_:atLoc:range:abiDecl:)") +BridgedABIAttr BridgedABIAttr_createParsed( + BridgedASTContext cContext, BridgedSourceLoc atLoc, + BridgedSourceRange range, BridgedNullableDecl abiDecl); + enum ENUM_EXTENSIBILITY_ATTR(closed) BridgedAccessLevel { BridgedAccessLevelPrivate, BridgedAccessLevelFilePrivate, diff --git a/include/swift/AST/ASTBridgingWrappers.def b/include/swift/AST/ASTBridgingWrappers.def index 320fcea135a81..7057a75d47eb2 100644 --- a/include/swift/AST/ASTBridgingWrappers.def +++ b/include/swift/AST/ASTBridgingWrappers.def @@ -68,7 +68,7 @@ // Some of the base classes need to be nullable to allow them to be used as // optional parameters. -AST_BRIDGING_WRAPPER_NONNULL(Decl) +AST_BRIDGING_WRAPPER_NULLABLE(Decl) AST_BRIDGING_WRAPPER_NONNULL(DeclContext) AST_BRIDGING_WRAPPER_NONNULL(SourceFile) AST_BRIDGING_WRAPPER_NULLABLE(Stmt) diff --git a/lib/AST/Bridging/DeclAttributeBridging.cpp b/lib/AST/Bridging/DeclAttributeBridging.cpp index dc80369bc877a..382f5d2835721 100644 --- a/lib/AST/Bridging/DeclAttributeBridging.cpp +++ b/lib/AST/Bridging/DeclAttributeBridging.cpp @@ -93,6 +93,16 @@ static std::optional unbridge(BridgedAccessLevel level) { llvm_unreachable("unhandled BridgedAccessLevel"); } +BridgedABIAttr BridgedABIAttr_createParsed(BridgedASTContext cContext, + BridgedSourceLoc atLoc, + BridgedSourceRange range, + BridgedNullableDecl abiDecl) { + return new (cContext.unbridged()) ABIAttr(abiDecl.unbridged(), + atLoc.unbridged(), + range.unbridged(), + /*isImplicit=*/false); +} + BridgedAccessControlAttr BridgedAccessControlAttr_createParsed(BridgedASTContext cContext, BridgedSourceRange cRange, diff --git a/lib/ASTGen/Sources/ASTGen/Bridge.swift b/lib/ASTGen/Sources/ASTGen/Bridge.swift index 5124be615e2ac..89b1a3654983c 100644 --- a/lib/ASTGen/Sources/ASTGen/Bridge.swift +++ b/lib/ASTGen/Sources/ASTGen/Bridge.swift @@ -26,6 +26,7 @@ extension BridgedNullable { extension BridgedSourceLoc: /*@retroactive*/ swiftASTGen.BridgedNullable {} extension BridgedIdentifier: /*@retroactive*/ swiftASTGen.BridgedNullable {} +extension BridgedNullableDecl: /*@retroactive*/ swiftASTGen.BridgedNullable {} extension BridgedNullableExpr: /*@retroactive*/ swiftASTGen.BridgedNullable {} extension BridgedNullableStmt: /*@retroactive*/ swiftASTGen.BridgedNullable {} extension BridgedNullableTypeRepr: /*@retroactive*/ swiftASTGen.BridgedNullable {} @@ -60,6 +61,9 @@ extension Optional where Wrapped: BridgedHasNullable { extension BridgedStmt: BridgedHasNullable { typealias Nullable = BridgedNullableStmt } +extension BridgedDecl: BridgedHasNullable { + typealias Nullable = BridgedNullableDecl +} extension BridgedExpr: BridgedHasNullable { typealias Nullable = BridgedNullableExpr } diff --git a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift index dfac6a0e7f2e2..90b5b8d0282b5 100644 --- a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift +++ b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift @@ -112,7 +112,7 @@ extension ASTGenVisitor { let attrKind = BridgedDeclAttrKind(from: attrName.bridged) switch attrKind { case .ABI: - return self.generateABIAttr(attribute: node)?.asDeclAttribute + return handle(self.generateABIAttr(attribute: node)?.asDeclAttribute) case .alignment: return handle(self.generateAlignmentAttr(attribute: node)?.asDeclAttribute) case .allowFeatureSuppression: @@ -335,8 +335,45 @@ extension ASTGenVisitor { /// @abi(func fn()) /// ``` func generateABIAttr(attribute node: AttributeSyntax) -> BridgedABIAttr? { - #warning("TODO: implement generateABIAttr") - fatalError("TODO: implement generateABIAttr") + guard + let arg = node.arguments?.as(ABIAttributeArgumentsSyntax.self) + else { + // TODO: diagnose + return nil + } + + let abiDecl: BridgedDecl? + switch arg.provider { + case .associatedType(let assocTyDecl): + abiDecl = self.generate(associatedTypeDecl: assocTyDecl)?.asDecl + case .deinitializer(let deinitDecl): + abiDecl = self.generate(deinitializerDecl: deinitDecl).asDecl + case .enumCase(let caseDecl): + abiDecl = self.generate(enumCaseDecl: caseDecl).asDecl + case .function(let funcDecl): + abiDecl = self.generate(functionDecl: funcDecl)?.asDecl + case .initializer(let initDecl): + abiDecl = self.generate(initializerDecl: initDecl).asDecl + case .`subscript`(let subscriptDecl): + abiDecl = self.generate(subscriptDecl: subscriptDecl).asDecl + case .typeAlias(let typealiasDecl): + abiDecl = self.generate(typeAliasDecl: typealiasDecl)?.asDecl + case .variable(let varDecl): + abiDecl = self.generate(variableDecl: varDecl).asDecl + case .missing(_): + // This error condition will have been diagnosed in SwiftSyntax. + abiDecl = nil + } + + // TODO: Diagnose if `abiDecl` has a body/initial value/etc. + // The C++ parser considers it syntactically invalid but SwiftSyntax does not. + + return .createParsed( + self.ctx, + atLoc: self.generateSourceLoc(node.atSign), + range: self.generateAttrSourceRange(node), + abiDecl: abiDecl.asNullable + ) } /// E.g.: diff --git a/lib/ASTGen/Sources/ASTGen/SourceFile.swift b/lib/ASTGen/Sources/ASTGen/SourceFile.swift index ba0036fa2b2d0..4529723a1518f 100644 --- a/lib/ASTGen/Sources/ASTGen/SourceFile.swift +++ b/lib/ASTGen/Sources/ASTGen/SourceFile.swift @@ -76,6 +76,7 @@ extension Parser.ExperimentalFeatures { mapFeature(.TrailingComma, to: .trailingComma) mapFeature(.CoroutineAccessors, to: .coroutineAccessors) mapFeature(.ValueGenerics, to: .valueGenerics) + mapFeature(.ABIAttribute, to: .abiAttribute) } } diff --git a/test/ASTGen/attrs.swift b/test/ASTGen/attrs.swift index 813ff884e0540..cb7c6ed5a7182 100644 --- a/test/ASTGen/attrs.swift +++ b/test/ASTGen/attrs.swift @@ -1,6 +1,6 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend %s -dump-parse -disable-availability-checking -enable-experimental-feature SymbolLinkageMarkers -enable-experimental-feature Extern -enable-experimental-move-only -enable-experimental-feature ParserASTGen > %t/astgen.ast.raw -// RUN: %target-swift-frontend %s -dump-parse -disable-availability-checking -enable-experimental-feature SymbolLinkageMarkers -enable-experimental-feature Extern -enable-experimental-move-only > %t/cpp-parser.ast.raw +// RUN: %target-swift-frontend %s -dump-parse -disable-availability-checking -enable-experimental-feature SymbolLinkageMarkers -enable-experimental-feature ABIAttribute -enable-experimental-feature Extern -enable-experimental-move-only -enable-experimental-feature ParserASTGen > %t/astgen.ast.raw +// RUN: %target-swift-frontend %s -dump-parse -disable-availability-checking -enable-experimental-feature SymbolLinkageMarkers -enable-experimental-feature ABIAttribute -enable-experimental-feature Extern -enable-experimental-move-only > %t/cpp-parser.ast.raw // Filter out any addresses in the dump, since they can differ. // RUN: sed -E 's#0x[0-9a-fA-F]+##g' %t/cpp-parser.ast.raw > %t/cpp-parser.ast @@ -8,13 +8,14 @@ // RUN: %diff -u %t/astgen.ast %t/cpp-parser.ast -// RUN: %target-typecheck-verify-swift -enable-experimental-feature SymbolLinkageMarkers -enable-experimental-feature Extern -enable-experimental-move-only -enable-experimental-feature ParserASTGen +// RUN: %target-typecheck-verify-swift -enable-experimental-feature SymbolLinkageMarkers -enable-experimental-feature ABIAttribute -enable-experimental-feature Extern -enable-experimental-move-only -enable-experimental-feature ParserASTGen // REQUIRES: executable_test // REQUIRES: swift_swift_parser // REQUIRES: swift_feature_SymbolLinkageMarkers // REQUIRES: swift_feature_Extern // REQUIRES: swift_feature_ParserASTGen +// REQUIRES: swift_feature_ABIAttribute // rdar://116686158 // UNSUPPORTED: asan @@ -62,7 +63,10 @@ struct S4 {} @implementation extension ObjCClass1 {} // expected-error {{cannot find type 'ObjCClass1' in scope}} @implementation(Category) extension ObjCClass1 {} // expected-error {{cannot find type 'ObjCClass1' in scope}} -@_alignment(8) struct AnyAlignment {} +@abi(func fn_abi()) // expected-error {{cannot give global function 'fn' the ABI of a global function with a different number of low-level parameters}} +func fn(_: Int) {} + +@_alignment(8) struct AnyAlignment {} @_allowFeatureSuppression(IsolatedAny) public func testFeatureSuppression(fn: @isolated(any) @Sendable () -> ()) {} diff --git a/test/attr/feature_requirement.swift b/test/attr/feature_requirement.swift index f0f0d7d413d5f..f568a707b91ad 100644 --- a/test/attr/feature_requirement.swift +++ b/test/attr/feature_requirement.swift @@ -1,4 +1,4 @@ -// RUN: %target-typecheck-verify-swift -parse-as-library -verify-additional-prefix disabled- +// RUN: %target-typecheck-verify-swift -parse-as-library -disable-experimental-parser-round-trip -verify-additional-prefix disabled- // RUN: %target-typecheck-verify-swift -parse-as-library -verify-additional-prefix enabled- -enable-experimental-feature ABIAttribute // REQUIRES: asserts From 413c673da4128f8a10df200899647a609c91fec1 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Thu, 19 Dec 2024 21:22:08 -0800 Subject: [PATCH 15/15] [NFC] Requestify ABI role computation --- include/swift/AST/ASTContext.h | 13 ----- include/swift/AST/Decl.h | 6 +++ include/swift/AST/NameLookupRequests.h | 27 ++++++++++ include/swift/AST/NameLookupTypeIDZone.def | 2 + lib/AST/Attr.cpp | 24 --------- lib/AST/Decl.cpp | 61 +++++++++++++++++++--- lib/Serialization/Deserialization.cpp | 2 +- 7 files changed, 90 insertions(+), 45 deletions(-) diff --git a/include/swift/AST/ASTContext.h b/include/swift/AST/ASTContext.h index a1faec2df4126..d0904f6f310f3 100644 --- a/include/swift/AST/ASTContext.h +++ b/include/swift/AST/ASTContext.h @@ -415,19 +415,6 @@ class ASTContext final { llvm::SmallPtrSet> DerivativeAttrs; - /// For each ABI-only declaration (created with the `@abi` attribute), points - /// to its API-only counterpart. That is, this table maps each - /// `ABIAttr::abiDecl` to the declaration the `ABIAttr` is attached to. - /// - /// \seeAlso \c recordABIAttr() - llvm::DenseMap ABIDeclCounterparts; - - /// Register \c this->abiDecl 's relationship with \p owner in - /// `ABIDeclCounterparts` . This is necessary for - /// \c ABIRoleInfo::ABIRoleInfo() to determine that \c this->abiDecl - /// is ABI-only and locate its API counterpart. - void recordABIAttr(ABIAttr *attr, Decl *owner); - /// The Swift module currently being compiled. ModuleDecl *MainModule = nullptr; diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 2d0b3321d5650..eb4425f66cd00 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -1023,6 +1023,12 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated, public Swi /// attribute macro expansion. DeclAttributes getSemanticAttrs() const; + /// Register the relationship between \c this and \p attr->abiDecl , assuming + /// that \p attr is attached to \c this . This is necessary for + /// \c ABIRoleInfo::ABIRoleInfo() to determine that \c attr->abiDecl + /// is ABI-only and locate its API counterpart. + void recordABIAttr(ABIAttr *attr); + /// Set this declaration's attributes to the specified attribute list, /// applying any post-processing logic appropriate for attributes parsed /// from source code. diff --git a/include/swift/AST/NameLookupRequests.h b/include/swift/AST/NameLookupRequests.h index 4572a79475471..36fe7b6974dbe 100644 --- a/include/swift/AST/NameLookupRequests.h +++ b/include/swift/AST/NameLookupRequests.h @@ -25,6 +25,7 @@ #include "swift/AST/TypeOrExtensionDecl.h" #include "swift/Basic/Statistic.h" #include "llvm/ADT/Hashing.h" +#include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/TinyPtrVector.h" namespace swift { @@ -1002,6 +1003,32 @@ class ObjCCategoryNameMapRequest bool isCached() const { return true; } }; +struct DeclABIRoleInfoResult { + llvm::PointerIntPair storage; + DeclABIRoleInfoResult(Decl *counterpart, uint8_t roleValue) + : storage(counterpart, roleValue) {} +}; + +/// Return the ABI role info (role and counterpart) of a given declaration. +class DeclABIRoleInfoRequest + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + + void recordABIOnly(Evaluator &evaluator, Decl *counterpart); + +private: + friend SimpleRequest; + + // Evaluation. + DeclABIRoleInfoResult evaluate(Evaluator &evaluator, Decl *decl) const; + +public: + bool isCached() const { return true; } +}; + #define SWIFT_TYPEID_ZONE NameLookup #define SWIFT_TYPEID_HEADER "swift/AST/NameLookupTypeIDZone.def" #include "swift/Basic/DefineTypeIDZone.h" diff --git a/include/swift/AST/NameLookupTypeIDZone.def b/include/swift/AST/NameLookupTypeIDZone.def index f358767f5c16c..a025901ece813 100644 --- a/include/swift/AST/NameLookupTypeIDZone.def +++ b/include/swift/AST/NameLookupTypeIDZone.def @@ -118,3 +118,5 @@ SWIFT_REQUEST(NameLookup, LookupIntrinsicRequest, FuncDecl *(ModuleDecl *, Identifier), Cached, NoLocationInfo) SWIFT_REQUEST(NameLookup, ObjCCategoryNameMapRequest, ObjCCategoryNameMap(ClassDecl *, ExtensionDecl *), Cached, NoLocationInfo) +SWIFT_REQUEST(NameLookup, DeclABIRoleInfoRequest, + DeclABIRoleInfoResult(Decl *), Cached, NoLocationInfo) diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index 8e761d400b52f..8cab833be1d82 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -2899,30 +2899,6 @@ LifetimeAttr *LifetimeAttr::create(ASTContext &context, SourceLoc atLoc, return new (context) LifetimeAttr(atLoc, baseRange, implicit, entry); } -void ASTContext::recordABIAttr(ABIAttr *attr, Decl *owner) { - // The ABIAttr on a VarDecl ought to point to its PBD. - if (auto VD = dyn_cast(owner)) { - if (auto PBD = VD->getParentPatternBinding()) { - owner = PBD; - } - } - - auto recordABIDecl = [&](Decl *decl) { - ABIDeclCounterparts.insert({ decl, owner }); - }; - - if (auto abiPBD = dyn_cast(attr->abiDecl)) { - // Add to *every* VarDecl in the ABI PBD, even ones that don't properly - // match anything in the API PBD. - for (auto i : range(abiPBD->getNumPatternEntries())) { - abiPBD->getPattern(i)->forEachVariable(recordABIDecl); - } - return; - } - - recordABIDecl(attr->abiDecl); -} - void swift::simple_display(llvm::raw_ostream &out, const DeclAttribute *attr) { if (attr) attr->print(out); diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 6088c17d96343..2c07a93177885 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -416,7 +416,7 @@ void Decl::attachParsedAttrs(DeclAttributes attrs) { for (auto *attr : attrs.getAttributes()) attr->setOriginalDeclaration(this); for (auto attr : attrs.getAttributes()) - this->getASTContext().recordABIAttr(attr, this); + recordABIAttr(attr); getAttrs() = attrs; } @@ -4271,10 +4271,47 @@ void ValueDecl::setRenamedDecl(const AvailableAttr *attr, std::move(renameDecl)); } -abi_role_detail::Storage abi_role_detail::computeStorage(Decl *decl) { - ASSERT(decl); +void Decl::recordABIAttr(ABIAttr *attr) { + Decl *owner = this; - auto &ctx = decl->getASTContext(); + // The ABIAttr on a VarDecl ought to point to its PBD. + if (auto VD = dyn_cast(owner)) { + if (auto PBD = VD->getParentPatternBinding()) { + owner = PBD; + } + } + + auto record = [&](Decl *decl) { + auto &evaluator = owner->getASTContext().evaluator; + DeclABIRoleInfoRequest(decl).recordABIOnly(evaluator, owner); + }; + + if (auto abiPBD = dyn_cast(attr->abiDecl)) { + // Add to *every* VarDecl in the ABI PBD, even ones that don't properly + // match anything in the API PBD. + for (auto i : range(abiPBD->getNumPatternEntries())) { + abiPBD->getPattern(i)->forEachVariable(record); + } + return; + } + + record(attr->abiDecl); +} + +void DeclABIRoleInfoRequest::recordABIOnly(Evaluator &evaluator, + Decl *counterpart) { + if (evaluator.hasCachedResult(*this)) + return; + DeclABIRoleInfoResult result{counterpart, ABIRole::ProvidesABI}; + evaluator.cacheOutput(*this, std::move(result)); +} + +DeclABIRoleInfoResult +DeclABIRoleInfoRequest::evaluate(Evaluator &evaluator, Decl *decl) const { + // NOTE: ABI decl -> API decl is manually cached through `recordABIOnly()`, + // so this code does not have to handle that case. + + ASSERT(decl); Decl *counterpartDecl = decl; ABIRole::Value flags = ABIRole::Either; @@ -4282,11 +4319,21 @@ abi_role_detail::Storage abi_role_detail::computeStorage(Decl *decl) { if (auto attr = decl->getAttrs().getAttribute()) { flags = ABIRole::ProvidesAPI; counterpartDecl = attr->abiDecl; - } else if (auto inverse = ctx.ABIDeclCounterparts.lookup(decl)) { - flags = ABIRole::ProvidesABI; - counterpartDecl = inverse; } + return DeclABIRoleInfoResult(counterpartDecl, (uint8_t)flags); +} + +abi_role_detail::Storage abi_role_detail::computeStorage(Decl *decl) { + ASSERT(decl); + + auto &ctx = decl->getASTContext(); + + auto result = evaluateOrDefault(ctx.evaluator, DeclABIRoleInfoRequest{decl}, + { decl, ABIRole::Either }); + Decl *counterpartDecl = result.storage.getPointer(); + auto flags = (ABIRole::Value)result.storage.getInt(); + // If we did find an `@abi` attribute, resolve PBD pointers to their VarDecl. if (flags != ABIRole::Either) { if (auto VD = dyn_cast(decl)) diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index b996df4ad7dcb..9fcc0c306d5b6 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -6513,7 +6513,7 @@ llvm::Error DeclDeserializer::finishRecursiveAttrs(Decl *decl, DeclAttribute *at if (!abiDeclOrError) return abiDeclOrError.takeError(); unresolvedABIAttr->first->abiDecl = abiDeclOrError.get(); - ctx.recordABIAttr(unresolvedABIAttr->first, decl); + decl->recordABIAttr(unresolvedABIAttr->first); } if (ABIDeclCounterpartID != 0) { // This decl is the `abiDecl` of an `ABIAttr`. Force the decl that `ABIAttr`