Sema: fix cycle involving protocol extensions

We've had a load-bearing optimization that avoids rebuilding
the generic signature of the protocol extension to avoid a
cycle involving a typealias in the extension that's mentioned
as a requirement in the protocol, for example:

```
public protocol P3 {
  associatedtype A
}

public protocol P4: P3 where A == B {}

extension P4 {
  public typealias B = String
}
```
What was happening is that we're inside a
StructuralRequirementsRequest, so we're performing TypeResolution
in the Structural stage. When we encounter this typealias that's
within a protocol extension, we'd end up accidentally requesting
the interface type of that alias through
`TypeChecker::substMemberTypeWithBase`
(via `aliasDecl->getUnderlyingType()`).

What we should do instead is call `aliasDecl->getStructuralType()`
and skip the substitution during that stage.

There happened to already be code doing this, but it was only
kicking in if the DeclContext `isa<ProtocolDecl>`, which excludes
extensions of a protocol. I see no reason why extensions of a
protocol should be excluded, so I assume it was unintentional.

Thus, the fix boils down to using `DeclContext::getSelfProtocolDecl`
which does include extensions of protocols. With this fix, the
optimization is no longer load-bearing on the example above.

See the history of this hack in f747121080
or rdar://problem/129540617
This commit is contained in:
Kavon Farvardin
2025-12-02 21:08:10 -08:00
parent c9d2f522c0
commit ab1d83a31b
5 changed files with 31 additions and 8 deletions

View File

@@ -591,11 +591,10 @@ Type TypeResolution::resolveTypeInContext(TypeDecl *typeDecl,
typeDecl = assocType->getAssociatedTypeAnchor();
} else if (auto *aliasDecl = dyn_cast<TypeAliasDecl>(typeDecl)) {
if (isa<ProtocolDecl>(typeDecl->getDeclContext()) &&
getStage() == TypeResolutionStage::Structural) {
if (aliasDecl && !aliasDecl->hasGenericParamList()) {
return adjustAliasType(aliasDecl->getStructuralType());
}
if (typeDecl->getDeclContext()->getSelfProtocolDecl() &&
getStage() == TypeResolutionStage::Structural &&
!aliasDecl->hasGenericParamList()) {
return adjustAliasType(aliasDecl->getStructuralType());
}
}

View File

@@ -21,3 +21,16 @@ struct S<T: P, U: P<T.A>> {
func f<V>(_: V) where V == T.A {}
// expected-error@-1 {{'A' was defined in extension of protocol 'P' and cannot be referenced from a 'where' clause}}
}
public protocol K3: ~Copyable {
associatedtype A
}
// This shouldn't work because the extension defining B is implicitly constrained to K4's where Self: Copyable
public protocol K4: K3
where A == B, // expected-error {{type 'Self' does not conform to protocol 'Copyable'}}
Self: ~Copyable {}
extension K4 {
public typealias B = String
}

View File

@@ -39,4 +39,15 @@ public protocol P4: P3 where A == B {}
extension P4 {
public typealias B = String
}
}
// This is additionally terrible
public protocol K3: ~Copyable {
associatedtype A
}
public protocol K4: K3 where A == B, Self: ~Copyable {}
extension K4 where Self: ~Copyable {
public typealias B = String
}

View File

@@ -1,5 +1,5 @@
// {"kind":"complete","signature":"swift::GenericSignatureImpl::requiresProtocol(swift::Type, swift::ProtocolDecl*) const"}
// RUN: not --crash %target-swift-ide-test -code-completion -batch-code-completion -skip-filecheck -code-completion-diagnostics -source-filename %s
// RUN: %target-swift-ide-test -code-completion -batch-code-completion -skip-filecheck -code-completion-diagnostics -source-filename %s
protocol a {
associatedtype b
}

View File

@@ -1,5 +1,5 @@
// {"kind":"typecheck","original":"3d2e2125","signature":"swift::SubstitutionMap::lookupConformance(swift::CanType, swift::ProtocolDecl*) const"}
// RUN: not --crash %target-swift-frontend -typecheck %s
// RUN: not %target-swift-frontend -typecheck %s
protocol a {
associatedtype b where c: d
}