Skip to content

Commit bf39cca

Browse files
committed
Fix #9248: Handle extension setters
This was pretty hard. Unfortunately, extension methods interact with many other things in tricky ways, which requires lots of special purpose code.
1 parent 45ed679 commit bf39cca

File tree

3 files changed

+77
-29
lines changed

3 files changed

+77
-29
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ object Trees {
445445
def forwardTo: Tree[T] = fun
446446
}
447447

448+
/** The kind of application */
448449
enum ApplyKind:
449450
case Regular // r.f(x)
450451
case Using // r.f(using x)
@@ -459,6 +460,9 @@ object Trees {
459460
putAttachment(untpd.KindOfApply, kind)
460461
this
461462

463+
/** The kind of this application. Works reliably only for untyped trees; typed trees
464+
* are under no obligation to update it correctly.
465+
*/
462466
def applyKind: ApplyKind =
463467
attachmentOrElse(untpd.KindOfApply, ApplyKind.Regular)
464468
}

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

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -828,35 +828,63 @@ class Typer extends Namer
828828
// allow assignments from the primary constructor to class fields
829829
ctx.owner.name.is(TraitSetterName) || ctx.owner.isStaticConstructor
830830

831-
lhsCore.tpe match {
832-
case ref: TermRef =>
833-
val lhsVal = lhsCore.denot.suchThat(!_.is(Method))
834-
if (canAssign(lhsVal.symbol)) {
835-
// lhsBounds: (T .. Any) as seen from lhs prefix, where T is the type of lhsVal.symbol
836-
// This ensures we do the as-seen-from on T with variance -1. Test case neg/i2928.scala
837-
val lhsBounds =
838-
TypeBounds.lower(lhsVal.symbol.info).asSeenFrom(ref.prefix, lhsVal.symbol.owner)
839-
assignType(cpy.Assign(tree)(lhs1, typed(tree.rhs, lhsBounds.loBound)))
840-
.computeAssignNullable()
841-
}
842-
else {
843-
val pre = ref.prefix
844-
val setterName = ref.name.setterName
845-
val setter = pre.member(setterName)
846-
lhsCore match {
847-
case lhsCore: RefTree if setter.exists =>
848-
val setterTypeRaw = pre.select(setterName, setter)
849-
val setterType = ensureAccessible(setterTypeRaw, isSuperSelection(lhsCore), tree.sourcePos)
850-
val lhs2 = untpd.rename(lhsCore, setterName).withType(setterType)
851-
typedUnadapted(untpd.Apply(untpd.TypedSplice(lhs2), tree.rhs :: Nil), WildcardType, locked)
852-
case _ =>
853-
reassignmentToVal
831+
lhsCore match
832+
case Apply(fn, _) if fn.symbol.isExtensionMethod =>
833+
def toSetter(fn: Tree): untpd.Tree = fn match
834+
case fn @ Ident(name: TermName) =>
835+
untpd.cpy.Ident(fn)(name.setterName)
836+
case fn @ Select(qual, name: TermName) =>
837+
untpd.cpy.Select(fn)(untpd.TypedSplice(qual), name.setterName)
838+
case fn @ TypeApply(fn1, targs) =>
839+
untpd.cpy.TypeApply(fn)(toSetter(fn1), targs.map(untpd.TypedSplice(_)))
840+
case fn @ Apply(fn1, args) =>
841+
val result = untpd.cpy.Apply(fn)(toSetter(fn1), args.map(untpd.TypedSplice(_)))
842+
fn1 match
843+
case Apply(_, _) =>
844+
result.setApplyKind(ApplyKind.Using)
845+
// Note that we cannot copy the apply kind of `fn` since `fn` is a typed
846+
// tree and applyKinds are not preserved for those.
847+
case _ => result
848+
case _ =>
849+
EmptyTree
850+
851+
val setter = toSetter(lhsCore)
852+
if setter.isEmpty then reassignmentToVal
853+
else tryEither {
854+
val assign = untpd.Apply(setter, tree.rhs :: Nil)
855+
//println(i"converted $tree / $lhsCore --> $setter / $assign")
856+
typed(assign, IgnoredProto(pt))
857+
} { (_, _) => reassignmentToVal }
858+
case _ => lhsCore.tpe match {
859+
case ref: TermRef =>
860+
val lhsVal = lhsCore.denot.suchThat(!_.is(Method))
861+
if (canAssign(lhsVal.symbol)) {
862+
// lhsBounds: (T .. Any) as seen from lhs prefix, where T is the type of lhsVal.symbol
863+
// This ensures we do the as-seen-from on T with variance -1. Test case neg/i2928.scala
864+
val lhsBounds =
865+
TypeBounds.lower(lhsVal.symbol.info).asSeenFrom(ref.prefix, lhsVal.symbol.owner)
866+
assignType(cpy.Assign(tree)(lhs1, typed(tree.rhs, lhsBounds.loBound)))
867+
.computeAssignNullable()
854868
}
855-
}
856-
case TryDynamicCallType =>
857-
typedDynamicAssign(tree, pt)
858-
case tpe =>
859-
reassignmentToVal
869+
else {
870+
val pre = ref.prefix
871+
val setterName = ref.name.setterName
872+
val setter = pre.member(setterName)
873+
//println(i"assign $ref")
874+
lhsCore match {
875+
case lhsCore: RefTree if setter.exists =>
876+
val setterTypeRaw = pre.select(setterName, setter)
877+
val setterType = ensureAccessible(setterTypeRaw, isSuperSelection(lhsCore), tree.sourcePos)
878+
val lhs2 = untpd.rename(lhsCore, setterName).withType(setterType)
879+
typedUnadapted(untpd.Apply(untpd.TypedSplice(lhs2), tree.rhs :: Nil), WildcardType, locked)
880+
case _ =>
881+
reassignmentToVal
882+
}
883+
}
884+
case TryDynamicCallType =>
885+
typedDynamicAssign(tree, pt)
886+
case tpe =>
887+
reassignmentToVal
860888
}
861889
}
862890

tests/pos/i9248.scala

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,20 @@ extension (c: C)
55
def foo_=(a: Int): Unit = c.a = a
66

77
val c = C(10)
8-
val test = c.foo
8+
val c1 = c.foo = 11
9+
10+
given C = C(0)
11+
12+
// Harder case: extensions defined in local scope, with type parameters and implicits
13+
def test =
14+
class D[T](var a: T)
15+
16+
extension [T](d: D[T])(using C)
17+
def foo: T = d.a
18+
def foo_=(a: T): Unit =
19+
val c = summon[C]
20+
d.a = a
21+
22+
val d = D(10)
23+
d.foo
24+
d.foo = 11

0 commit comments

Comments
 (0)