Skip to content

[5.9][Macros] Allow extension macros to suppress conformances that are stated in the original source. #67083

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 8 commits into from
Jul 3, 2023
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
9 changes: 9 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -8595,6 +8595,15 @@ class MacroDecl : public GenericContext, public ValueDecl {
void getIntroducedNames(MacroRole role, ValueDecl *attachedTo,
SmallVectorImpl<DeclName> &names) const;

/// Populate the \c conformances vector with the protocols that
/// this macro generates conformances to.
///
/// Only extension macros can add conformances; no results will
/// be added if this macro does not contain an extension role.
void getIntroducedConformances(
NominalTypeDecl *attachedTo,
SmallVectorImpl<ProtocolDecl *> &conformances) const;

/// Returns a DeclName that represents arbitrary names.
static DeclName getArbitraryName() {
return DeclName();
Expand Down
7 changes: 7 additions & 0 deletions include/swift/AST/DeclContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,10 @@ enum class ConformanceEntryKind : unsigned {

/// 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 @@ -167,6 +171,9 @@ 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
6 changes: 5 additions & 1 deletion include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2726,7 +2726,8 @@ NOTE(declared_protocol_conformance_here,none,
"%select{%0 inherits conformance to protocol %2 from superclass|"
"%0 declares conformance to protocol %2|"
"%0 implicitly conforms to protocol %2 (via conformance to %3)|"
"%0 implicitly conforms to protocol %2}1 here",
"%0 implicitly conforms to protocol %2 |"
"conformance to %2 generated by macro }1 here",
(Type, unsigned, Identifier, Identifier))

ERROR(witness_unavailable,none,
Expand Down Expand Up @@ -7151,6 +7152,9 @@ ERROR(global_arbitrary_name,none,
ERROR(local_extension_macro,none,
"local type cannot have attached extension macro",
())
ERROR(extension_macro_invalid_conformance,none,
"invalid protocol conformance %0 in extension macro",
(Type))

ERROR(macro_resolve_circular_reference, none,
"circular reference resolving %select{freestanding|attached}0 macro %1",
Expand Down
2 changes: 1 addition & 1 deletion include/swift/AST/ProtocolConformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ class NormalProtocolConformance : public RootProtocolConformance,
///
/// This should never be Inherited: that is handled by
/// InheritedProtocolConformance.
llvm::PointerIntPair<NormalProtocolConformance *, 2, ConformanceEntryKind>
llvm::PointerIntPair<NormalProtocolConformance *, 3, ConformanceEntryKind>
SourceKindAndImplyingConformance = {nullptr,
ConformanceEntryKind::Explicit};

Expand Down
41 changes: 19 additions & 22 deletions include/swift/AST/TypeCheckRequests.h
Original file line number Diff line number Diff line change
Expand Up @@ -3337,6 +3337,25 @@ class ResolveMacroRequest
void noteCycleStep(DiagnosticEngine &diags) const;
};

/// Returns the resolved constraint types that an extension macro
/// adds conformances to.
class ResolveExtensionMacroConformances
: public SimpleRequest<ResolveExtensionMacroConformances,
ArrayRef<Type>(const MacroRoleAttr *, const Decl *),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

ArrayRef<Type> evaluate(Evaluator &evaluator,
const MacroRoleAttr *, const Decl *) const;

public:
bool isCached() const { return true; }
};

class ResolveTypeEraserTypeRequest
: public SimpleRequest<ResolveTypeEraserTypeRequest,
Type (ProtocolDecl *, TypeEraserAttr *),
Expand Down Expand Up @@ -4032,28 +4051,6 @@ class ExpandAccessorMacros
void noteCycleStep(DiagnosticEngine &diags) const;
};

/// Expand all conformance macros attached to the given declaration.
///
/// Produces the set of macro expansion buffer IDs.
class ExpandConformanceMacros
: public SimpleRequest<ExpandConformanceMacros,
ArrayRef<unsigned>(NominalTypeDecl *),
RequestFlags::Cached> {
public:
using SimpleRequest::SimpleRequest;

private:
friend SimpleRequest;

ArrayRef<unsigned> evaluate(Evaluator &evaluator,
NominalTypeDecl *nominal) const;

public:
bool isCached() const { return true; }
void diagnoseCycle(DiagnosticEngine &diags) const;
void noteCycleStep(DiagnosticEngine &diags) const;
};

/// Expand all extension macros attached to the given declaration.
///
/// Produces the set of macro expansion buffer IDs.
Expand Down
6 changes: 3 additions & 3 deletions include/swift/AST/TypeCheckerTypeIDZone.def
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,9 @@ SWIFT_REQUEST(TypeChecker, ResolveImplicitMemberRequest,
SWIFT_REQUEST(TypeChecker, ResolveMacroRequest,
ConcreteDeclRef(UnresolvedMacroReference, const Decl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ResolveExtensionMacroConformances,
ArrayRef<Type>(const MacroRoleAttr *, const Decl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ResolveTypeEraserTypeRequest,
Type(ProtocolDecl *, TypeEraserAttr *),
SeparatelyCached, NoLocationInfo)
Expand Down Expand Up @@ -457,9 +460,6 @@ SWIFT_REQUEST(TypeChecker, ExpandMemberAttributeMacros,
SWIFT_REQUEST(TypeChecker, ExpandAccessorMacros,
ArrayRef<unsigned>(AbstractStorageDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ExpandConformanceMacros,
ArrayRef<unsigned>(NominalTypeDecl *),
Cached, NoLocationInfo)
SWIFT_REQUEST(TypeChecker, ExpandExtensionMacros,
ArrayRef<unsigned>(NominalTypeDecl *),
Cached, NoLocationInfo)
Expand Down
53 changes: 42 additions & 11 deletions lib/AST/ConformanceLookupTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ DeclContext *ConformanceLookupTable::ConformanceSource::getDeclContext() const {

case ConformanceEntryKind::Synthesized:
return getSynthesizedDeclContext();

case ConformanceEntryKind::PreMacroExpansion:
return getMacroGeneratedDeclContext();
}

llvm_unreachable("Unhandled ConformanceEntryKind in switch.");
Expand Down Expand Up @@ -104,6 +107,10 @@ void ConformanceLookupTable::ConformanceEntry::dump(raw_ostream &os,
case ConformanceEntryKind::Synthesized:
os << " synthesized";
break;

case ConformanceEntryKind::PreMacroExpansion:
os << " unexpanded macro";
break;
}

if (auto conf = getConformance()) {
Expand Down Expand Up @@ -282,14 +289,8 @@ void ConformanceLookupTable::updateLookupTable(NominalTypeDecl *nominal,
addInheritedProtocols(nominal,
ConformanceSource::forExplicit(nominal));

// Expand conformance macros.
ASTContext &ctx = nominal->getASTContext();
(void)evaluateOrDefault(
ctx.evaluator, ExpandConformanceMacros{nominal}, { });

// Expand extension macros.
(void)evaluateOrDefault(
ctx.evaluator, ExpandExtensionMacros{nominal}, { });
addMacroGeneratedProtocols(
nominal, ConformanceSource::forUnexpandedMacro(nominal));
},
[&](ExtensionDecl *ext,
ArrayRef<ConformanceConstructionInfo> protos) {
Expand Down Expand Up @@ -445,6 +446,7 @@ bool ConformanceLookupTable::addProtocol(ProtocolDecl *protocol, SourceLoc loc,
switch (existingEntry->getKind()) {
case ConformanceEntryKind::Explicit:
case ConformanceEntryKind::Inherited:
case ConformanceEntryKind::PreMacroExpansion:
return false;

case ConformanceEntryKind::Implied:
Expand Down Expand Up @@ -495,6 +497,20 @@ void ConformanceLookupTable::addInheritedProtocols(
}
}

void ConformanceLookupTable::addMacroGeneratedProtocols(
NominalTypeDecl *nominal, ConformanceSource source) {
nominal->forEachAttachedMacro(
MacroRole::Extension,
[&](CustomAttr *attr, MacroDecl *macro) {
SmallVector<ProtocolDecl *, 2> conformances;
macro->getIntroducedConformances(nominal, conformances);

for (auto *protocol : conformances) {
addProtocol(protocol, attr->getLocation(), source);
}
});
}

void ConformanceLookupTable::expandImpliedConformances(NominalTypeDecl *nominal,
DeclContext *dc) {
// Note: recursive type-checking implies that AllConformances
Expand Down Expand Up @@ -534,6 +550,7 @@ static bool isReplaceable(ConformanceEntryKind kind) {
switch (kind) {
case ConformanceEntryKind::Implied:
case ConformanceEntryKind::Synthesized:
case ConformanceEntryKind::PreMacroExpansion:
return true;

case ConformanceEntryKind::Explicit:
Expand Down Expand Up @@ -562,6 +579,23 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
: Ordering::After);
}

ConformanceEntryKind lhsKind = lhs->getRankingKind();
ConformanceEntryKind rhsKind = rhs->getRankingKind();

// Pre-expanded macro conformances are always superseded by
// conformances written in source. If the conformance is not
// written in the original source, the pre-expanded conformance
// will be superseded by the conformance in the macro expansion
// buffer.
if (lhsKind == ConformanceEntryKind::PreMacroExpansion ||
rhsKind == ConformanceEntryKind::PreMacroExpansion) {
if (lhsKind != rhsKind) {
return (lhs->getKind() < rhs->getKind()
? Ordering::Before
: Ordering::After);
}
}

// If one entry is fixed and the other is not, we have our answer.
if (lhs->isFixed() != rhs->isFixed()) {
auto isReplaceableOrMarker = [](ConformanceEntry *entry) -> bool {
Expand All @@ -584,9 +618,6 @@ ConformanceLookupTable::Ordering ConformanceLookupTable::compareConformances(
return lhs->isFixed() ? Ordering::Before : Ordering::After;
}

ConformanceEntryKind lhsKind = lhs->getRankingKind();
ConformanceEntryKind rhsKind = rhs->getRankingKind();

if (lhsKind != ConformanceEntryKind::Implied ||
rhsKind != ConformanceEntryKind::Implied) {
// If both conformances are non-replaceable, diagnose the
Expand Down
36 changes: 29 additions & 7 deletions lib/AST/ConformanceLookupTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,15 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
/// Describes the "source" of a conformance, indicating where the
/// conformance came from.
class ConformanceSource {
llvm::PointerIntPair<void *, 2, ConformanceEntryKind> Storage;
void *Storage;

ConformanceEntryKind Kind;

/// The location of the "unchecked" attribute, if there is one.
SourceLoc uncheckedLoc;

ConformanceSource(void *ptr, ConformanceEntryKind kind)
: Storage(ptr, kind) { }
: Storage(ptr), Kind(kind) { }

public:
/// Create an inherited conformance.
Expand Down Expand Up @@ -126,6 +128,10 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
return ConformanceSource(dc, ConformanceEntryKind::Synthesized);
}

static ConformanceSource forUnexpandedMacro(DeclContext *dc) {
return ConformanceSource(dc, ConformanceEntryKind::PreMacroExpansion);
}

/// Return a new conformance source with the given location of "@unchecked".
ConformanceSource withUncheckedLoc(SourceLoc uncheckedLoc) {
ConformanceSource result(*this);
Expand All @@ -135,7 +141,7 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
}

/// Retrieve the kind of conformance formed from this source.
ConformanceEntryKind getKind() const { return Storage.getInt(); }
ConformanceEntryKind getKind() const { return Kind; }

/// Retrieve kind of the conformance for ranking purposes.
///
Expand All @@ -148,6 +154,7 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
case ConformanceEntryKind::Explicit:
case ConformanceEntryKind::Inherited:
case ConformanceEntryKind::Synthesized:
case ConformanceEntryKind::PreMacroExpansion:
return kind;

case ConformanceEntryKind::Implied:
Expand All @@ -169,28 +176,33 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
/// for the inheriting class.
ClassDecl *getInheritingClass() const {
assert(getKind() == ConformanceEntryKind::Inherited);
return static_cast<ClassDecl *>(Storage.getPointer());
return static_cast<ClassDecl *>(Storage);
}

/// For an explicit conformance, retrieve the declaration context
/// that specifies the conformance.
DeclContext *getExplicitDeclContext() const {
assert(getKind() == ConformanceEntryKind::Explicit);
return static_cast<DeclContext *>(Storage.getPointer());
return static_cast<DeclContext *>(Storage);
}

DeclContext *getMacroGeneratedDeclContext() const {
assert(getKind() == ConformanceEntryKind::PreMacroExpansion);
return static_cast<DeclContext *>(Storage);
}

/// For a synthesized conformance, retrieve the nominal type decl
/// that will receive the conformance.
ConformanceEntry *getImpliedSource() const {
assert(getKind() == ConformanceEntryKind::Implied);
return static_cast<ConformanceEntry *>(Storage.getPointer());
return static_cast<ConformanceEntry *>(Storage);
}

/// For a synthesized conformance, retrieve the nominal type decl
/// that will receive the conformance.
DeclContext *getSynthesizedDeclContext() const {
assert(getKind() == ConformanceEntryKind::Synthesized);
return static_cast<DeclContext *>(Storage.getPointer());
return static_cast<DeclContext *>(Storage);
}

/// Get the declaration context that this conformance will be
Expand Down Expand Up @@ -226,6 +238,10 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {

/// Whether this conformance is already "fixed" and cannot be superseded.
bool isFixed() const {
// A conformance from an unexpanded macro can always be superseded.
if (getKind() == ConformanceEntryKind::PreMacroExpansion)
return true;

// If a conformance has been assigned, it cannot be superseded.
if (getConformance())
return true;
Expand All @@ -235,6 +251,7 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
case ConformanceEntryKind::Explicit:
case ConformanceEntryKind::Implied:
case ConformanceEntryKind::Synthesized:
case ConformanceEntryKind::PreMacroExpansion:
return false;

case ConformanceEntryKind::Inherited:
Expand Down Expand Up @@ -341,6 +358,11 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
llvm::PointerUnion<const TypeDecl *, const ExtensionDecl *> decl,
ConformanceSource source);

/// Add the protocols added by attached extension macros that are not
/// yet expanded.
void addMacroGeneratedProtocols(
NominalTypeDecl *nominal, ConformanceSource source);

/// Expand the implied conformances for the given DeclContext.
void expandImpliedConformances(NominalTypeDecl *nominal, DeclContext *dc);

Expand Down
Loading