-
Notifications
You must be signed in to change notification settings - Fork 10.5k
SE-0309: Covariant erasure for dependent member types #39492
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
Conversation
lib/AST/Type.cpp
Outdated
continue; | ||
} | ||
inputInfo |= ::findExistentialSelfReferences( | ||
sig, param.getParameterType(), TypePosition::Contravariant); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't quite right. In ((T) -> U) -> V
, the input (T) -> U
is contravariant, so (T)
is covariant and U
is contravariant in those positions. In general, recurring under the input side of an arrow flips variance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is the entry point for a member ValueDecl
. We do flip variance in the recursive findExistentialSelfReferences
that takes a type and a relative variance position.
eca1fae
to
9f21b12
Compare
9f21b12
to
f053281
Compare
f053281
to
7f3d9b3
Compare
dcf6b2f
to
761b98f
Compare
761b98f
to
371b7a0
Compare
@swift-ci please smoke test |
auto gather = ExpansionGatherer{fn}; | ||
Type transformedPat = expand->getPatternType().transformRec(gather); | ||
Type transformedPat = | ||
expand->getPatternType().transformWithPosition(pos, gather); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CodaFi I am intuitively treating packs as invariant (because they seem like they cannot appear within another type), and expansions as covariant here; let me know if anything sounds fishy about that.
@swift-ci please smoke test macOS |
@swift-ci please clean smoke test macOS |
371b7a0
to
c627a2c
Compare
@swift-ci please smoke test |
c627a2c
to
4581c95
Compare
@swift-ci please smoke test |
@swift-ci please test source compatibility |
9f24e75
to
891689f
Compare
@swift-ci please smoke test |
891689f
to
1723baf
Compare
@swift-ci please smoke test |
@swift-ci please clean smoke test Linux |
@swift-ci please test source compatibility |
@slavapestov This is finally ready for another review. |
@swift-ci please smoke test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't reviewed earlier drafts of this pull request, but in it's current state it's looking fantastic. I have just a few review comments, but nothing that would prevent us from merging this.
@@ -616,7 +616,7 @@ class alignas(1 << TypeAlignInBits) TypeBase | |||
|
|||
/// Determine whether the type involves the given opened existential | |||
/// archetype. | |||
bool hasOpenedExistential(OpenedArchetypeType *opened); | |||
bool hasOpenedExistentialWithRoot(const OpenedArchetypeType *root) const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was looking at this recently, and wondering whether the GenericEnvironment
itself is a better discriminator than the root archetype for the long term. Right now, any opened existential always has a single generic parameter at the top level, but it's not inconceivable that we would want to allow matching existentials to structural types with type parameters, e.g., x as <T, U>(T, U)
to open something as a 2-tuple. In such cases, the root no longer uniquely identifies the opened existential. We don't have to make this change to use GenericEnvironment
---your change here makes sure clients are properly reasoning about the roots so they handle nested archetypes properly, which is a huge step forward.
return mapTypeIntoContext(type, conformanceLookupFn); | ||
|
||
if (type->hasTypeParameter()) { | ||
return mapTypeIntoContext(type, conformanceLookupFn); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why we need this check for type parameters here. Are we tripping the assertion in mapTypeIntoContext
in some way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, we trip it when we encounter an archetype from somewhere else. Consider the following:
class Class<A>: P {}
protocol P {
associatedtype A
}
protocol Q: P {
func method(_: A)
}
extension Q {
func test(arg: Q & Class<Self>) {
arg.method(self) // OK
}
}
The opened archetype signature for Q & Class<Self>
is <Self where Self : Q, Self : Class<archetype>>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We were discussing this internally and we concluded the best approach is to model the opened existential's environment as being derived from the outer generic signature. So in your example, it becomes
<A, Self where Self : Q, Self : Class<A>>
However this is a big refactoring and outside of the scope of this PR.
// building a root OpenedArchetypeType, and the latter when building | ||
// nested archetypes. | ||
// For compatibility, continue using the existential layout's version when | ||
// the interface type is a generic parameter. We should align these at |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good choice; we might as well get the better answer for nested types.
lib/Sema/ConstraintSystem.cpp
Outdated
@@ -1727,6 +1727,103 @@ static bool isMainDispatchQueueMember(ConstraintLocator *locator) { | |||
return true; | |||
} | |||
|
|||
/// Type-erase occurences of covariant 'Self'-rooted type parameters to their |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo "occurences"
lib/Sema/ConstraintSystem.cpp
Outdated
return None; | ||
}); | ||
|
||
// Then, type-erase occurences of covariant 'Self'-rooted type parameters. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo occurences
. This three-step transform makes a ton of sense. getTypeOfMemberReference
is getting huge, so at some point I'd like us to factor the transform out into a separate function.
@@ -5686,6 +5687,12 @@ class OpenedArchetypeType final : public ArchetypeType, | |||
/// Retrieve the opened existential type | |||
Type getOpenedExistentialType() const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is a bit of a footgun, because even on a nested opened archetype it still returns the top-level existential type, rather than the erased type for this particular archetype. We probably want to do a quick audit of clients to ensure that they are only using this on the root archetype, and consider renaming the API (getOpenedExistentialTypeAtRoot
or similar) or removing it entirely (clients can ask for the opened existential type on the generic environment, which is obviously shared amongst all archetypes in that opened existential).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noticed this too; I will explore your suggestions in a follow-up.
Ah, looks like some minor bit-rot due to the change in the signature of |
@DougGregor already at it! |
While we're here, also tidy up the documentation for the type transform APIs
…uild "nested" opened archetypes
1723baf
to
9bbe2f9
Compare
@swift-ci please smoke test |
…existential member availability
…in requirement failure messages
…ExistentialSelfReferences'
9bbe2f9
to
6a25198
Compare
@swift-ci please smoke test |
Thanks to all for reviewing! |
This change enables us to access existential members when their type signatures, as seen in the context of the base type, reference
Self
-rooted associated types only in covariant positions, by type-erasing these references to their upper bounds. This implies that you can use a protocol value to call a method that returns an associated type, or a method that accepts one if the associated type is bound to a fully concrete type via generic requirements on the protocol. Accessing these members becomes possible because their types are adjusted so as to become well-defined from outside their protocol context, which is key to type-safe member accesses on a type-erased value.