Skip to content

[6.2] Sema: Fix the insertion location for conformances attributes #82664

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 2 commits into from
Jul 2, 2025
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
4 changes: 2 additions & 2 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -1330,10 +1330,10 @@ class ASTContext final {
getNormalConformance(Type conformingType,
ProtocolDecl *protocol,
SourceLoc loc,
TypeRepr *inheritedTypeRepr,
DeclContext *dc,
ProtocolConformanceState state,
ProtocolConformanceOptions options,
SourceLoc preconcurrencyLoc = SourceLoc());
ProtocolConformanceOptions options);

/// Produce a self-conformance for the given protocol.
SelfProtocolConformance *
Expand Down
13 changes: 9 additions & 4 deletions include/swift/AST/NameLookup.h
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,11 @@ void forEachPotentialAttachedMacro(

/// Describes an inherited nominal entry.
struct InheritedNominalEntry : Located<NominalTypeDecl *> {
/// The `TypeRepr` of the inheritance clause entry from which this nominal was
/// sourced, if any. For example, if this is a conformance to `Y` declared as
/// `struct S: X, Y & Z {}`, this is the `TypeRepr` for `Y & Z`.
TypeRepr *inheritedTypeRepr;

ConformanceAttributes attributes;

/// Whether this inherited entry was suppressed via "~".
Expand All @@ -612,10 +617,10 @@ struct InheritedNominalEntry : Located<NominalTypeDecl *> {
InheritedNominalEntry() { }

InheritedNominalEntry(NominalTypeDecl *item, SourceLoc loc,
ConformanceAttributes attributes,
bool isSuppressed)
: Located(item, loc), attributes(attributes),
isSuppressed(isSuppressed) {}
TypeRepr *inheritedTypeRepr,
ConformanceAttributes attributes, bool isSuppressed)
: Located(item, loc), inheritedTypeRepr(inheritedTypeRepr),
attributes(attributes), isSuppressed(isSuppressed) {}
};

/// Retrieve the set of nominal type declarations that are directly
Expand Down
50 changes: 39 additions & 11 deletions include/swift/AST/ProtocolConformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -586,10 +586,17 @@ class NormalProtocolConformance : public RootProtocolConformance,
SourceLoc Loc;

/// The location of the protocol name within the conformance.
///
/// - Important: This is not a valid insertion location for an attribute.
/// Use `applyConformanceAttribute` instead.
SourceLoc ProtocolNameLoc;

/// The location of the `@preconcurrency` attribute, if any.
SourceLoc PreconcurrencyLoc;
/// The `TypeRepr` of the inheritance clause entry that declares this
/// conformance, if any. For example, if this is a conformance to `Y`
/// declared as `struct S: X, Y & Z {}`, this is the `TypeRepr` for `Y & Z`.
///
/// - Important: The value can be valid only for an explicit conformance.
TypeRepr *inheritedTypeRepr;

/// The declaration context containing the ExtensionDecl or
/// NominalTypeDecl that declared the conformance.
Expand Down Expand Up @@ -625,22 +632,26 @@ class NormalProtocolConformance : public RootProtocolConformance,
// Record the explicitly-specified global actor isolation.
void setExplicitGlobalActorIsolation(TypeExpr *typeExpr);

/// Return the `TypeRepr` of the inheritance clause entry that declares this
/// conformance, if any. For example, if this is a conformance to `Y`
/// declared as `struct S: X, Y & Z {}`, this is the `TypeRepr` for `Y & Z`.
///
/// - Important: The value can be valid only for an explicit conformance.
TypeRepr *getInheritedTypeRepr() const { return inheritedTypeRepr; }

public:
NormalProtocolConformance(Type conformingType, ProtocolDecl *protocol,
SourceLoc loc, DeclContext *dc,
ProtocolConformanceState state,
ProtocolConformanceOptions options,
SourceLoc preconcurrencyLoc)
SourceLoc loc, TypeRepr *inheritedTypeRepr,
DeclContext *dc, ProtocolConformanceState state,
ProtocolConformanceOptions options)
: RootProtocolConformance(ProtocolConformanceKind::Normal,
conformingType),
Protocol(protocol), Loc(extractNearestSourceLoc(dc)),
ProtocolNameLoc(loc), PreconcurrencyLoc(preconcurrencyLoc),
ProtocolNameLoc(loc), inheritedTypeRepr(inheritedTypeRepr),
Context(dc) {
assert(!conformingType->hasArchetype() &&
"ProtocolConformances should store interface types");
assert((preconcurrencyLoc.isInvalid() ||
options.contains(ProtocolConformanceFlags::Preconcurrency)) &&
"Cannot have a @preconcurrency location without isPreconcurrency");

setState(state);
Bits.NormalProtocolConformance.IsInvalid = false;
Bits.NormalProtocolConformance.IsPreconcurrencyEffectful = false;
Expand All @@ -650,6 +661,9 @@ class NormalProtocolConformance : public RootProtocolConformance,
unsigned(ConformanceEntryKind::Explicit);
Bits.NormalProtocolConformance.HasExplicitGlobalActor = false;
setExplicitGlobalActorIsolation(options.getGlobalActorIsolationType());

assert((!getPreconcurrencyLoc() || isPreconcurrency()) &&
"Cannot have a @preconcurrency location without isPreconcurrency");
}

/// Get the protocol being conformed to.
Expand All @@ -659,6 +673,9 @@ class NormalProtocolConformance : public RootProtocolConformance,
SourceLoc getLoc() const { return Loc; }

/// Retrieve the name of the protocol location.
///
/// - Important: This is not a valid insertion location for an attribute.
/// Use `applyConformanceAttribute` instead.
SourceLoc getProtocolNameLoc() const { return ProtocolNameLoc; }

/// Get the declaration context that contains the conforming extension or
Expand Down Expand Up @@ -729,7 +746,9 @@ class NormalProtocolConformance : public RootProtocolConformance,

/// Retrieve the location of `@preconcurrency`, if there is one and it is
/// known.
SourceLoc getPreconcurrencyLoc() const { return PreconcurrencyLoc; }
///
/// - Important: The value can be valid only for an explicit conformance.
SourceLoc getPreconcurrencyLoc() const;

/// Query whether this conformance was explicitly declared to be safe or
/// unsafe.
Expand Down Expand Up @@ -853,6 +872,15 @@ class NormalProtocolConformance : public RootProtocolConformance,
/// Triggers a request that resolves all of the conformance's value witnesses.
void resolveValueWitnesses() const;

/// If the necessary source location information is found, attaches a fix-it
/// to the given diagnostic for applying the given attribute to the
/// conformance.
///
/// \param attrStr A conformance attribute as a string, e.g. "@unsafe" or
/// "nonisolated".
void applyConformanceAttribute(InFlightDiagnostic &diag,
std::string attrStr) const;

/// Determine whether the witness for the given type requirement
/// is the default definition.
bool usesDefaultDefinition(AssociatedTypeDecl *requirement) const {
Expand Down
7 changes: 3 additions & 4 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2911,10 +2911,10 @@ NormalProtocolConformance *
ASTContext::getNormalConformance(Type conformingType,
ProtocolDecl *protocol,
SourceLoc loc,
TypeRepr *inheritedTypeRepr,
DeclContext *dc,
ProtocolConformanceState state,
ProtocolConformanceOptions options,
SourceLoc preconcurrencyLoc) {
ProtocolConformanceOptions options) {
assert(dc->isTypeContext());

llvm::FoldingSetNodeID id;
Expand All @@ -2928,8 +2928,7 @@ ASTContext::getNormalConformance(Type conformingType,

// Build a new normal protocol conformance.
auto result = new (*this) NormalProtocolConformance(
conformingType, protocol, loc, dc, state,
options, preconcurrencyLoc);
conformingType, protocol, loc, inheritedTypeRepr, dc, state, options);
normalConformances.InsertNode(result, insertPos);

return result;
Expand Down
60 changes: 38 additions & 22 deletions lib/AST/ConformanceLookupTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,20 @@ void ConformanceLookupTable::destroy() {

namespace {
struct ConformanceConstructionInfo : public Located<ProtocolDecl *> {
/// The `TypeRepr` of the inheritance clause entry from which this nominal
/// was sourced, if any. For example, if this is a conformance to `Y`
/// declared as `struct S: X, Y & Z {}`, this is the `TypeRepr` for `Y & Z`.
TypeRepr *inheritedTypeRepr;

ConformanceAttributes attributes;

ConformanceConstructionInfo() { }

ConformanceConstructionInfo(ProtocolDecl *item, SourceLoc loc,
TypeRepr *inheritedTypeRepr,
ConformanceAttributes attributes)
: Located(item, loc), attributes(attributes) {}
: Located(item, loc), inheritedTypeRepr(inheritedTypeRepr),
attributes(attributes) {}
};
}

Expand Down Expand Up @@ -210,8 +217,9 @@ void ConformanceLookupTable::forEachInStage(ConformanceStage stage,
loader.first->loadAllConformances(next, loader.second, conformances);
registerProtocolConformances(next, conformances);
for (auto conf : conformances) {
protocols.push_back(
{conf->getProtocol(), SourceLoc(), ConformanceAttributes()});
protocols.push_back({conf->getProtocol(), SourceLoc(),
/*inheritedTypeRepr=*/nullptr,
ConformanceAttributes()});
}
} else if (next->getParentSourceFile() ||
next->getParentModule()->isBuiltinModule()) {
Expand All @@ -220,7 +228,8 @@ void ConformanceLookupTable::forEachInStage(ConformanceStage stage,
for (const auto &found :
getDirectlyInheritedNominalTypeDecls(next, inverses, anyObject)) {
if (auto proto = dyn_cast<ProtocolDecl>(found.Item))
protocols.push_back({proto, found.Loc, found.attributes});
protocols.push_back(
{proto, found.Loc, found.inheritedTypeRepr, found.attributes});
}
}

Expand Down Expand Up @@ -292,8 +301,6 @@ void ConformanceLookupTable::updateLookupTable(NominalTypeDecl *nominal,
forEachInStage(
stage, nominal,
[&](NominalTypeDecl *nominal) {
auto source = ConformanceSource::forExplicit(nominal);

// Get all of the protocols in the inheritance clause.
InvertibleProtocolSet inverses;
bool anyObject = false;
Expand All @@ -303,10 +310,12 @@ void ConformanceLookupTable::updateLookupTable(NominalTypeDecl *nominal,
if (!proto)
continue;
auto kp = proto->getKnownProtocolKind();
assert(!found.isSuppressed ||
kp.has_value() &&
"suppressed conformance for non-known protocol!?");
assert(!found.isSuppressed ||
kp.has_value() &&
"suppressed conformance for non-known protocol!?");
if (!found.isSuppressed) {
auto source = ConformanceSource::forExplicit(
nominal, found.inheritedTypeRepr);
addProtocol(
proto, found.Loc, source.withAttributes(found.attributes));
}
Expand All @@ -318,11 +327,12 @@ void ConformanceLookupTable::updateLookupTable(NominalTypeDecl *nominal,
[&](ExtensionDecl *ext, ArrayRef<ConformanceConstructionInfo> protos) {
// The extension decl may not be validated, so we can't use
// its inherited protocols directly.
auto source = ConformanceSource::forExplicit(ext);
for (auto locAndProto : protos)
addProtocol(
locAndProto.Item, locAndProto.Loc,
source.withAttributes(locAndProto.attributes));
for (auto locAndProto : protos) {
auto source = ConformanceSource::forExplicit(
ext, locAndProto.inheritedTypeRepr);
addProtocol(locAndProto.Item, locAndProto.Loc,
source.withAttributes(locAndProto.attributes));
}
});
break;

Expand Down Expand Up @@ -976,12 +986,17 @@ ConformanceLookupTable::getConformance(NominalTypeDecl *nominal,
implyingConf = origImplyingConf->getRootNormalConformance();
}

TypeRepr *inheritedTypeRepr = nullptr;
if (entry->Source.getKind() == ConformanceEntryKind::Explicit) {
inheritedTypeRepr = entry->Source.getInheritedTypeRepr();
}

// Create or find the normal conformance.
auto normalConf = ctx.getNormalConformance(
conformingType, protocol, conformanceLoc, conformingDC,
ProtocolConformanceState::Incomplete,
entry->Source.getOptions(),
entry->Source.getPreconcurrencyLoc());
conformingType, protocol, conformanceLoc, inheritedTypeRepr,
conformingDC, ProtocolConformanceState::Incomplete,
entry->Source.getOptions());

// Invalid code may cause the getConformance call below to loop, so break
// the infinite recursion by setting this eagerly to shortcircuit with the
// early return at the start of this function.
Expand Down Expand Up @@ -1049,10 +1064,11 @@ void ConformanceLookupTable::registerProtocolConformance(

// Otherwise, add a new entry.
auto inherited = dyn_cast<InheritedProtocolConformance>(conformance);
ConformanceSource source
= inherited ? ConformanceSource::forInherited(cast<ClassDecl>(nominal)) :
synthesized ? ConformanceSource::forSynthesized(dc) :
ConformanceSource::forExplicit(dc);
ConformanceSource source =
inherited ? ConformanceSource::forInherited(cast<ClassDecl>(nominal))
: synthesized
? ConformanceSource::forSynthesized(dc)
: ConformanceSource::forExplicit(dc, /*inheritedEntry=*/nullptr);

ASTContext &ctx = nominal->getASTContext();
ConformanceEntry *entry = new (ctx) ConformanceEntry(SourceLoc(),
Expand Down
25 changes: 21 additions & 4 deletions lib/AST/ConformanceLookupTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,20 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
class ConformanceSource {
void *Storage;

/// The `TypeRepr` of the inheritance clause entry that declares this
/// conformance, if any. For example, if this is a conformance to `Y`
/// declared as `struct S: X, Y & Z {}`, this is the `TypeRepr` for `Y & Z`.
///
/// - Important: The value can be valid only for an explicit conformance.
TypeRepr *inheritedTypeRepr;

ConformanceEntryKind Kind;

ConformanceAttributes attributes;

ConformanceSource(void *ptr, ConformanceEntryKind kind)
: Storage(ptr), Kind(kind) { }
ConformanceSource(void *ptr, ConformanceEntryKind kind,
TypeRepr *inheritedTypeRepr = nullptr)
: Storage(ptr), inheritedTypeRepr(inheritedTypeRepr), Kind(kind) {}

public:
/// Create an inherited conformance.
Expand All @@ -109,8 +117,10 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
/// The given declaration context (nominal type declaration or
/// extension thereof) explicitly specifies conformance to the
/// protocol.
static ConformanceSource forExplicit(DeclContext *dc) {
return ConformanceSource(dc, ConformanceEntryKind::Explicit);
static ConformanceSource forExplicit(DeclContext *dc,
TypeRepr *inheritedEntry) {
return ConformanceSource(dc, ConformanceEntryKind::Explicit,
inheritedEntry);
}

/// Create an implied conformance.
Expand All @@ -134,6 +144,13 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
return ConformanceSource(dc, ConformanceEntryKind::PreMacroExpansion);
}

/// Return the `TypeRepr` of the inheritance clause entry that declares this
/// conformance, if any. For example, if this is a conformance to `Y`
/// declared as `struct S: X, Y & Z {}`, this is the `TypeRepr` for `Y & Z`.
///
/// - Important: The value can be valid only for an explicit conformance.
TypeRepr *getInheritedTypeRepr() const { return inheritedTypeRepr; }

/// Return a new conformance source with the given conformance
/// attributes.
ConformanceSource withAttributes(ConformanceAttributes attributes) {
Expand Down
10 changes: 6 additions & 4 deletions lib/AST/NameLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4091,7 +4091,8 @@ void swift::getDirectlyInheritedNominalTypeDecls(

// Form the result.
for (auto nominal : nominalTypes) {
result.push_back({nominal, loc, attributes, isSuppressed});
result.push_back({nominal, loc, inheritedTypes.getTypeRepr(i), attributes,
isSuppressed});
}
}

Expand Down Expand Up @@ -4124,8 +4125,8 @@ swift::getDirectlyInheritedNominalTypeDecls(
ConformanceAttributes attributes;
if (attr->isUnchecked())
attributes.uncheckedLoc = loc;
result.push_back(
{attr->getProtocol(), loc, attributes, /*isSuppressed=*/false});
result.push_back({attr->getProtocol(), loc, /*inheritedTypeRepr=*/nullptr,
attributes, /*isSuppressed=*/false});
}

// Else we have access to this information on the where clause.
Expand All @@ -4136,7 +4137,8 @@ swift::getDirectlyInheritedNominalTypeDecls(
// FIXME: Refactor SelfBoundsFromWhereClauseRequest to dig out
// the source location.
for (auto inheritedNominal : selfBounds.decls)
result.emplace_back(inheritedNominal, SourceLoc(), ConformanceAttributes(),
result.emplace_back(inheritedNominal, SourceLoc(),
/*inheritedTypeRepr=*/nullptr, ConformanceAttributes(),
/*isSuppressed=*/false);

return result;
Expand Down
Loading