Skip to content

[5.9] Always supersede conformances implied by pre-macro-expansion conformances #68032

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions include/swift/AST/DeclContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,15 +150,15 @@ enum class ConformanceEntryKind : unsigned {
/// Explicitly specified.
Explicit,

/// The conformance is generated by a macro that has not been
/// expanded yet.
PreMacroExpansion,

/// Implicitly synthesized.
Synthesized,

/// Implied by an explicitly-specified conformance.
Implied,

/// The conformance is generated by a macro that has not been
/// expanded yet.
PreMacroExpansion,
};

/// Describes the kind of conformance lookup desired.
Expand Down
23 changes: 17 additions & 6 deletions lib/AST/ConformanceLookupTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
///
/// The only difference between the ranking kind and the kind is
/// that implied conformances originating from a synthesized
/// conformance are considered to be synthesized (which has a
/// or pre-macro-expansion conformance are considered to be synthesized (which has a
/// lower ranking).
ConformanceEntryKind getRankingKind() const {
switch (auto kind = getKind()) {
Expand All @@ -157,11 +157,22 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
case ConformanceEntryKind::PreMacroExpansion:
return kind;

case ConformanceEntryKind::Implied:
return (getImpliedSource()->getDeclaredConformance()->getKind()
== ConformanceEntryKind::Synthesized)
? ConformanceEntryKind::Synthesized
: ConformanceEntryKind::Implied;
case ConformanceEntryKind::Implied: {
auto impliedSourceKind =
getImpliedSource()->getDeclaredConformance()->getKind();
switch (impliedSourceKind) {
case ConformanceEntryKind::Synthesized:
case ConformanceEntryKind::PreMacroExpansion:
return impliedSourceKind;

case ConformanceEntryKind::Explicit:
case ConformanceEntryKind::Inherited:
return ConformanceEntryKind::Implied;

case ConformanceEntryKind::Implied:
return getImpliedSource()->getRankingKind();
}
}
}

llvm_unreachable("Unhandled ConformanceEntryKind in switch.");
Expand Down
30 changes: 14 additions & 16 deletions lib/Macros/Sources/SwiftMacros/OptionSetMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,27 +123,25 @@ public struct OptionSetMacro {
}
}

extension OptionSetMacro: ConformanceMacro {
public static func expansion<
Decl: DeclGroupSyntax,
Context: MacroExpansionContext
>(
extension OptionSetMacro: ExtensionMacro {
public static func expansion(
of attribute: AttributeSyntax,
providingConformancesOf decl: Decl,
in context: Context
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
// Decode the expansion arguments.
guard let (structDecl, _, _) = decodeExpansion(of: attribute, attachedTo: decl, in: context) else {
return []
}

attachedTo decl: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
// If there is an explicit conformance to OptionSet already, don't add one.
if let inheritedTypes = structDecl.inheritanceClause?.inheritedTypeCollection,
inheritedTypes.contains(where: { inherited in inherited.typeName.trimmedDescription == "OptionSet" }) {
if protocols.isEmpty {
return []
}

return [("OptionSet", nil)]
let ext: DeclSyntax =
"""
extension \(type.trimmed): OptionSet {}
"""

return [ext.cast(ExtensionDeclSyntax.self)]
}
}

Expand Down
12 changes: 7 additions & 5 deletions test/Index/index_macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,16 @@ public struct SomeAccessorMacro: AccessorMacro {
}
}

public struct SomeConformanceMacro: ConformanceMacro {
public struct SomeConformanceMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
providingConformancesOf decl: some DeclGroupSyntax,
attachedTo: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
let protocolName: TypeSyntax = "TestProto"
return [(protocolName, nil)]
) throws -> [ExtensionDeclSyntax] {
let ext: DeclSyntax = "extension \(type.trimmed): TestProto {}"
return [ext.cast(ExtensionDeclSyntax.self)]
}
}

Expand Down
75 changes: 44 additions & 31 deletions test/Macros/Inputs/syntax_macro_definitions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1344,14 +1344,16 @@ public struct EmptyPeerMacro: PeerMacro {
}
}

public struct EquatableMacro: ConformanceMacro {
public struct EquatableMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
providingConformancesOf decl: some DeclGroupSyntax,
attachedTo: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
let protocolName: TypeSyntax = "Equatable"
return [(protocolName, nil)]
) throws -> [ExtensionDeclSyntax] {
let ext: DeclSyntax = "extension \(type.trimmed): Equatable {}"
return [ext.cast(ExtensionDeclSyntax.self)]
}
}

Expand Down Expand Up @@ -1412,34 +1414,50 @@ public struct ConformanceViaExtensionMacro: ExtensionMacro {
}
}

public struct HashableMacro: ConformanceMacro {
public struct HashableMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
attachedTo: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
let ext: DeclSyntax = "extension \(type.trimmed): Hashable {}"
return [ext.cast(ExtensionDeclSyntax.self)]
}
}

public struct ImpliesHashableMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
providingConformancesOf decl: some DeclGroupSyntax,
attachedTo: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
let protocolName: TypeSyntax = "Hashable"
return [(protocolName, nil)]
) throws -> [ExtensionDeclSyntax] {
let ext: DeclSyntax = "extension \(type.trimmed): ImpliesHashable {}"
return [ext.cast(ExtensionDeclSyntax.self)]
}
}

public struct DelegatedConformanceMacro: ConformanceMacro, MemberMacro {
public struct DelegatedConformanceMacro: ExtensionMacro, MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingConformancesOf decl: some DeclGroupSyntax,
attachedTo: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
let protocolName: TypeSyntax = "P"
) throws -> [ExtensionDeclSyntax] {
let conformance: DeclSyntax =
"""
extension Placeholder where Element: P {}
extension \(type.trimmed): P where Element: P {}
"""

guard let extensionDecl = conformance.as(ExtensionDeclSyntax.self) else {
return []
}

return [(protocolName, extensionDecl.genericWhereClause)]
return [extensionDecl]
}

public static func expansion(
Expand Down Expand Up @@ -1894,26 +1912,21 @@ public struct AddPeerStoredPropertyMacro: PeerMacro, Sendable {
}
}

public struct InitializableMacro: ConformanceMacro, MemberMacro {
public struct InitializableMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
providingConformancesOf decl: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
return [("Initializable", nil)]
}

public static func expansion(
of node: AttributeSyntax,
providingMembersOf decl: some DeclGroupSyntax,
attachedTo: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
let requirement: DeclSyntax =
) throws -> [ExtensionDeclSyntax] {
let ext: DeclSyntax =
"""
init(value: Int) {}
extension \(type.trimmed): Initializable {
init(value: Int) {}
}
"""

return [requirement]
return [ext.cast(ExtensionDeclSyntax.self)]
}
}

Expand Down
18 changes: 18 additions & 0 deletions test/Macros/macro_expand_extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
// RUN: not %target-swift-frontend -enable-experimental-feature ExtensionMacros -swift-version 5 -typecheck -load-plugin-library %t/%target-library-name(MacroDefinition) -module-name MacroUser -DTEST_DIAGNOSTICS -serialize-diagnostics-path %t/macro_expand.dia %s -emit-macro-expansion-files no-diagnostics
// RUN: c-index-test -read-diagnostics %t/macro_expand.dia 2>&1 | %FileCheck -check-prefix CHECK-DIAGS %s

// Ensure that we can serialize this file as a module.
// RUN: %target-swift-frontend -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) %s -I %t -disable-availability-checking -emit-module -o %t/MyModule.swiftmodule -enable-testing

// RUN: %target-build-swift -enable-experimental-feature ExtensionMacros -swift-version 5 -load-plugin-library %t/%target-library-name(MacroDefinition) %s -o %t/main -module-name MacroUser -swift-version 5 -emit-tbd -emit-tbd-path %t/MacroUser.tbd -I %t
// RUN: %target-codesign %t/main
// RUN: %target-run %t/main | %FileCheck %s
Expand Down Expand Up @@ -198,3 +201,18 @@ func requiresEquatable<T: Equatable>(_: T) { }
func testHasPropertyWrappers(hpw: HasPropertyWrappers) {
requiresEquatable(hpw)
}

// Check that conformances implied by a macro-defined conformance are serialized
// without issue.
public protocol ImpliesHashable: Hashable { }

@attached(extension, conformances: ImpliesHashable)
macro ImpliesHashable() = #externalMacro(module: "MacroDefinition", type: "ImpliesHashableMacro")

func requiresHashable<T: Hashable>(_: T) { }
func testMakeMeHashable(mmh: MakeMeHashable, dict: [MakeMeHashable: Int]) {
requiresHashable(mmh)
}

@ImpliesHashable
public struct MakeMeHashable { }
3 changes: 1 addition & 2 deletions test/Macros/top_level_freestanding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ macro Empty<T>(_ closure: () -> T) = #externalMacro(module: "MacroDefinition", t
S(a: 10, b: 10)
}

@attached(extension, conformances: Initializable)
@attached(member, names: named(init))
@attached(extension, conformances: Initializable, names: named(init))
macro Initializable() = #externalMacro(module: "MacroDefinition", type: "InitializableMacro")

protocol Initializable {
Expand Down