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

AST owner loss during a type match #17192

Closed
ftucky opened this issue Mar 31, 2023 · 6 comments · Fixed by #18765
Closed

AST owner loss during a type match #17192

ftucky opened this issue Mar 31, 2023 · 6 comments · Fixed by #18765

Comments

@ftucky
Copy link

ftucky commented Mar 31, 2023

Compiler version

3.2.2, 3.3.0-RC3

Minimized code

class Ifce[BT <: Boolean] extends Selectable:
  type RT = BT match {
    case true  => this.type  { val v1: Int }
    case false => this.type
  }
  def cast : RT = this.asInstanceOf[RT]
  def selectDynamic(key:String) : Any = ???

val full = (new Ifce[true]).cast

println(s"full.v1 = ${full.v1}")

Output

scala java.lang.AssertionError: assertion failed: no owner from / in Playground.full.v1

java.lang.AssertionError: assertion failed: no owner from <span class="ansi-color-yellow"></span> <span class="ansi-color-magenta">&lt;none&gt;</span>/<span class="ansi-color-yellow"></span> <span class="ansi-color-magenta">&lt;none&gt;</span> in Playground.full.v1
	at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
	at dotty.tools.dotc.transform.Erasure$Typer.typedSelect(Erasure.scala:716)
	at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:2897)
	at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2990)
	at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:126)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3058)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3062)
	at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3174)
	at dotty.tools.dotc.transform.Erasure$Typer.$anonfun$7(Erasure.scala:844)
	at dotty.tools.dotc.core.Decorators$.zipWithConserve(Decorators.scala:155)
	at dotty.tools.dotc.transform.Erasure$Typer.typedApply(Erasure.scala:844)
	at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2928)
	at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2991)
	at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:126)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3058)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3062)
	at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3174)
	at dotty.tools.dotc.transform.Erasure$Typer.$anonfun$7(Erasure.scala:844)
	at dotty.tools.dotc.core.Decorators$.zipWithConserve(Decorators.scala:155)
	at dotty.tools.dotc.transform.Erasure$Typer.typedApply(Erasure.scala:844)
	at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2928)
	at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2991)
	at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:126)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3058)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3062)
	at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3174)
	at dotty.tools.dotc.typer.Typer.typedValDef(Typer.scala:2326)
	at dotty.tools.dotc.transform.Erasure$Typer.typedValDef(Erasure.scala:901)
	at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:2901)
	at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2990)
	at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:126)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3058)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3062)
	at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:3084)
	at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:3130)
	at dotty.tools.dotc.transform.Erasure$Typer.typedStats(Erasure.scala:1047)
	at dotty.tools.dotc.typer.Typer.typedBlockStats(Typer.scala:1096)
	at dotty.tools.dotc.typer.Typer.typedBlock(Typer.scala:1100)
	at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2936)
	at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2991)
	at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:126)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3058)
	at dotty.tools.dotc.typer.Typer.typed(Typer.scala:3062)
	at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:3174)
	at dotty.tools.dotc.transform.Erasure$Typer.$anonfun$7(Erasure.scala:844)
	at dotty.tools.dotc.core.Decorators$.zipWithConserve(Decorators.scala:155)
	at dotty.tools.dotc.transform.Erasure$Typer.typedApply(Erasure.scala:844)
	at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:2928)
	at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:2991)
	at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:126)

Observations

The crash is related to the type match. Following code does not crash.

class Ifce[BT <: Boolean] extends Selectable:
  type RT = this.type & {val v1 : Int}
  def cast : RT = this.asInstanceOf[RT]
  def selectDynamic(key:String) : Any = ???

When the type match is defined outside the class, no crash either.

type RT[BT] = BT match {
    case true  => { val v1: Int }
    case false => {}
  }
class Ifce[BT <: Boolean] extends Selectable:
  def cast : this.type & RT[BT] = this.asInstanceOf[this.type & RT[BT]]
  def selectDynamic(key:String) : Any = ???

extending trait Selectable is not involved. Following code does crash.

class Ifce[BT <: Boolean]:
  type RT = BT match {
    case true  => this.type  { val v1: Int }
    case false => this.type
  }
  def cast : RT = this.asInstanceOf[RT]
@ftucky ftucky added itype:bug itype:crash stat:needs triage Every issue needs to have an "area" and "itype" label labels Mar 31, 2023
@Decel
Copy link
Contributor

Decel commented Mar 31, 2023

The problem is

class C[A]:
  type T = A match
    case true  => this.type
  implicitly[this.type =:= T]

val a = new C[true]

This leads us to have

-- [E172] Type Error: ------------------------------------------------------------------
4 |  implicitly[this.type =:= T]
  |                             ^
  |                           Cannot prove that (C.this : C[A]) =:= C.this.T.

The most interesting thing is:

class C[A]:
  type X = A match
    case _ => this.type
  implicitly[X =:= this.type]

val a = new C[true] 

Will work. We can use anything instead of true, as long as we have an anonymous type parameter or scrutinee.

So moving match types outside simply don't do anything.

@ftucky
Copy link
Author

ftucky commented Mar 31, 2023

Hi Decel,

I am not sure to understand what you are suggesting.

The two snippets you suggests, do behave as expected.
AA:

class C[A]:
  type T = A match
    case true  => this.type
  implicitly[this.type =:= T] // Fails

BB:

class C[A]:
  type T = A match
    case _ => this.type
  implicitly[this.type =:= T] // Works

implicitly is part of class C[A] constructor which is not (and cannot be) inlined.
Thus, the evidence for this.type =:=T must be found for any A.
in example (AA), this is false: equivalence is only true when A <: true.
in example (BB), this is true.

What is the relation with the compiler crash (assertion failure on missing AST Symbol owner) described in the ticket ?

@ftucky ftucky closed this as completed Mar 31, 2023
@ftucky
Copy link
Author

ftucky commented Mar 31, 2023

Oops, closed by mistake, reopening.

@ftucky ftucky reopened this Mar 31, 2023
@Decel
Copy link
Contributor

Decel commented Mar 31, 2023

implicitly is part of class C[A] constructor which is not (and cannot be) inlined. Thus, the evidence for this.type =:=T must be found for any A. in example (AA), this is false: equivalence is only true when A <: true. in example (BB), this is true.

What is the relation with the compiler crash (assertion failure on missing AST Symbol owner) described in the ticket ?

The problem is, even if we add a case for non-true cases in AA:

class C[A]:
  type X = A match
    case true  => true
    case _ => true
  type Y = A match
    case _ => true
  implicitly[X =:= Y]

These match types are equivalent for any type of A, but we still get an error.

We could fix your snippet in the same fashion:

class Ifce[BT <: Boolean] extends Selectable:
  type RT = BT match {
    case _  => this.type  { val v1: Int }
  }
  def cast : RT = this.asInstanceOf[RT]
  def selectDynamic(key:String) : Any = ???

val full = (new Ifce[true]).cast

println(s"full.v1 = ${full.v1}")

@ftucky
Copy link
Author

ftucky commented Mar 31, 2023

Mhhh, not convinced.

Code below fails :

class Ifce[BT <: Boolean]:
  type RT = BT match {
    case true  => this.type  { val v1: Int }
  }
  def cast : RT = this.asInstanceOf[RT]

val full /*: Ifce[true] {val v1:Int} */ = (new Ifce[true]).cast

println(s"full.v1 = ${full.v1}")

But doing the type ascription (removing the comment) makes it compile.
That means that the compiler does resolve the match type to the correct refinement.
I suspect the owner of the symbol in the AST is not correctly set during the resolution of the match type.
Doing the ascription succeeds, and the compiler then uses the (correct) owner of the ascription, and does not crash.

It is very possible that other resolutions of the match type (for instance those which always succeed, and may be resolved in an earlier phase, when the type parameter is still unknown) do not get corrupted.

Anyway, a failing type-conformance test should not result in a compiler crash.

@mbovel mbovel added area:match-types and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Apr 3, 2023
@sjrd
Copy link
Member

sjrd commented Apr 20, 2023

Interestingly, if we change the definition of type RT to one of

  type RT =
    this.type { val v1: Int }

or

  type RT = BT match {
    case _ => this.type { val v1: Int }
  }

we get the following compile error:

-- [E007] Type Mismatch Error: tests/run/hello.scala:16:13 ---------------------
16 |    val v1 = full.v1
   |             ^^^^
   |             Found:    (full : Ifce[(true : Boolean)]#RT)
   |             Required: Selectable | Dynamic
   |
   |             The following import might fix the problem:
   |
   |               import reflect.Selectable.reflectiveSelectable
   |
   |
   | longer explanation available when compiling with `-explain`

So my feeling is that it's not really an owner chain issue (in fact that would be very surprising because Types are not AST based and don't have owners; only Trees and Symbols do). Instead, it is the Selectable treatment that fails to kick in on the selection of full.v1.

Indeed, after typer on the minimization of the previous post, we still see a bare selection

  final module class Test() extends Object() { this: Test.type =>
    def main(args: Array[String]): Unit =
      {
        val full: Ifce[(true : Boolean)]#RT = new Ifce[true.type]().cast
        val v1: Int = full.v1
      }
  }

which is a Select of a term member that only belongs in a refinement. That kind of Select is not supposed to survive the typer, and is why erasure crashes. Instead, we would expect something like

val v1: Int = reflect.Selectable.reflectiveSelectable(full).selectDynamic("v1").$asInstanceOf[Int]

assuming we added the correct import of

import reflect.Selectable.reflectiveSelectable

In conclusion, while a match type is required to trigger the crash, I believe the problem is situated more in the treatment of Selectables.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants