Skip to content

Fix #16405 - wildcards prematurely resolving to Nothing #16625

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 1 commit into from
Jan 26, 2023

Conversation

jchyb
Copy link
Contributor

@jchyb jchyb commented Jan 5, 2023

Fixes #16405, which was a problem because it could it get in the way of some metaprogramming techniques. The main issue was the fact that when typing functions, the type inference would first look at the types from the source method (in decomposeProtoFunction resolving found type wildcards to Nothing) and only after that, it could look at the target method.
Now, in the case of wildcards we delay the resolution from the source method until later, after which it is resolved according to the target method (in inferredFromTarget). We also modify the target type resolution method (inferredFromTarget) a bit, as just applying the above procedure would fail some of the tests, eg.

val y1: Function1[_, Nothing] = x => x

would be typed as Function1[Any, Nothing], which was incorrect.

On a side note I will fully admit that that setting the minimizeSelected option for isFullyDefined inside of inferredFromTarget was a bit of an experiment on my part, nevertheless this seems to instantly work well with all of the test cases. An alternative would be to allow decomposeProtoFunction to proceed as before, and check if formal type was resolved to Nothing in inferredFromTarget and handle things that way, but that brings with itself its own set of issues (that I hope to avoid here).

@jchyb
Copy link
Contributor Author

jchyb commented Jan 6, 2023

Never mind, forgot about the bootstrapped only tests, I'll look into this tomorrow

@jchyb jchyb force-pushed the fix-i16405 branch 4 times, most recently from c6b8e2c to f595650 Compare January 9, 2023 15:41
@jchyb
Copy link
Contributor Author

jchyb commented Jan 11, 2023

Changed approach due to the above one failing the tests.
Now we check for wildcards in decomposeProtoFunction, and if detected we change order in what we use to try to infer types (to simplify: by default first we try protoFormal, then to infer from target, however with wildcard we try first to infer from target and only then protoFormal).

This was a problem because it could it get in the way of some
metaprogramming techniques. The main issue was the fact that when typing
functions, the type inference would first look at the types from the
source method (resolving type wildcards to Nothing) and only after that,
it could look at the target method.
Now, in the case of wildcards we save that fact for later (while still
resolving the prototype parameter to Nothing) and we in that case we
prioritize according to the target method, after which we fallback to
the default procedure.
@jchyb jchyb marked this pull request as ready for review January 11, 2023 11:46
@Kordyjan Kordyjan requested a review from odersky January 13, 2023 17:30
@odersky
Copy link
Contributor

odersky commented Jan 23, 2023

It looks a bit heavyweight for what it achieves, but I am not yet sure whether one can do better. I'll take a closer look.

@odersky
Copy link
Contributor

odersky commented Jan 24, 2023

@jchyb Can you push it to staging please? That makes it easier for me to work with it.

@jchyb
Copy link
Contributor Author

jchyb commented Jan 24, 2023

Done: https://github.com/dotty-staging/dotty/tree/fix-i16405
Should I reopen this PR with the different source?

Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach is correct, but I think it can be simplified.

@@ -1206,7 +1207,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
* def double(x: Char): String = s"$x$x"
* "abc" flatMap double
*/
private def decomposeProtoFunction(pt: Type, defaultArity: Int, pos: SrcPos)(using Context): (List[Type], untpd.Tree) = {
private def decomposeProtoFunction(pt: Type, defaultArity: Int, pos: SrcPos)(using Context): (List[Type], Option[List[Boolean]], untpd.Tree) = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I found another way that achieves the same outcome and that does not need a separate list of booleans.

// wildcards, in case of types inferred from target being more specific

val fromWildcards = pt1.argInfos.init.map{
case bounds @ TypeBounds(nt, at) if nt == defn.NothingType && at == defn.AnyType => true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type comparison via == or != is not recommended. One can compare types with eq, or pattern match the structure and compare symbols. Or else use =:= which is semantic equality, equivalent to mutual subtyping, but this one is expensive.

In this specific case, there are detectors for the types your are interested in: isExactlyNothing and isAny.

// from below, then in the case that the argument was also identified as
// a wildcard type we try to prioritize inferring from target, if possible.
// See issue 16405 (tests/run/16405.scala)
val (usingFormal, paramType) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why usingFormal is needed here. Why isknownFormal not the right condition?

odersky added a commit that referenced this pull request Jan 26, 2023
Fixes #16405, which was a
problem because it could it get in the way of some metaprogramming
techniques. The main issue was the fact that when typing functions, the
type inference would first look at the types from the source method (in
decomposeProtoFunction resolving found type wildcards to Nothing) and
only after that, it could look at the target method.

This is a continuation and simplification of #16625
@odersky odersky merged commit 4523a6f into scala:main Jan 26, 2023
@Kordyjan Kordyjan added this to the 3.3.1 milestone Aug 1, 2023
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.

Argument types are erroneously inferred as Nothing when a method is explicitly eta-expanded as in f(_)
3 participants