Skip to content

RequirementMachine: Tighten up type witness recursion hack #39673

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

Conversation

slavapestov
Copy link
Contributor

@slavapestov slavapestov commented Oct 10, 2021

When a type parameter is subject to a conformance requirement and a concrete type requirement, the concrete type unification pass recursively walks each nested type introduced by the conformance requirement, and fixes it to the concrete type witness in the concrete type conformance.

In general, this can produce an infinite sequence of concrete type requirements. There are a couple of heuristics to "tie off" the recursion.

One heuristic is that if a nested type of a concrete parent type is exactly equal to the parent type, a same-type requirement is introduced between the child and the parent, preventing concrete type unification from recursing further.

This used to check for exact equality of types, but it is possible for the type witness to be equal under canonical type equivalence, but use a different spelling via same-type requirements for some type parameter appearing in structural position.

Instead, canonicalize terms here, allowing recursion where the type witness names a different spelling of the same type.

Here is an example:

public protocol P1 {
  associatedtype A: P1 where A.A == A
}

public protocol P2 {
  associatedtype B: P1
  associatedtype C: P2
}

public struct G<B : P1> : P2 {
  public typealias C = G<B.A>
}

public protocol P3: P2 where C == G<B> {}

The requirement C == G<B> in protocol P3 together with the definition of G<B>.C implies an infinite series of rules:

C.C == G<B.A>
C.C.C == G<B.A.A>
C.C.C.C == G<B.A.A.A>
...

This would normally prevent the completion procedure from terminating, however A.A == A in protocol P1 means that the right hand sides simplify as follows:

C.C == G<B.A>
C.C.C == B<G.A>
C.C.C.C == G<B.A>
...

Therefore, the single rule C.C == C.C.C suffices to "tie off" the recursion.

Fixes rdar://problem/83894546.

When a type parameter is subject to a conformance requirement and a
concrete type requirement, the concrete type unification pass
recursively walks each nested type introduced by the conformance
requirement, and fixes it to the concrete type witness in the
concrete type conformance.

In general, this can produce an infinite sequence of concrete type
requirements. There are a couple of heuristics to "tie off" the
recursion.

One heuristic is that if a nested type of a concrete parent type is
exactly equal to the parent type, a same-type requirement is
introduced between the child and the parent, preventing concrete
type unification from recursing further.

This used to check for exact equality of types, but it is possible
for the type witness to be equal under canonical type equivalence,
but use a different spelling via same-type requirements for some
type parameter appearing in structural position.

Instead, canonicalize terms here, allowing recursion where the
type witness names a different spelling of the same type.

Fixes <rdar://problem/83894546>.
@slavapestov slavapestov force-pushed the rqm-type-witness-recursion-hack branch from 37461de to 576845d Compare October 10, 2021 04:26
@slavapestov
Copy link
Contributor Author

@swift-ci Please smoke test

@slavapestov
Copy link
Contributor Author

@swift-ci Please test source compatibility

@slavapestov slavapestov merged commit 47d02d2 into swiftlang:main Oct 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant