Skip to content
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

Match type reduction failure when applied to a type containing a bounded wildcard #19607

Closed
Bersier opened this issue Feb 4, 2024 · 3 comments

Comments

@Bersier
Copy link
Contributor

Bersier commented Feb 4, 2024

Compiler version

3.4.0-RC4

Minimized code

trait Foo
trait Bar[T]

type MatchType[T] = T match
  case Bar[?] => Nothing
  case _ => T

def foo(b: Bar[? >: Foo]): MatchType[b.type] = ???

def bar(b: Bar[? >: Foo]): Nothing = foo(b)

Compiler output

Found:    MatchType[(b : Bar[? >: Foo])]
Required: Nothing

Note: a match type could not be fully reduced:

  trying to reduce  MatchType[(b : Bar[? >: Foo])]
  failed since selector (b : Bar[? >: Foo])
  does not match  case Bar[_] => Nothing
  and cannot be shown to be disjoint from it either.
  Therefore, reduction cannot advance to the remaining case

    case _ => (b : Bar[? >: Foo])

Expectation

No compile error

@Bersier Bersier added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Feb 4, 2024
@sjrd sjrd self-assigned this Feb 4, 2024
@sjrd
Copy link
Member

sjrd commented Feb 5, 2024

Ah, that turns out to be an interesting one. The behavior is actually according to spec. If we look at the Matching algorithm at https://docs.scala-lang.org/sips/match-types-spec.html#matching, we can see what happens. We are trying to (b : Bar[? >: Foo <: Any]) against Bar[?]. The ? on the right is a wildcard capture, and Bar is invariant. Therefore, the following step applies:

  • If P is a WildcardCapture ti = _:
    • If X is of the form _ >: L <: H, instantiate ti := H (anything between L and H would work here),

So it picks Any for the wildcard capture. Then it tests is Bar[? >: Foo <: Any] <:< Bar[Any], and the answer is false because Bar is invariant.

I don't think there is a good solution to this problem. There is nothing that the matching algorithm could pick that would work. Neither Foo nor Any, nor anything in between, will actually work here, because we have a wildcard type parameter on the left.

The real solution would be to consider ? in patterns as wildcard type args instead of wildcard captures. That would be changing the language, however.

sjrd added a commit to dotty-staging/dotty that referenced this issue Feb 5, 2024
@sjrd
Copy link
Member

sjrd commented Feb 5, 2024

Or maybe I can make it work: main...dotty-staging:dotty:make-i19607-work ... although it still involves a (minor) change of spec.

We might need to discuss that at the next SIP meeting, to see whether we could still accommodate it. /cc @anatoliykmetyuk

@sjrd
Copy link
Member

sjrd commented Feb 5, 2024

Perhaps we can consider this a "spec bug", since the intent of the spec was clearly to pick something that would always work (see text in parentheses), but picking hi does not actually always work. The solution I have is to pick the wildcard type argument itself.

sjrd added a commit to sjrd/improvement-proposals that referenced this issue Feb 6, 2024
As demonstrated by scala/scala3#19607,
picking `H` does *not* in fact always work. It was always the
intent of the spec to pick an instantiation that would make
subtyping work, but the particular choice was wrong.

IMO this is therefore clearly a spec bug, and we can address it
irrespective of compatibility concerns.
@Gedochao Gedochao added area:match-types area:spec and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Feb 9, 2024
odersky added a commit that referenced this issue Feb 15, 2024
…ds. (#19627)

When matching in a match type, if we encounter a `TypeBounds` scrutinee
and we have a wildcard capture on the right, we used to pick the `hi`
bound "because anything between between `lo` and `hi` would work".

It turns out that *nothing* between `lo` and `hi` works when the type
constructor is invariant. Instead, we must be keep the type bounds, and
instantiate the wildcard capture to a wildcard type argument.

This is fine because a wildcard capture can never be referred to in the
body of the case. However, previously this could never happen in
successful cases, and we therefore used the presence of a `TypeBounds`
in the `instances` as the canonical signal for "fail as not specific".
We now use a separate `noInstances` list to be that signal.

This change departs from the letter of the spec but not from its spirit.
As evidenced by the wording, the spec always *intended* for "the pick"
to one that would always succeed. We wrongly assumed `hi` was always
working.

---

Companion PR to fix the spec/SIP:
scala/improvement-proposals#77
odersky pushed a commit to odersky/improvement-proposals that referenced this issue Jul 18, 2024
As demonstrated by scala/scala3#19607,
picking `H` does *not* in fact always work. It was always the
intent of the spec to pick an instantiation that would make
subtyping work, but the particular choice was wrong.

IMO this is therefore clearly a spec bug, and we can address it
irrespective of compatibility concerns.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants