diff --git a/Examples/Sources/ExamplePlugin/ExamplePlugin.swift b/Examples/Sources/ExamplePlugin/ExamplePlugin.swift index f0b765147e3..c3953f95930 100644 --- a/Examples/Sources/ExamplePlugin/ExamplePlugin.swift +++ b/Examples/Sources/ExamplePlugin/ExamplePlugin.swift @@ -5,6 +5,12 @@ import SwiftSyntaxMacros struct ThePlugin: CompilerPlugin { var providingMacros: [Macro.Type] = [ EchoExpressionMacro.self, + FuncUniqueMacro.self, MetadataMacro.self, + PeerValueWithSuffixNameMacro.self, + MemberDeprecatedMacro.self, + EquatableConformanceMacro.self, + DidSetPrintMacro.self, + PrintAnyMacro.self, ] } diff --git a/Examples/Sources/ExamplePlugin/Macros.swift b/Examples/Sources/ExamplePlugin/Macros.swift index 0509304c75e..234d533d9e3 100644 --- a/Examples/Sources/ExamplePlugin/Macros.swift +++ b/Examples/Sources/ExamplePlugin/Macros.swift @@ -16,6 +16,17 @@ struct EchoExpressionMacro: ExpressionMacro { } } +/// Func With unique name. +struct FuncUniqueMacro: DeclarationMacro { + static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + let name = context.makeUniqueName("unique") + return ["func \(name)() {}"] + } +} + /// Add a static property `__metadata__`. struct MetadataMacro: MemberMacro { static func expansion< @@ -26,14 +37,81 @@ struct MetadataMacro: MemberMacro { providingMembersOf declaration: Declaration, in context: Context ) throws -> [DeclSyntax] { - guard let cls = declaration.as(ClassDeclSyntax.self) else { + guard let type = declaration.asProtocol(IdentifiedDeclSyntax.self) else { return [] } - let className = cls.identifier.trimmedDescription + let typeName = type.identifier.trimmedDescription return [ """ - static var __metadata__: [String: String] { ["name": "\(raw: className)"] } + static var __metadata__: [String: String] { ["name": "\(raw: typeName)"] } """ ] } } + +/// Peer 'var' with the name suffixed with '_peer'. +struct PeerValueWithSuffixNameMacro: PeerMacro { + static func expansion( + of node: AttributeSyntax, + providingPeersOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + guard let identified = declaration.asProtocol(IdentifiedDeclSyntax.self) else { + return [] + } + return ["var \(raw: identified.identifier.text)_peer: Int { 1 }"] + } +} + +/// Add '@available(*, deprecated)' to members. +struct MemberDeprecatedMacro: MemberAttributeMacro { + static func expansion( + of node: SwiftSyntax.AttributeSyntax, + attachedTo declaration: some SwiftSyntax.DeclGroupSyntax, + providingAttributesFor member: some SwiftSyntax.DeclSyntaxProtocol, + in context: some SwiftSyntaxMacros.MacroExpansionContext + ) throws -> [SwiftSyntax.AttributeSyntax] { + return ["@available(*, deprecated)"] + } +} + +/// Add 'Equatable' conformance. +struct EquatableConformanceMacro: ConformanceMacro { + static func expansion( + of node: AttributeSyntax, + providingConformancesOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] { + return [("Equatable", nil)] + } +} + +/// Add 'didSet' printing the new value. +struct DidSetPrintMacro: AccessorMacro { + static func expansion( + of node: AttributeSyntax, + providingAccessorsOf declaration: some DeclSyntaxProtocol, + in context: some MacroExpansionContext + ) throws -> [AccessorDeclSyntax] { + guard + let identifier = declaration.as(VariableDeclSyntax.self)?.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier + else { + return [] + } + + return ["didSet { print(\(identifier)) }"] + } +} + +/// 'print()'. +struct PrintAnyMacro: CodeItemMacro { + static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) throws -> [CodeBlockItemSyntax] { + guard let expr = node.argumentList.first?.expression else { + return [] + } + return ["print(\(expr))"] + } +} diff --git a/Sources/SwiftCompilerPluginMessageHandling/PluginMessageCompatibility.swift b/Sources/SwiftCompilerPluginMessageHandling/PluginMessageCompatibility.swift new file mode 100644 index 00000000000..63a758a3597 --- /dev/null +++ b/Sources/SwiftCompilerPluginMessageHandling/PluginMessageCompatibility.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See http://swift.org/LICENSE.txt for license information +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +/// Old compiler might send '.declaration' as "freeStandingDeclaration". +extension PluginMessage.MacroRole { + init(from decoder: Decoder) throws { + let stringValue = try decoder.singleValueContainer().decode(String.self) + if let role = Self(rawValue: stringValue) { + self = role + return + } + // Accept "freeStandingDeclaration" as '.declaration'. + if stringValue == "freeStandingDeclaration" { + self = Self.declaration + return + } + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Invalid string value for MacroRole: \(stringValue)" + ) + ) + } +} diff --git a/lit_tests/compiler_plugin_basic.swift b/lit_tests/compiler_plugin_basic.swift index db3608de860..8d1ec1ec42f 100644 --- a/lit_tests/compiler_plugin_basic.swift +++ b/lit_tests/compiler_plugin_basic.swift @@ -2,11 +2,12 @@ // // RUN: %empty-directory(%t) // -// RUN: %swift-frontend -typecheck -swift-version 5 \ -// RUN: -enable-experimental-feature Macros \ +// RUN: %swift-frontend -typecheck -verify -swift-version 5 \ +// RUN: -enable-experimental-feature CodeItemMacros \ // RUN: -dump-macro-expansions \ // RUN: -load-plugin-executable %examples_bin_path/ExamplePlugin#ExamplePlugin \ -// RUN -module-name MyApp \ +// RUN: -parse-as-library \ +// RUN: -module-name TestApp \ // RUN: %s 2>&1 | tee %t/expansions-dump.txt // // RUN: %FileCheck %s < %t/expansions-dump.txt @@ -14,18 +15,92 @@ @freestanding(expression) macro echo(_: T) -> T = #externalMacro(module: "ExamplePlugin", type: "EchoExpressionMacro") +@freestanding(declaration) +macro funcUnique() = #externalMacro(module: "ExamplePlugin", type: "FuncUniqueMacro") + +@freestanding(codeItem) +macro printAny(_: Any) = #externalMacro(module: "ExamplePlugin", type: "PrintAnyMacro") + @attached(member, names: named(__metadata__)) macro Metadata() = #externalMacro(module: "ExamplePlugin", type: "MetadataMacro") +@attached(peer, names: suffixed(_peer)) +macro PeerWithSuffix() = #externalMacro(module: "ExamplePlugin", type: "PeerValueWithSuffixNameMacro") + +@attached(memberAttribute) +macro MemberDeprecated() = #externalMacro(module: "ExamplePlugin", type: "MemberDeprecatedMacro") + +@attached(conformance) +macro Equatable() = #externalMacro(module: "ExamplePlugin", type: "EquatableConformanceMacro") + +@attached(accessor) +macro DidSetPrint() = #externalMacro(module: "ExamplePlugin", type: "DidSetPrintMacro") + @Metadata -class MyClass { +@MemberDeprecated +@Equatable +@PeerWithSuffix +struct MyStruct { + @DidSetPrint var value: Int = #echo(12) + // expected-error@-1 {{expansion of macro 'DidSetPrint()' did not produce a non-observing accessor}} + + func _test() { + #printAny("test") + } } -// For '@Metadata' -// CHECK: {{^}}static var __metadata__: [String: String] { -// CHECK-NEXT: {{^}} ["name": "MyClass"] -// CHECK-NEXT: {{^}}} +#funcUnique + +// CHECK: @__swiftmacro_7TestApp8MyStruct14PeerWithSuffixfMp_.swift +// CHECK-NEXT: ------------------------------ +// CHECK-NEXT: var MyStruct_peer: Int { +// CHECK-NEXT: 1 +// CHECK-NEXT: } +// CHECK-NEXT: ------------------------------ + +// CHECK: @__swiftmacro_7TestApp8MyStruct9EquatablefMc_.swift +// CHECK-NEXT: ------------------------------ +// CHECK-NEXT: extension MyStruct : Equatable {} +// CHECK-NEXT: ------------------------------ + +// CHECK: @__swiftmacro_7TestApp8MyStruct8MetadatafMm_.swift +// CHECK-NEXT: ------------------------------ +// CHECK-NEXT: static var __metadata__: [String: String] { +// CHECK-NEXT: ["name": "MyStruct"] +// CHECK-NEXT: } +// CHECK-NEXT: ------------------------------ + +// CHECK: @__swiftmacro_7TestApp8MyStructV5value16MemberDeprecatedfMr_.swift +// CHECK-NEXT: ------------------------------ +// CHECK-NEXT: @available(*, deprecated) +// CHECK-NEXT: ------------------------------ + +// CHECK: @__swiftmacro_7TestApp8MyStructV5_test16MemberDeprecatedfMr0_.swift +// CHECK-NEXT: ------------------------------ +// CHECK-NEXT: @available(*, deprecated) +// CHECK-NEXT: ------------------------------ + +// CHECK: @__swiftmacro_7TestApp8MyStructV5value11DidSetPrintfMa_.swift +// CHECK-NEXT: ------------------------------ +// CHECK-NEXT: { +// CHECK-NEXT: didSet { +// CHECK-NEXT: print(value) +// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: ------------------------------ + +// CHECK: @__swiftmacro_7TestApp8MyStructV4echofMf_.swift +// CHECK-NEXT: ------------------------------ +// CHECK-NEXT: /* echo */12 +// CHECK-NEXT: ------------------------------ + +// CHECK: @__swiftmacro_7TestApp33_B5E4CA48BE2C4AA1BE7F954C809E362ALl10funcUniquefMf_.swift +// CHECK-NEXT: ------------------------------ +// CHECK-NEXT: func $s7TestApp33_B5E4CA48BE2C4AA1BE7F954C809E362ALl10funcUniquefMf_6uniquefMu_() { +// CHECK-NEXT: } -// For '#echo(12)' -// CHECK: /* echo */12 +// CHECK: @__swiftmacro_7TestApp8MyStructV5_testyyF8printAnyfMf0_.swift +// CHECK-NEXT: ------------------------------ +// CHECK-NEXT: print("test") +// CHECK-NEXT: ------------------------------