Skip to content

Structural selection on intersection type leads to crash #2871

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

Closed
LPTK opened this issue Jul 14, 2017 · 6 comments
Closed

Structural selection on intersection type leads to crash #2871

LPTK opened this issue Jul 14, 2017 · 6 comments

Comments

@LPTK
Copy link
Contributor

LPTK commented Jul 14, 2017

The following code:

class Cont[A0](x0: A0) { type A = A0; val x: A = x0 }
val c: { type A; val x: A } & { type A = Int } = new Cont(1)
c.x : Int

... makes the compiler crash with:

Exception in thread "main" java.lang.AssertionError: NoDenotation.owner
        at dotty.tools.dotc.core.SymDenotations$NoDenotation.owner(SymDenotations.scala:1816)
        at dotty.tools.dotc.transform.Erasure$Typer.mapOwner$1(Erasure.scala:360)
        at dotty.tools.dotc.transform.Erasure$Typer.typedSelect(Erasure.scala:364)
        at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:1606)
        at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:1671)
        at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:94)
        at dotty.tools.dotc.typer.Typer$$anonfun$typed$2.apply(Typer.scala:1687)
        at dotty.tools.dotc.typer.Typer$$anonfun$typed$2.apply(Typer.scala:1685)
        at dotty.tools.dotc.reporting.Reporting$class.traceIndented(Reporter.scala:140)
        at dotty.tools.dotc.core.Contexts$Context.traceIndented(Contexts.scala:57)
        at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1685)
        at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:1720)
        at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:1732)
        at dotty.tools.dotc.transform.Erasure$Typer.typedStats(Erasure.scala:608)
        at dotty.tools.dotc.typer.Typer$$anonfun$typedClassDef$1.apply(Typer.scala:1398)
        at dotty.tools.dotc.typer.Typer$$anonfun$typedClassDef$1.apply(Typer.scala:1328)
        at dotty.tools.dotc.util.Stats$.track(Stats.scala:35)
        at dotty.tools.dotc.typer.Typer.typedClassDef(Typer.scala:1328)
        at dotty.tools.dotc.typer.Typer.typedNamed$1(Typer.scala:1616)
        at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:1671)
        at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:94)
        at dotty.tools.dotc.typer.Typer$$anonfun$typed$2.apply(Typer.scala:1687)
        at dotty.tools.dotc.typer.Typer$$anonfun$typed$2.apply(Typer.scala:1685)
        at dotty.tools.dotc.reporting.Reporting$class.traceIndented(Reporter.scala:140)
        at dotty.tools.dotc.core.Contexts$Context.traceIndented(Contexts.scala:57)
        at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1685)
        at dotty.tools.dotc.typer.Typer.traverse$1(Typer.scala:1709)
        at dotty.tools.dotc.typer.Typer.typedStats(Typer.scala:1732)
        at dotty.tools.dotc.transform.Erasure$Typer.typedStats(Erasure.scala:608)
        at dotty.tools.dotc.typer.Typer$$anonfun$typedPackageDef$1.apply(Typer.scala:1515)
        at dotty.tools.dotc.typer.Typer$$anonfun$typedPackageDef$1.apply(Typer.scala:1502)
        at dotty.tools.dotc.util.Stats$.track(Stats.scala:35)
        at dotty.tools.dotc.typer.Typer.typedPackageDef(Typer.scala:1502)
        at dotty.tools.dotc.typer.Typer.typedUnnamed$1(Typer.scala:1655)
        at dotty.tools.dotc.typer.Typer.typedUnadapted(Typer.scala:1672)
        at dotty.tools.dotc.typer.ReTyper.typedUnadapted(ReTyper.scala:94)
        at dotty.tools.dotc.typer.Typer$$anonfun$typed$2.apply(Typer.scala:1687)
        at dotty.tools.dotc.typer.Typer$$anonfun$typed$2.apply(Typer.scala:1685)
        at dotty.tools.dotc.reporting.Reporting$class.traceIndented(Reporter.scala:140)
        at dotty.tools.dotc.core.Contexts$Context.traceIndented(Contexts.scala:57)
        at dotty.tools.dotc.typer.Typer.typed(Typer.scala:1685)
        at dotty.tools.dotc.typer.Typer.typedExpr(Typer.scala:1744)
        at dotty.tools.dotc.transform.Erasure.run(Erasure.scala:95)
        at dotty.tools.dotc.core.Phases$Phase$$anonfun$runOn$1.apply(Phases.scala:283)
        at dotty.tools.dotc.core.Phases$Phase$$anonfun$runOn$1.apply(Phases.scala:281)
        at scala.collection.immutable.List.map(List.scala:284)
        at dotty.tools.dotc.core.Phases$Phase$class.runOn(Phases.scala:281)
        at dotty.tools.dotc.transform.Erasure.runOn(Erasure.scala:33)
        at dotty.tools.dotc.Run$$anonfun$compileUnits$1$$anonfun$apply$mcV$sp$1.apply(Run.scala:82)
        at dotty.tools.dotc.Run$$anonfun$compileUnits$1$$anonfun$apply$mcV$sp$1.apply(Run.scala:79)
        at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
        at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:186)
        at dotty.tools.dotc.Run$$anonfun$compileUnits$1.apply$mcV$sp(Run.scala:79)
        at dotty.tools.dotc.Run$$anonfun$compileUnits$1.apply(Run.scala:67)
        at dotty.tools.dotc.Run$$anonfun$compileUnits$1.apply(Run.scala:67)
        at dotty.tools.dotc.util.Stats$.monitorHeartBeat(Stats.scala:76)
        at dotty.tools.dotc.Run.compileUnits(Run.scala:67)
        at dotty.tools.dotc.Run.compileSources(Run.scala:64)
        at dotty.tools.dotc.Run.compile(Run.scala:48)
        at dotty.tools.dotc.Driver.doCompile(Driver.scala:26)
        at dotty.tools.dotc.Driver.process(Driver.scala:124)
        at dotty.tools.dotc.Driver.process(Driver.scala:93)
        at dotty.tools.dotc.Driver.process(Driver.scala:105)
        at dotty.tools.dotc.Driver.main(Driver.scala:132)
        at dotty.tools.dotc.Main.main(Main.scala)
@smarter smarter changed the title Intersection types still crash the compiler structural selection on Intersection leads to crash Jul 14, 2017
@smarter
Copy link
Member

smarter commented Jul 14, 2017

The issue is in TreeInfo#isStructuralTermSelect:

  def isStructuralTermSelect(tree: Tree)(implicit ctx: Context) = tree match {
    case tree: Select =>
      def hasRefinement(qualtpe: Type): Boolean = qualtpe.dealias match {
        case RefinedType(parent, rname, rinfo) =>
          rname == tree.name || hasRefinement(parent)
        case tp: TypeProxy =>
          hasRefinement(tp.underlying)
        case tp: OrType =>
          hasRefinement(tp.tp1) || hasRefinement(tp.tp2)
        case tp: AndType =>
          hasRefinement(tp.tp1) && hasRefinement(tp.tp2)
        case _ =>
          false
      }
      !tree.symbol.exists && tree.isTerm && hasRefinement(tree.qualifier.tpe)
    case _ =>
      false
  }

A selection of a member x is deemed structural if it's done on an intersection where both sides contain x, but in your example x only appears on one side of the intersection. This is easily fixed by flipping the use of && and || for OrType and AndType above. One thing that we need to think about (perhaps as a separate issue) is what should happen when a selection is made on an intersection or a union where one side contains a structural member x and the other side contains a regular member x, I suggest simply making that a type error.

@smarter
Copy link
Member

smarter commented Jul 14, 2017

As a workaround, you can make sure the structural member x is part of both sides of the intersection:

val c: { type A; val x: A } & { type A = Int; val x: A } = new Cont(1)

@smarter smarter changed the title structural selection on Intersection leads to crash Structural selection on intersection type leads to crash Jul 14, 2017
@LPTK
Copy link
Contributor Author

LPTK commented Jul 14, 2017

This is easily fixed by flipping the use of && and || for OrType and AndType above

When the fix is in, will the compiler crash if in the example we write { type A; val x: A } | { type A = Int } instead? (It should normally raise a type error.)

what should happen when a selection is made on an intersection or a union where one side contains a structural member x and the other side contains a regular member x, I suggest simply making that a type error.

Why make it a type error? This would be weird, as it means having A <: B will no longer ensure that if a program types with x: A it also types with x: A & B (take A = Cont[Int] and B = { val x: Int }).

Besides, I think it's useful being able to use intersection types statically to talk about capabilities that types can have, without actually using runtime structural typing.

@smarter
Copy link
Member

smarter commented Jul 14, 2017

When the fix is in, will the compiler crash if in the example we write { type A; val x: A } | { type A = Int } instead? (It should normally raise a type error.)

No, it should raise an error and stop before the point where it could crash :).

Why make it a type error?

Maybe not. The question is: should a.x be a structural selection or a regular selection if a is an intersection where one side contains a structural member x and the other side contains a regular member x ?

@LPTK
Copy link
Contributor Author

LPTK commented Jul 14, 2017

Well, the latter side is a proof that the field can be accessed without needing a runtime structural selection, so why not just pick that one?

@smarter
Copy link
Member

smarter commented Jul 14, 2017

I worry that either solution might be surprising for users, but I guess picking the rule "always avoid structural selection if possible" is not too bad.

odersky added a commit that referenced this issue Jul 18, 2017
Fix #2871: Recognize more types as structural selections
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

2 participants