Skip to content

Commit

Permalink
Add support for var in refinements (#19982)
Browse files Browse the repository at this point in the history
Closes #19809
  • Loading branch information
hamzaremmal committed Apr 3, 2024
2 parents 9a5b9b4 + 87c2b14 commit ece87c3
Show file tree
Hide file tree
Showing 13 changed files with 46 additions and 19 deletions.
17 changes: 16 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1943,12 +1943,27 @@ object desugar {
case AndType(tp1, tp2) => stripToCore(tp1) ::: stripToCore(tp2)
case _ => defn.AnyType :: Nil
}

val refinements1 = Trees.flatten:
refinements.mapConserve {
case tree: ValDef if tree.mods.is(Mutable) =>
val getter =
cpy.DefDef(tree)(name = tree.name, paramss = Nil, tpt = tree.tpt, rhs = tree.rhs)
.withFlags(tree.mods.flags & (AccessFlags | Synthetic))
val setterParam = makeSyntheticParameter(tpt = tree.tpt)
val setter =
cpy.DefDef(tree)(name = tree.name.setterName, paramss = List(List(setterParam)), tpt = untpd.scalaUnit, rhs = EmptyTree)
.withFlags(tree.mods.flags & (AccessFlags | Synthetic))
Thicket(getter, setter)
case tree => tree
}

val parentCores = stripToCore(parent.tpe)
val untpdParent = TypedSplice(parent)
val (classParents, self) =
if (parentCores.length == 1 && (parent.tpe eq parentCores.head)) (untpdParent :: Nil, EmptyValDef)
else (parentCores map TypeTree, ValDef(nme.WILDCARD, untpdParent, EmptyTree))
val impl = Template(emptyConstructor, classParents, Nil, self, refinements)
val impl = Template(emptyConstructor, classParents, Nil, self, refinements1)
TypeDef(tpnme.REFINE_CLASS, impl).withFlags(Trait)
}

Expand Down
6 changes: 2 additions & 4 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4381,6 +4381,7 @@ object Parsers {

/** RefineStatSeq ::= RefineStat {semi RefineStat}
* RefineStat ::= ‘val’ VarDef
* | ‘var’ VarDef
* | ‘def’ DefDef
* | ‘type’ {nl} TypeDef
* (in reality we admit class defs and vars and filter them out afterwards in `checkLegal`)
Expand All @@ -4393,10 +4394,7 @@ object Parsers {
syntaxError(msg, tree.span)
Nil
tree match
case tree: ValDef if tree.mods.is(Mutable) =>
fail(em"""refinement cannot be a mutable var.
|You can use an explicit getter ${tree.name} and setter ${tree.name}_= instead""")
case tree: MemberDef if !(tree.mods.flags & ModifierFlags).isEmpty =>
case tree: MemberDef if !(tree.mods.flags & (ModifierFlags &~ Mutable)).isEmpty =>
fail(em"refinement cannot be ${(tree.mods.flags & ModifierFlags).flagStrings().mkString("`", "`, `", "`")}")
case tree: DefDef if tree.termParamss.nestedExists(!_.rhs.isEmpty) =>
fail(em"refinement cannot have default arguments")
Expand Down
1 change: 0 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2213,7 +2213,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
val refineCls = createSymbol(refineClsDef).asClass
val TypeDef(_, impl: Template) = typed(refineClsDef): @unchecked
val refinements1 = impl.body
assert(tree.refinements.hasSameLengthAs(refinements1), i"${tree.refinements}%, % > $refinements1%, %")
val seen = mutable.Set[Symbol]()
for (refinement <- refinements1) { // TODO: get clarity whether we want to enforce these conditions
typr.println(s"adding refinement $refinement")
Expand Down
1 change: 1 addition & 0 deletions docs/_docs/internals/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ EndMarkerTag ::= id | ‘if’ | ‘while’ | ‘for’ | ‘match’ |
### Definitions
```ebnf
RefineDcl ::= ‘val’ ValDcl
| ‘var’ ValDcl
| ‘def’ DefDcl
| ‘type’ {nl} TypeDef
ValDcl ::= ids ‘:’ Type
Expand Down
1 change: 1 addition & 0 deletions docs/_docs/reference/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ EndMarkerTag ::= id | ‘if’ | ‘while’ | ‘for’ | ‘match’ |
```
RefineDcl ::= ‘val’ ValDcl
| ‘def’ DefDcl
| ‘var’ ValDcl
| ‘type’ {nl} TypeDef
ValDcl ::= ids ‘:’ Type
DefDcl ::= DefSig ‘:’ Type
Expand Down
9 changes: 2 additions & 7 deletions tests/neg/i13703.check
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
-- Error: tests/neg/i13703.scala:3:17 ----------------------------------------------------------------------------------
3 |val f: Foo { var i: Int } = new Foo { var i: Int = 0 } // error
| ^^^^^^^^^^
| refinement cannot be a mutable var.
| You can use an explicit getter i and setter i_= instead
-- [E007] Type Mismatch Error: tests/neg/i13703.scala:5:78 -------------------------------------------------------------
5 |val f2: Foo { val i: Int; def i_=(x: Int): Unit } = new Foo { var i: Int = 0 } // error
-- [E007] Type Mismatch Error: tests/neg/i13703.scala:3:78 -------------------------------------------------------------
3 |val f2: Foo { val i: Int; def i_=(x: Int): Unit } = new Foo { var i: Int = 0 } // error
| ^
| Found: Object with Foo {...}
| Required: Foo{val i: Int; def i_=(x: Int): Unit}
Expand Down
2 changes: 0 additions & 2 deletions tests/neg/i13703.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
trait Foo extends reflect.Selectable

val f: Foo { var i: Int } = new Foo { var i: Int = 0 } // error

val f2: Foo { val i: Int; def i_=(x: Int): Unit } = new Foo { var i: Int = 0 } // error

val f3: Foo { def i: Int; def i_=(x: Int): Unit } = new Foo { var i: Int = 0 } // OK
10 changes: 10 additions & 0 deletions tests/neg/i19809.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- [E120] Naming Error: tests/neg/i19809.scala:3:6 ---------------------------------------------------------------------
3 | def x_=(x: Int): Unit // error
| ^
| Double definition:
| def x_=(x$1: Int): Unit in trait <refinement> at line 2 and
| def x_=(x: Int): Unit in trait <refinement> at line 3
| have the same type after erasure.
|
| Consider adding a @targetName annotation to one of the conflicting definitions
| for disambiguation.
4 changes: 4 additions & 0 deletions tests/neg/i19809.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type A = Any {
var x : Int
def x_=(x: Int): Unit // error
}
2 changes: 1 addition & 1 deletion tests/neg/i4496b.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ trait Foo2 { def a: Int }
trait Foo3 { var a: Int }

object TestStructuralVar {
type T0 = {var a: Int} // error
type T0 = {var a: Int}
object TestStructuralVar {
type T = {val a: Int; def a_=(x: Int): Unit}
def upcast1(v: Foo1): T = v // error
Expand Down
3 changes: 1 addition & 2 deletions tests/neg/illegal-refinements.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@ trait x0 {

type T = String { val x: Int = 1 } // error: illegal refinement
type U = String { def x(): Int = 1 } // error: illegal refinement
type V = String { var x: Int } // error: illegal refinement

type V = String { var x: Int = 1 } // error: illegal refinement
}
2 changes: 1 addition & 1 deletion tests/neg/structural.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ object Test3 {
type A = { def foo(x: Int): Unit; def foo(x: String): Unit } // error: overloaded definition // error: overloaded definition
type B = { val foo: Int; def foo: Int } // error: duplicate foo

type C = { var foo: Int } // error: refinements cannot have vars
type C = { var foo: Int }

trait Entry { type Key; val key: Key }
type D = { def foo(e: Entry, k: e.Key): Unit }
Expand Down
7 changes: 7 additions & 0 deletions tests/pos/i19809.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type A = Any { var x: Int }

val f: Any { var i: Int } = new AnyRef { var i: Int = 0 }

def Test =
summon[Any { def x: Int; def x_=(x: Int): Unit } <:< Any { var x: Int }]
summon[Any { var x: Int } <:< Any { def x: Int; def x_=(x: Int): Unit }]

0 comments on commit ece87c3

Please sign in to comment.