Skip to content

Commit ece87c3

Browse files
authored
Add support for var in refinements (#19982)
Closes #19809
2 parents 9a5b9b4 + 87c2b14 commit ece87c3

File tree

13 files changed

+46
-19
lines changed

13 files changed

+46
-19
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

+16-1
Original file line numberDiff line numberDiff line change
@@ -1943,12 +1943,27 @@ object desugar {
19431943
case AndType(tp1, tp2) => stripToCore(tp1) ::: stripToCore(tp2)
19441944
case _ => defn.AnyType :: Nil
19451945
}
1946+
1947+
val refinements1 = Trees.flatten:
1948+
refinements.mapConserve {
1949+
case tree: ValDef if tree.mods.is(Mutable) =>
1950+
val getter =
1951+
cpy.DefDef(tree)(name = tree.name, paramss = Nil, tpt = tree.tpt, rhs = tree.rhs)
1952+
.withFlags(tree.mods.flags & (AccessFlags | Synthetic))
1953+
val setterParam = makeSyntheticParameter(tpt = tree.tpt)
1954+
val setter =
1955+
cpy.DefDef(tree)(name = tree.name.setterName, paramss = List(List(setterParam)), tpt = untpd.scalaUnit, rhs = EmptyTree)
1956+
.withFlags(tree.mods.flags & (AccessFlags | Synthetic))
1957+
Thicket(getter, setter)
1958+
case tree => tree
1959+
}
1960+
19461961
val parentCores = stripToCore(parent.tpe)
19471962
val untpdParent = TypedSplice(parent)
19481963
val (classParents, self) =
19491964
if (parentCores.length == 1 && (parent.tpe eq parentCores.head)) (untpdParent :: Nil, EmptyValDef)
19501965
else (parentCores map TypeTree, ValDef(nme.WILDCARD, untpdParent, EmptyTree))
1951-
val impl = Template(emptyConstructor, classParents, Nil, self, refinements)
1966+
val impl = Template(emptyConstructor, classParents, Nil, self, refinements1)
19521967
TypeDef(tpnme.REFINE_CLASS, impl).withFlags(Trait)
19531968
}
19541969

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

+2-4
Original file line numberDiff line numberDiff line change
@@ -4381,6 +4381,7 @@ object Parsers {
43814381

43824382
/** RefineStatSeq ::= RefineStat {semi RefineStat}
43834383
* RefineStat ::= ‘val’ VarDef
4384+
* | ‘var’ VarDef
43844385
* | ‘def’ DefDef
43854386
* | ‘type’ {nl} TypeDef
43864387
* (in reality we admit class defs and vars and filter them out afterwards in `checkLegal`)
@@ -4393,10 +4394,7 @@ object Parsers {
43934394
syntaxError(msg, tree.span)
43944395
Nil
43954396
tree match
4396-
case tree: ValDef if tree.mods.is(Mutable) =>
4397-
fail(em"""refinement cannot be a mutable var.
4398-
|You can use an explicit getter ${tree.name} and setter ${tree.name}_= instead""")
4399-
case tree: MemberDef if !(tree.mods.flags & ModifierFlags).isEmpty =>
4397+
case tree: MemberDef if !(tree.mods.flags & (ModifierFlags &~ Mutable)).isEmpty =>
44004398
fail(em"refinement cannot be ${(tree.mods.flags & ModifierFlags).flagStrings().mkString("`", "`, `", "`")}")
44014399
case tree: DefDef if tree.termParamss.nestedExists(!_.rhs.isEmpty) =>
44024400
fail(em"refinement cannot have default arguments")

compiler/src/dotty/tools/dotc/typer/Typer.scala

-1
Original file line numberDiff line numberDiff line change
@@ -2213,7 +2213,6 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
22132213
val refineCls = createSymbol(refineClsDef).asClass
22142214
val TypeDef(_, impl: Template) = typed(refineClsDef): @unchecked
22152215
val refinements1 = impl.body
2216-
assert(tree.refinements.hasSameLengthAs(refinements1), i"${tree.refinements}%, % > $refinements1%, %")
22172216
val seen = mutable.Set[Symbol]()
22182217
for (refinement <- refinements1) { // TODO: get clarity whether we want to enforce these conditions
22192218
typr.println(s"adding refinement $refinement")

docs/_docs/internals/syntax.md

+1
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ EndMarkerTag ::= id | ‘if’ | ‘while’ | ‘for’ | ‘match’ |
431431
### Definitions
432432
```ebnf
433433
RefineDcl ::= ‘val’ ValDcl
434+
| ‘var’ ValDcl
434435
| ‘def’ DefDcl
435436
| ‘type’ {nl} TypeDef
436437
ValDcl ::= ids ‘:’ Type

docs/_docs/reference/syntax.md

+1
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ EndMarkerTag ::= id | ‘if’ | ‘while’ | ‘for’ | ‘match’ |
415415
```
416416
RefineDcl ::= ‘val’ ValDcl
417417
| ‘def’ DefDcl
418+
| ‘var’ ValDcl
418419
| ‘type’ {nl} TypeDef
419420
ValDcl ::= ids ‘:’ Type
420421
DefDcl ::= DefSig ‘:’ Type

tests/neg/i13703.check

+2-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
-- Error: tests/neg/i13703.scala:3:17 ----------------------------------------------------------------------------------
2-
3 |val f: Foo { var i: Int } = new Foo { var i: Int = 0 } // error
3-
| ^^^^^^^^^^
4-
| refinement cannot be a mutable var.
5-
| You can use an explicit getter i and setter i_= instead
6-
-- [E007] Type Mismatch Error: tests/neg/i13703.scala:5:78 -------------------------------------------------------------
7-
5 |val f2: Foo { val i: Int; def i_=(x: Int): Unit } = new Foo { var i: Int = 0 } // error
1+
-- [E007] Type Mismatch Error: tests/neg/i13703.scala:3:78 -------------------------------------------------------------
2+
3 |val f2: Foo { val i: Int; def i_=(x: Int): Unit } = new Foo { var i: Int = 0 } // error
83
| ^
94
| Found: Object with Foo {...}
105
| Required: Foo{val i: Int; def i_=(x: Int): Unit}

tests/neg/i13703.scala

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
trait Foo extends reflect.Selectable
22

3-
val f: Foo { var i: Int } = new Foo { var i: Int = 0 } // error
4-
53
val f2: Foo { val i: Int; def i_=(x: Int): Unit } = new Foo { var i: Int = 0 } // error
64

75
val f3: Foo { def i: Int; def i_=(x: Int): Unit } = new Foo { var i: Int = 0 } // OK

tests/neg/i19809.check

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- [E120] Naming Error: tests/neg/i19809.scala:3:6 ---------------------------------------------------------------------
2+
3 | def x_=(x: Int): Unit // error
3+
| ^
4+
| Double definition:
5+
| def x_=(x$1: Int): Unit in trait <refinement> at line 2 and
6+
| def x_=(x: Int): Unit in trait <refinement> at line 3
7+
| have the same type after erasure.
8+
|
9+
| Consider adding a @targetName annotation to one of the conflicting definitions
10+
| for disambiguation.

tests/neg/i19809.scala

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
type A = Any {
2+
var x : Int
3+
def x_=(x: Int): Unit // error
4+
}

tests/neg/i4496b.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ trait Foo2 { def a: Int }
55
trait Foo3 { var a: Int }
66

77
object TestStructuralVar {
8-
type T0 = {var a: Int} // error
8+
type T0 = {var a: Int}
99
object TestStructuralVar {
1010
type T = {val a: Int; def a_=(x: Int): Unit}
1111
def upcast1(v: Foo1): T = v // error

tests/neg/illegal-refinements.scala

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,5 @@ trait x0 {
22

33
type T = String { val x: Int = 1 } // error: illegal refinement
44
type U = String { def x(): Int = 1 } // error: illegal refinement
5-
type V = String { var x: Int } // error: illegal refinement
6-
5+
type V = String { var x: Int = 1 } // error: illegal refinement
76
}

tests/neg/structural.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ object Test3 {
1111
type A = { def foo(x: Int): Unit; def foo(x: String): Unit } // error: overloaded definition // error: overloaded definition
1212
type B = { val foo: Int; def foo: Int } // error: duplicate foo
1313

14-
type C = { var foo: Int } // error: refinements cannot have vars
14+
type C = { var foo: Int }
1515

1616
trait Entry { type Key; val key: Key }
1717
type D = { def foo(e: Entry, k: e.Key): Unit }

tests/pos/i19809.scala

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type A = Any { var x: Int }
2+
3+
val f: Any { var i: Int } = new AnyRef { var i: Int = 0 }
4+
5+
def Test =
6+
summon[Any { def x: Int; def x_=(x: Int): Unit } <:< Any { var x: Int }]
7+
summon[Any { var x: Int } <:< Any { def x: Int; def x_=(x: Int): Unit }]

0 commit comments

Comments
 (0)