-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Fix signature computation and caching involving type variables #18092
Conversation
d84d6d6
to
47b2f48
Compare
47b2f48
to
4870ade
Compare
17012c8
to
429dff2
Compare
else if (arity <= Definitions.MaxTupleArity) defn.TupleType(arity).nn | ||
private def erasePair(tp: Type)(using Context): Type | Null = { | ||
val arity = tupleArity(tp) | ||
if arity == -2 then null // erasure depends on an uninstantiated type variable or WildcardType |
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.
Elsewhere we make sure that we return null
only if inSigName
is true. But here it seems no such check is done.
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.
erasePair is called from TypeErasure#apply which does the null check, elsewhere we check that our input is not null which isn't necessary here with non-nullable types.
@@ -630,7 +671,10 @@ class TypeErasure(sourceLanguage: SourceLanguage, semiEraseVCs: Boolean, isConst | |||
else if sourceLanguage.isScala2 then | |||
this(Scala2Erasure.intersectionDominator(Scala2Erasure.flattenedParents(tp))) | |||
else | |||
erasedGlb(this(tp1), this(tp2)) | |||
val e1 = this(tp1) |
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.
can be streamlined by moving to WildcardType
instead of null
val e1 = this(tp1) | ||
val e2 = this(tp2) | ||
if e1 == null || e2 == null then null | ||
else TypeComparer.orType(e1, e2, isErased = true) |
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.
can be streamlined by moving to WildcardType
instead of null
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 switched to WildcardType but didn't find a better place to check for the special value than here, let me know if you had something specific in mind.
9a59970
to
4f1c26c
Compare
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.
LGTM
Its meaning will be expanded in the next commit.
During typechecking, we sometimes need to compute the signature of a type involving uninstantiated type variables or wildcards (in particular, because signature matching is doen as part of subtype checking for methods), this is handled with `tpnme.Uninstantiated` which is handled specially in `Signature`. Before this commit, `sigName` only checked for wildcards and type variables at the top-level of the type, even though nested types can still have an impact on type erasure, in particular when they appear as part of: - an intersection - a union - an underlying type of a derived value class - the element type of an array type - an element type of a tuple (... *: X *: ...) We keep track of all these situations by returning `null` in `TypeErasure#apply` when encountering a wildcard or uninstantiated type variable, which we then propage upwards to the `sigName` call. This propagation only happens for `sigName` calls (where `inSigName` is true), otherwise we throw an assertion since erasure shouldn't normally be computed for underdefined types.
Signatures should not be cached if they may change. The existing `isUnderDefined` check is not enough to guarantee this because the signature might have been computed based on a type variable instantiated in a TyperState which was subsequently retracted. To guard against this, we now rely on `Type#isProvisional`. This is a stricter check than needed since the provisional part of the type does not always have an impact on its signature, but in practice this is fine: when compiling Dotty, signature cache misses increased by less than 2%.
I'm afraid we'll have to get rid of it if we want our caches to be correct, but I don't have the time to look into it right now.
As requested in the review, this is slightly shorter but means we have to be very careful to not accidentally forget to propagate a WildcardType to the top-level when computing signatures.
4f1c26c
to
4499bc0
Compare
This fixes the logic taking the signature of a type uninstantiated type variables (using tpnme.Uninstantiated) and non-permanently instantiated type variables (which have a legitimate signature in the currenty TyperState but cannot be cached).
The test case was minimized from fmonniot/scala3mock#2.