Skip to content

[5.9.0] Fix serialization and type-checking issues with extension macros that introduce conformances #68175

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
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
11 changes: 4 additions & 7 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 All @@ -172,9 +172,6 @@ enum class ConformanceLookupKind : unsigned {
/// All conformances except structurally-derived conformances, of which
/// Sendable is the only one.
NonStructural,
/// Exclude conformances added by a macro that has not been expanded
/// yet.
ExcludeUnexpandedMacros,
};

/// Describes a diagnostic for a conflict between two protocol
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/ProtocolConformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,8 @@ class NormalProtocolConformance : public RootProtocolConformance,
assert((sourceKind == ConformanceEntryKind::Implied) ==
(bool)implyingConformance &&
"an implied conformance needs something that implies it");
assert(sourceKind != ConformanceEntryKind::PreMacroExpansion &&
"cannot create conformance pre-macro-expansion");
SourceKindAndImplyingConformance = {implyingConformance, sourceKind};
}

Expand Down
26 changes: 20 additions & 6 deletions lib/AST/ConformanceLookupTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,16 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
if (!conformingDC)
return nullptr;

// Never produce a conformance for a pre-macro-expansion conformance. They
// are placeholders that will be superseded.
if (entry->getKind() == ConformanceEntryKind::PreMacroExpansion) {
if (auto supersedingEntry = entry->SupersededBy) {
return getConformance(nominal, supersedingEntry);
}

return nullptr;
}

auto *conformingNominal = conformingDC->getSelfNominalTypeDecl();

// Form the conformance.
Expand Down Expand Up @@ -926,6 +936,16 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
? conformingNominal->getLoc()
: cast<ExtensionDecl>(conformingDC)->getLoc();

NormalProtocolConformance *implyingConf = nullptr;
if (entry->Source.getKind() == ConformanceEntryKind::Implied) {
auto implyingEntry = entry->Source.getImpliedSource();
auto origImplyingConf = getConformance(conformingNominal, implyingEntry);
if (!origImplyingConf)
return nullptr;

implyingConf = origImplyingConf->getRootNormalConformance();
}

// Create or find the normal conformance.
auto normalConf =
ctx.getConformance(conformingType, protocol, conformanceLoc,
Expand All @@ -936,12 +956,6 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
// early return at the start of this function.
entry->Conformance = normalConf;

NormalProtocolConformance *implyingConf = nullptr;
if (entry->Source.getKind() == ConformanceEntryKind::Implied) {
auto implyingEntry = entry->Source.getImpliedSource();
implyingConf = getConformance(conformingNominal, implyingEntry)
->getRootNormalConformance();
}
normalConf->setSourceKindAndImplyingConformance(entry->Source.getKind(),
implyingConf);

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
36 changes: 29 additions & 7 deletions lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1757,6 +1757,17 @@ namespace {
};
}

/// Given an extension declaration, return the extended nominal type if the
/// extension was produced by expanding an extension or conformance macro from
/// the nominal declaration itself.
static NominalTypeDecl *nominalForExpandedExtensionDecl(ExtensionDecl *ext) {
if (!ext->isInMacroExpansionInContext())
return nullptr;


return ext->getSelfNominalTypeDecl();
}

PotentialMacroExpansions PotentialMacroExpansionsInContextRequest::evaluate(
Evaluator &evaluator, TypeOrExtensionDecl container) const {
/// The implementation here needs to be kept in sync with
Expand All @@ -1767,6 +1778,15 @@ PotentialMacroExpansions PotentialMacroExpansionsInContextRequest::evaluate(
auto containerDecl = container.getAsDecl();
forEachPotentialAttachedMacro(containerDecl, MacroRole::Member, nameTracker);

// If the container is an extension that was created from an extension macro,
// look at the nominal declaration to find any extension macros.
if (auto ext = dyn_cast<ExtensionDecl>(containerDecl)) {
if (auto nominal = nominalForExpandedExtensionDecl(ext)) {
forEachPotentialAttachedMacro(
nominal, MacroRole::Extension, nameTracker);
}
}

// Peer and freestanding declaration macros.
auto dc = container.getAsDeclContext();
auto idc = container.getAsIterableDeclContext();
Expand Down Expand Up @@ -1825,13 +1845,15 @@ populateLookupTableEntryFromMacroExpansions(ASTContext &ctx,
// names match.
{
MacroIntroducedNameTracker nameTracker;
if (auto nominal = dyn_cast<NominalTypeDecl>(container.getAsDecl())) {
forEachPotentialAttachedMacro(nominal, MacroRole::Extension, nameTracker);
if (nameTracker.shouldExpandForName(name)) {
(void)evaluateOrDefault(
ctx.evaluator,
ExpandExtensionMacros{nominal},
false);
if (auto ext = dyn_cast<ExtensionDecl>(container.getAsDecl())) {
if (auto nominal = nominalForExpandedExtensionDecl(ext)) {
forEachPotentialAttachedMacro(nominal, MacroRole::Extension, nameTracker);
if (nameTracker.shouldExpandForName(name)) {
(void)evaluateOrDefault(
ctx.evaluator,
ExpandExtensionMacros{nominal},
false);
}
}
}
}
Expand Down
18 changes: 3 additions & 15 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1302,8 +1302,8 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind)
switch (conformance->getSourceKind()) {
case ConformanceEntryKind::Explicit:
case ConformanceEntryKind::Synthesized:
return true;
case ConformanceEntryKind::PreMacroExpansion:
return true;
case ConformanceEntryKind::Implied:
case ConformanceEntryKind::Inherited:
return false;
Expand All @@ -1314,34 +1314,22 @@ IterableDeclContext::getLocalConformances(ConformanceLookupKind lookupKind)
case ConformanceEntryKind::Explicit:
case ConformanceEntryKind::Synthesized:
case ConformanceEntryKind::Implied:
case ConformanceEntryKind::PreMacroExpansion:
return true;
case ConformanceEntryKind::Inherited:
case ConformanceEntryKind::PreMacroExpansion:
return false;
}

case ConformanceLookupKind::All:
case ConformanceLookupKind::NonStructural:
return true;

case ConformanceLookupKind::ExcludeUnexpandedMacros:
switch (conformance->getSourceKind()) {
case ConformanceEntryKind::PreMacroExpansion:
return false;
case ConformanceEntryKind::Explicit:
case ConformanceEntryKind::Synthesized:
case ConformanceEntryKind::Implied:
case ConformanceEntryKind::Inherited:
return true;
}
}
});

// If we want to add structural conformances, do so now.
switch (lookupKind) {
case ConformanceLookupKind::All:
case ConformanceLookupKind::NonInherited:
case ConformanceLookupKind::ExcludeUnexpandedMacros: {
case ConformanceLookupKind::NonInherited: {
// Look for a Sendable conformance globally. If it is synthesized
// and matches this declaration context, use it.
auto dc = getAsGenericContext();
Expand Down
3 changes: 0 additions & 3 deletions lib/SIL/IR/SILSymbolVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,6 @@ class SILSymbolVisitorImpl : public ASTVisitor<SILSymbolVisitorImpl> {
void addConformances(const IterableDeclContext *IDC) {
for (auto conformance :
IDC->getLocalConformances(ConformanceLookupKind::NonInherited)) {
if (conformance->getSourceKind() == ConformanceEntryKind::PreMacroExpansion)
continue;

auto protocol = conformance->getProtocol();
if (Ctx.getOpts().PublicSymbolsOnly &&
getDeclLinkage(protocol) != FormalLinkage::PublicUnique)
Expand Down
3 changes: 0 additions & 3 deletions lib/SILGen/SILGenType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1132,9 +1132,6 @@ class SILGenType : public TypeMemberVisitor<SILGenType> {
// are existential and do not have witness tables.
for (auto *conformance : theType->getLocalConformances(
ConformanceLookupKind::NonInherited)) {
if (conformance->getSourceKind() == ConformanceEntryKind::PreMacroExpansion)
continue;

assert(conformance->isComplete());
if (auto *normal = dyn_cast<NormalProtocolConformance>(conformance))
SGM.getWitnessTable(normal);
Expand Down
10 changes: 1 addition & 9 deletions lib/Sema/TypeCheckMacros.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1593,15 +1593,7 @@ llvm::Optional<unsigned> swift::expandExtensions(CustomAttr *attr,
for (auto protocol : potentialConformances) {
SmallVector<ProtocolConformance *, 2> existingConformances;
nominal->lookupConformance(protocol, existingConformances);

bool hasExistingConformance = llvm::any_of(
existingConformances,
[&](ProtocolConformance *conformance) {
return conformance->getSourceKind() !=
ConformanceEntryKind::PreMacroExpansion;
});

if (!hasExistingConformance) {
if (existingConformances.empty()) {
introducedConformances.push_back(protocol);
}
}
Expand Down
3 changes: 1 addition & 2 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6443,8 +6443,7 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
const auto defaultAccess = nominal->getFormalAccess();

// Check each of the conformances associated with this context.
auto conformances = idc->getLocalConformances(
ConformanceLookupKind::ExcludeUnexpandedMacros);
auto conformances = idc->getLocalConformances();

// The conformance checker bundle that checks all conformances in the context.
auto &Context = dc->getASTContext();
Expand Down
1 change: 0 additions & 1 deletion test/IDE/complete_optionset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public macro OptionSet<RawType>() =
// MEMBER_STATIC: Decl[StaticVar]/CurrNominal: secondDay[#ShippingOptions#]; name=secondDay
// MEMBER_STATIC: Decl[StaticVar]/CurrNominal: priority[#ShippingOptions#]; name=priority
// MEMBER_STATIC: Decl[StaticVar]/CurrNominal: standard[#ShippingOptions#]; name=standard
// MEMBER_STATIC: Decl[TypeAlias]/CurrNominal: ArrayLiteralElement[#ShippingOptions#]; name=ArrayLiteralElement
// MEMBER_STATIC: Decl[TypeAlias]/CurrNominal: Element[#ShippingOptions#]; name=Element
// MEMBER_STATIC: Decl[Constructor]/Super/IsSystem: init()[#ShippingOptions#]; name=init()
// MEMBER_STATIC: Decl[Constructor]/Super/IsSystem: init({#(sequence): Sequence#})[#ShippingOptions#]; name=init(:)
46 changes: 46 additions & 0 deletions test/Macros/Inputs/syntax_macro_definitions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,39 @@ public struct EquatableMacro: ConformanceMacro {
}
}

public struct EquatableViaMembersMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
attachedTo decl: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
let comparisons: [String] = decl.storedProperties().map { property in
guard let binding = property.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier else {
return "true"
}

return "lhs.\(identifier) == rhs.\(identifier)"
}

let condition = comparisons.joined(separator: " && ")
let equalOperator: DeclSyntax = """
static func ==(lhs: \(type.trimmed), rhs: \(type.trimmed)) -> Bool {
return \(raw: condition)
}
"""

let ext: DeclSyntax = """
extension \(type.trimmed): Equatable {
\(equalOperator)
}
"""
return [ext.cast(ExtensionDeclSyntax.self)]
}
}

public struct ConformanceViaExtensionMacro: ExtensionMacro {
public static func expansion(
of node: AttributeSyntax,
Expand Down Expand Up @@ -1370,6 +1403,19 @@ public struct HashableMacro: ConformanceMacro {
}
}

public struct ImpliesHashableMacro: 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): ImpliesHashable {}"
return [ext.cast(ExtensionDeclSyntax.self)]
}
}

public struct DelegatedConformanceMacro: ConformanceMacro, MemberMacro {
public static func expansion(
of node: AttributeSyntax,
Expand Down
37 changes: 37 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 @@ -165,3 +168,37 @@ macro AvailableEquatable() = #externalMacro(module: "MacroDefinition", type: "Co
struct TestAvailability {
static let x : any Equatable.Type = TestAvailability.self
}

@attached(extension, conformances: Equatable, names: named(==))
macro Equatable() = #externalMacro(module: "MacroDefinition", type: "EquatableViaMembersMacro")

@propertyWrapper
struct NotEquatable<T> {
var wrappedValue: T
}

@Equatable
struct HasPropertyWrappers {
@NotEquatable
var value: Int = 0
}

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 { }