diff --git a/docs/ReferenceGuides/UnderscoredAttributes.md b/docs/ReferenceGuides/UnderscoredAttributes.md index a702e1ebe06dc..74774549da9c1 100644 --- a/docs/ReferenceGuides/UnderscoredAttributes.md +++ b/docs/ReferenceGuides/UnderscoredAttributes.md @@ -437,6 +437,18 @@ the export name. It's the equivalent of clang's `__attribute__((export_name))`. +## `@_extern()` + +Indicates that a particular declaration should be imported +from the external environment. + +### `@_extern(wasm, module: <"moduleName">, name: <"fieldName">)` + +Indicates that a particular declaration should be imported +through WebAssembly's import interface. + +It's the equivalent of clang's `__attribute__((import_module("module"), import_name("field")))`. + ## `@_fixed_layout` Same as `@frozen` but also works for classes. diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index 46b7756f2cf7f..935302ec4b5a9 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -418,6 +418,9 @@ DECL_ATTR(_section, Section, DECL_ATTR(_rawLayout, RawLayout, OnStruct | UserInaccessible | ABIBreakingToAdd | ABIBreakingToRemove | APIStableToAdd | APIStableToRemove, 146) +DECL_ATTR(_extern, Extern, + OnFunc | ABIStableToAdd | ABIBreakingToRemove | APIStableToAdd | APIStableToRemove, + 147) CONTEXTUAL_SIMPLE_DECL_ATTR(final, Final, OnClass | OnFunc | OnAccessor | OnVar | OnSubscript | DeclModifier | ABIBreakingToAdd | ABIBreakingToRemove | APIStableToAdd | APIStableToRemove, 2) diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index ee651eeb965d7..e7de0f14a0635 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -2335,6 +2335,28 @@ class ExposeAttr : public DeclAttribute { } }; +/// Define the `@_extern` attribute, used to import external declarations in +/// the specified way to interoperate with Swift. +class ExternAttr : public DeclAttribute { +public: + ExternAttr(StringRef ModuleName, StringRef Name, SourceLoc AtLoc, SourceRange Range, bool Implicit) + : DeclAttribute(DAK_Extern, AtLoc, Range, Implicit), + ModuleName(ModuleName), Name(Name) {} + + ExternAttr(StringRef ModuleName, StringRef Name, bool Implicit) + : ExternAttr(ModuleName, Name, SourceLoc(), SourceRange(), Implicit) {} + + /// The module name to import the named declaration in it + const StringRef ModuleName; + + /// The declaration name to import + const StringRef Name; + + static bool classof(const DeclAttribute *DA) { + return DA->getKind() == DAK_Extern; + } +}; + /// The `@_documentation(...)` attribute, used to override a symbol's visibility /// in symbol graphs, and/or adding arbitrary metadata to it. class DocumentationAttr: public DeclAttribute { diff --git a/include/swift/AST/DiagnosticsCommon.def b/include/swift/AST/DiagnosticsCommon.def index d3623cd6412fc..63bdee0da4d99 100644 --- a/include/swift/AST/DiagnosticsCommon.def +++ b/include/swift/AST/DiagnosticsCommon.def @@ -84,6 +84,9 @@ ERROR(require_const_initializer_for_const,none, ERROR(func_decl_without_brace,PointsToFirstBadToken, "expected '{' in body of function declaration", ()) +ERROR(func_decl_no_body_expected,PointsToFirstBadToken, + "unexpected body of function declaration", ()) + NOTE(convert_let_to_var,none, "change 'let' to 'var' to make it mutable", ()) diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index 031eb93099241..ca2feefe432f9 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -1867,6 +1867,8 @@ ERROR(attr_rawlayout_expected_integer_count,none, ERROR(attr_rawlayout_expected_params,none, "expected %1 argument after %0 argument in @_rawLayout attribute", (StringRef, StringRef)) +ERROR(attr_extern_expected_label,none, + "expected %0 argument to @_extern attribute", (StringRef)) //------------------------------------------------------------------------------ // MARK: Generics parsing diagnostics //------------------------------------------------------------------------------ diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 602c1d1b9aee0..dc35ec2843193 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -1866,6 +1866,10 @@ ERROR(section_not_at_top_level,none, ERROR(section_empty_name,none, "@_section section name cannot be empty", ()) +// @_extern +ERROR(extern_not_at_top_level_func,none, + "@_extern attribute can only be applied to global functions", ()) + ERROR(expose_wasm_not_at_top_level_func,none, "@_expose attribute with 'wasm' can only be applied to global functions", ()) diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index b3b641b4f24cb..4ade91345d961 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -1095,6 +1095,10 @@ class Parser { ParserResult parseDifferentiableAttribute(SourceLoc AtLoc, SourceLoc Loc); + /// Parse the @_extern attribute. + bool parseExternAttribute(DeclAttributes &Attributes, bool &DiscardAttribute, + StringRef AttrName, SourceLoc AtLoc, SourceLoc Loc); + /// Parse the arguments inside the @differentiable attribute. bool parseDifferentiableAttributeArguments( DifferentiabilityKind &diffKind, diff --git a/include/swift/SIL/SILFunction.h b/include/swift/SIL/SILFunction.h index 08b62f23ba66a..76543349fd149 100644 --- a/include/swift/SIL/SILFunction.h +++ b/include/swift/SIL/SILFunction.h @@ -303,6 +303,9 @@ class SILFunction /// empty. StringRef WasmExportName; + /// Name of a Wasm import module and field if @_extern(wasm) attribute + llvm::Optional> WasmImportModuleAndField; + /// Has value if there's a profile for this function /// Contains Function Entry Count ProfileCounter EntryCount; @@ -1278,6 +1281,24 @@ class SILFunction StringRef wasmExportName() const { return WasmExportName; } void setWasmExportName(StringRef value) { WasmExportName = value; } + /// Return Wasm import module name if @_extern(wasm) was used otherwise empty + StringRef wasmImportModuleName() const { + if (WasmImportModuleAndField) + return WasmImportModuleAndField->first; + return StringRef(); + } + + /// Return Wasm import field name if @_extern(wasm) was used otherwise empty + StringRef wasmImportFieldName() const { + if (WasmImportModuleAndField) + return WasmImportModuleAndField->second; + return StringRef(); + } + + void setWasmImportModuleAndField(StringRef module, StringRef field) { + WasmImportModuleAndField = std::make_pair(module, field); + } + /// Returns true if this function belongs to a declaration that returns /// an opaque result type with one or more availability conditions that are /// allowed to produce a different underlying type at runtime. diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index 7dc8ac394a0c8..6a642a5d295c4 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -1138,6 +1138,15 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options, Printer << ")"; break; } + + case DAK_Extern: { + auto *Attr = cast(this); + Printer.printAttrName("@_extern"); + // For now, it accepts only "wasm" as its kind. + Printer << "(wasm, module: \"" << Attr->ModuleName << "\", name: \"" << Attr->Name << "\")"; + break; + } + case DAK_Section: Printer.printAttrName("@_section"); Printer << "(\"" << cast(this)->Name << "\")"; @@ -1708,6 +1717,8 @@ StringRef DeclAttribute::getAttrName() const { } case DAK_RawLayout: return "_rawLayout"; + case DAK_Extern: + return "_extern"; } llvm_unreachable("bad DeclAttrKind"); } diff --git a/lib/IRGen/GenDecl.cpp b/lib/IRGen/GenDecl.cpp index 410d76d4f223f..8936b6d4b25a7 100644 --- a/lib/IRGen/GenDecl.cpp +++ b/lib/IRGen/GenDecl.cpp @@ -3577,11 +3577,18 @@ llvm::Function *IRGenModule::getAddrOfSILFunction( addUsedGlobal(fn); if (!f->section().empty()) fn->setSection(f->section()); + + llvm::AttrBuilder attrBuilder(getLLVMContext()); if (!f->wasmExportName().empty()) { - llvm::AttrBuilder attrBuilder(getLLVMContext()); attrBuilder.addAttribute("wasm-export-name", f->wasmExportName()); - fn->addFnAttrs(attrBuilder); } + if (!f->wasmImportFieldName().empty()) { + attrBuilder.addAttribute("wasm-import-name", f->wasmImportFieldName()); + } + if (!f->wasmImportModuleName().empty()) { + attrBuilder.addAttribute("wasm-import-module", f->wasmImportModuleName()); + } + fn->addFnAttrs(attrBuilder); // If `hasCReferences` is true, then the function is either marked with // @_silgen_name OR @_cdecl. If it is the latter, it must have a definition diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 28f7da278c162..23713d3d500e9 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -1431,6 +1431,83 @@ Parser::parseDifferentiableAttribute(SourceLoc atLoc, SourceLoc loc) { parameters, whereClause)); } +bool Parser::parseExternAttribute(DeclAttributes &Attributes, + bool &DiscardAttribute, StringRef AttrName, + SourceLoc AtLoc, SourceLoc Loc) { + + // Parse @_extern(, ...) + if (!consumeIf(tok::l_paren)) { + diagnose(Loc, diag::attr_expected_lparen, AttrName, + DeclAttribute::isDeclModifier(DAK_Extern)); + return false; + } + auto diagnoseExpectLanguage = [&]() { + diagnose(Tok.getLoc(), diag::attr_expected_option_such_as, AttrName, + "wasm"); + }; + if (Tok.isNot(tok::identifier)) { + diagnoseExpectLanguage(); + return false; + } + + if (Tok.getText() != "wasm") { + diagnoseExpectLanguage(); + DiscardAttribute = true; + } + consumeToken(tok::identifier); + + // Parse @_extern(wasm, module: "x", name: "y") + auto parseStringLiteralArgument = [&](StringRef fieldName, StringRef &fieldValue) { + if (!consumeIf(tok::comma) || Tok.isNot(tok::identifier) || Tok.getText() != fieldName) { + diagnose(Loc, diag::attr_extern_expected_label, fieldName); + return false; + } + consumeToken(tok::identifier); + + if (!consumeIf(tok::colon)) { + diagnose(Tok.getLoc(), diag::attr_expected_colon_after_label, fieldName); + return false; + } + + if (Tok.isNot(tok::string_literal)) { + diagnose(Loc, diag::attr_expected_string_literal, AttrName); + return false; + } + llvm::Optional importModuleName = + getStringLiteralIfNotInterpolated(Loc, ("'" + AttrName + "'").str()); + consumeToken(tok::string_literal); + + if (!importModuleName.has_value()) { + DiscardAttribute = true; + return false; + } + fieldValue = importModuleName.value(); + return true; + }; + + StringRef importModuleName, importName; + if (!parseStringLiteralArgument("module", importModuleName)) + DiscardAttribute = true; + + if (!parseStringLiteralArgument("name", importName)) + DiscardAttribute = true; + + if (!consumeIf(tok::r_paren)) { + diagnose(Loc, diag::attr_expected_rparen, AttrName, + DeclAttribute::isDeclModifier(DAK_Extern)); + return false; + } + + auto AttrRange = SourceRange(Loc, Tok.getLoc()); + + if (!DiscardAttribute) { + Attributes.add(new (Context) ExternAttr(importModuleName, importName, AtLoc, + AttrRange, + /*Implicit=*/false)); + } + return false; +} + // Attribute parsing error helper. // For the given parentheses depth, skip until ')' and consume it if possible. // If no ')' is found, produce error. @@ -3168,6 +3245,12 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes, break; } + case DAK_Extern: { + if (!parseExternAttribute(Attributes, DiscardAttribute, AttrName, AtLoc, Loc)) + return makeParserSuccess(); + break; + } + case DAK_Section: { if (!consumeIf(tok::l_paren)) { diagnose(Loc, diag::attr_expected_lparen, AttrName, diff --git a/lib/SIL/IR/SILFunctionBuilder.cpp b/lib/SIL/IR/SILFunctionBuilder.cpp index 2b1db93cb7aa3..65ecd9c548324 100644 --- a/lib/SIL/IR/SILFunctionBuilder.cpp +++ b/lib/SIL/IR/SILFunctionBuilder.cpp @@ -180,6 +180,10 @@ void SILFunctionBuilder::addFunctionAttributes( } } + if (auto *EA = Attrs.getAttribute()) { + F->setWasmImportModuleAndField(EA->ModuleName, EA->Name); + } + if (Attrs.hasAttribute()) F->setMarkedAsUsed(true); diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index e0a722250d6fe..f2168df898926 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -253,6 +253,7 @@ class AttributeChecker : public AttributeVisitor { void visitCDeclAttr(CDeclAttr *attr); void visitExposeAttr(ExposeAttr *attr); + void visitExternAttr(ExternAttr *attr); void visitUsedAttr(UsedAttr *attr); void visitSectionAttr(SectionAttr *attr); @@ -2070,6 +2071,13 @@ void AttributeChecker::visitExposeAttr(ExposeAttr *attr) { } } +void AttributeChecker::visitExternAttr(ExternAttr *attr) { + // Only top-level func decls are currently supported. + if (!isa(D) || D->getDeclContext()->isTypeContext()) { + diagnose(attr->getLocation(), diag::extern_not_at_top_level_func); + } +} + void AttributeChecker::visitUsedAttr(UsedAttr *attr) { if (!Ctx.LangOpts.hasFeature(Feature::SymbolLinkageMarkers)) { diagnoseAndRemoveAttr(attr, diag::section_linkage_markers_disabled); diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 3da9bce843d76..c6797b97f0657 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1511,6 +1511,7 @@ namespace { UNINTERESTING_ATTR(Inlinable) UNINTERESTING_ATTR(Effects) UNINTERESTING_ATTR(Expose) + UNINTERESTING_ATTR(Extern) UNINTERESTING_ATTR(Final) UNINTERESTING_ATTR(MoveOnly) UNINTERESTING_ATTR(FixedLayout) diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index bbc63c7075c15..2b1e3e718718f 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -3247,6 +3247,16 @@ class DeclChecker : public DeclVisitor { // PatternBindingDecl. } + /// 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; + } + /// Determine whether the given declaration requires a definition. /// /// Only valid for declarations that can have definitions, i.e., @@ -3264,6 +3274,7 @@ class DeclChecker : public DeclVisitor { // 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; @@ -3346,6 +3357,9 @@ class DeclChecker : public DeclVisitor { if (requiresDefinition(FD) && !FD->hasBody()) { // Complain if we should have a body. FD->diagnose(diag::func_decl_without_brace); + } else if (requiresNoDefinition(FD) && FD->hasBody()) { + // Complain if we have a body but shouldn't. + FD->diagnose(diag::func_decl_no_body_expected); } else if (FD->getDeclContext()->isLocalContext()) { // Check local function bodies right away. (void)FD->getTypecheckedBody(); diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 40d18e78e43d0..448d15899f196 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -5860,6 +5860,18 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() { break; } + case decls_block::Extern_DECL_ATTR: { + bool isImplicit; + unsigned moduleNameSize, declNameSize; + serialization::decls_block::ExternDeclAttrLayout::readRecord( + scratch, isImplicit, moduleNameSize, declNameSize); + StringRef moduleName = blobData.substr(0, moduleNameSize); + blobData = blobData.substr(moduleNameSize); + StringRef declName = blobData.substr(0, declNameSize); + Attr = new (ctx) ExternAttr(moduleName, declName, isImplicit); + break; + } + case decls_block::Documentation_DECL_ATTR: { bool isImplicit; uint64_t CategoryID; diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 9b58cdf2a8e06..675a2a3587848 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 = 811; // default argument isolation +const uint16_t SWIFTMODULE_VERSION_MINOR = 812; // @_extern(wasm) /// A standard hash seed used for all string hashes in a serialized module. /// @@ -2340,6 +2340,13 @@ namespace decls_block { BCBlob // declaration name >; + using ExternDeclAttrLayout = BCRecordLayout, // implicit flag + BCVBR<4>, // number of bytes in module name + BCVBR<4>, // number of bytes in name + BCBlob // module name and declaration name + >; + using DocumentationDeclAttrLayout = BCRecordLayout< Documentation_DECL_ATTR, BCFixed<1>, // implicit flag diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index a1f447d8ed246..31c0d3424d6a6 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "Serialization.h" +#include "ModuleFormat.h" #include "SILFormat.h" #include "swift/AST/ASTContext.h" #include "swift/AST/ASTMangler.h" @@ -3112,6 +3113,18 @@ class Serializer::DeclSerializer : public DeclVisitor { return; } + case DAK_Extern: { + auto *theAttr = cast(DA); + auto abbrCode = S.DeclTypeAbbrCodes[ExternDeclAttrLayout::Code]; + llvm::SmallString<32> blob; + blob.append(theAttr->ModuleName); + blob.append(theAttr->Name); + ExternDeclAttrLayout::emitRecord( + S.Out, S.ScratchRecord, abbrCode, theAttr->isImplicit(), + theAttr->ModuleName.size(), theAttr->Name.size(), blob); + return; + } + case DAK_Section: { auto *theAttr = cast(DA); auto abbrCode = S.DeclTypeAbbrCodes[SectionDeclAttrLayout::Code]; diff --git a/test/Interop/WebAssembly/extern-wasm-decls-to-swift.swift b/test/Interop/WebAssembly/extern-wasm-decls-to-swift.swift new file mode 100644 index 0000000000000..3f6cd7baab569 --- /dev/null +++ b/test/Interop/WebAssembly/extern-wasm-decls-to-swift.swift @@ -0,0 +1,26 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend %s -emit-ir -module-name Extern | %FileCheck %s + +// CHECK: declare {{.*}} void @"$s6Extern7import1yyF"() [[EA1:#[0-9]+]] +@_extern(wasm, module: "m0", name: "import1") +func import1() + +// CHECK: declare {{.*}} i32 @"$s6Extern20import2WithReturnInts5Int32VyF"() [[EA2:#[0-9]+]] +@_extern(wasm, module: "m0", name: "import2") +func import2WithReturnInt() -> Int32 + +// CHECK: declare {{.*}} void @"$s6Extern16import3TakingIntyys5Int32VF"(i32) [[EA3:#[0-9]+]] +@_extern(wasm, module: "m0", name: "import3") +func import3TakingInt(_: Int32) + +func test() { + import1() + _ = import2WithReturnInt() + import3TakingInt(0) +} + +test() + +// CHECK: attributes [[EA1]] = {{{.*}} "wasm-import-module"="m0" "wasm-import-name"="import1" {{.*}}} +// CHECK: attributes [[EA2]] = {{{.*}} "wasm-import-module"="m0" "wasm-import-name"="import2" {{.*}}} +// CHECK: attributes [[EA3]] = {{{.*}} "wasm-import-module"="m0" "wasm-import-name"="import3" {{.*}}} diff --git a/test/Serialization/attr-extern.swift b/test/Serialization/attr-extern.swift new file mode 100644 index 0000000000000..ae8413d42cf22 --- /dev/null +++ b/test/Serialization/attr-extern.swift @@ -0,0 +1,10 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module-path %t/a.swiftmodule -module-name a %s +// RUN: llvm-bcanalyzer -dump %t/a.swiftmodule | %FileCheck --check-prefix BC-CHECK --implicit-check-not UnknownCode %s +// RUN: %target-swift-ide-test -print-module -module-to-print a -source-filename x -I %t | %FileCheck --check-prefix MODULE-CHECK %s + +// BC-CHECK: Int + +@_extern(wasm, module: "m2", name: ) // expected-error {{expected string literal in '_extern' attribute}} +func f2ErrorOnMissingNameLiteral(x: Int) -> Int // expected-error{{expected '{' in body of function declaration}} + +@_extern(wasm, module: "m3", name) // expected-error {{expected ':' after label 'name'}} +func f3ErrorOnMissingNameColon(x: Int) -> Int // expected-error{{expected '{' in body of function declaration}} + +@_extern(wasm, module: "m4",) // expected-error {{expected name argument to @_extern attribute}} +func f4ErrorOnMissingNameLabel(x: Int) -> Int // expected-error{{expected '{' in body of function declaration}} + +@_extern(wasm, module: "m5") // expected-error {{expected name argument to @_extern attribute}} +func f5ErrorOnMissingName(x: Int) -> Int // expected-error{{expected '{' in body of function declaration}} + +@_extern(wasm, module: ) // expected-error {{expected string literal in '_extern' attribute}} expected-error {{expected name argument to @_extern attribute}} +func f6ErrorOnMissingModuleLiteral(x: Int) -> Int // expected-error{{expected '{' in body of function declaration}} + +@_extern(wasm, module) // expected-error {{expected ':' after label 'module'}} expected-error {{expected name argument to @_extern attribute}} +func f7ErrorOnMissingModuleColon(x: Int) -> Int // expected-error{{expected '{' in body of function declaration}} + +@_extern(wasm,) // expected-error {{expected module argument to @_extern attribute}} expected-error {{expected name argument to @_extern attribute}} +func f8ErrorOnMissingModuleLabel(x: Int) -> Int // expected-error{{expected '{' in body of function declaration}} + +@_extern(wasm, module: "m9", name: "f9") +func f9WithBody() {} // expected-error {{unexpected body of function declaration}} + +struct S { + @_extern(wasm, module: "m10", name: "f10") // expected-error {{@_extern attribute can only be applied to global functions}} + func f10Member() +} + +func f11Scope() { + @_extern(wasm, module: "m11", name: "f11") + func f11Inner() +} + +@_extern(invalid, module: "m12", name: "f12") // expected-error {{expected '_extern' option such as 'wasm'}} +func f12InvalidLang() // expected-error {{expected '{' in body of function declaration}}