From 16ecedd956ac1da38931293cb410c45f419b5e5e Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Mon, 5 Feb 2024 01:14:30 -0500 Subject: [PATCH 1/8] Add supports for type cast and filtering type for field and method owners --- .../tools/dotc/transform/init/Objects.scala | 31 ++++++++++++++----- .../tools/dotc/transform/init/Util.scala | 7 +++++ tests/init-global/neg/TypeCast.scala | 18 +++++++++++ tests/init-global/pos/i18882.scala | 15 +++++++++ 4 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 tests/init-global/neg/TypeCast.scala create mode 100644 tests/init-global/pos/i18882.scala diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 763b71619de8..8e8eda4fac30 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -599,6 +599,22 @@ object Objects: case _ => a + def filterType(tpe: Type)(using Context): Value = + val baseClasses = tpe.baseClasses + if baseClasses.isEmpty then a + else filterClass(baseClasses.head) + + def filterClass(sym: Symbol)(using Context): Value = + if !sym.isClass then a + else + val klass = sym.asClass + a match + case Cold => Cold + case ref: Ref if ref.klass.isSubClass(klass) => ref + case ref: Ref => Bottom + case ValueSet(values) => values.map(v => v.filterClass(klass)).join + case _ => a // TODO: could be more precise for OfArray; possibly add class information for Fun + extension (value: Ref | Cold.type) def widenRefOrCold(height : Int)(using Context) : Ref | Cold.type = value.widen(height).asInstanceOf[ThisValue] @@ -617,7 +633,7 @@ object Objects: * @param needResolve Whether the target of the call needs resolution? */ def call(value: Value, meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, needResolve: Boolean = true): Contextual[Value] = log("call " + meth.show + ", this = " + value.show + ", args = " + args.map(_.value.show), printer, (_: Value).show) { - value match + value.filterClass(meth.owner) match case Cold => report.warning("Using cold alias. " + Trace.show, Trace.position) Bottom @@ -733,7 +749,6 @@ object Objects: * @param args Arguments of the constructor call (all parameter blocks flatten to a list). */ def callConstructor(value: Value, ctor: Symbol, args: List[ArgInfo]): Contextual[Value] = log("call " + ctor.show + ", args = " + args.map(_.value.show), printer, (_: Value).show) { - value match case ref: Ref => if ctor.hasSource then @@ -768,7 +783,7 @@ object Objects: * @param needResolve Whether the target of the selection needs resolution? */ def select(value: Value, field: Symbol, receiver: Type, needResolve: Boolean = true): Contextual[Value] = log("select " + field.show + ", this = " + value.show, printer, (_: Value).show) { - value match + value.filterClass(field.owner) match case Cold => report.warning("Using cold alias", Trace.position) Bottom @@ -839,12 +854,12 @@ object Objects: * @param rhsTyp The type of the right-hand side. */ def assign(lhs: Value, field: Symbol, rhs: Value, rhsTyp: Type): Contextual[Value] = log("Assign" + field.show + " of " + lhs.show + ", rhs = " + rhs.show, printer, (_: Value).show) { - lhs match + lhs.filterClass(field.owner) match case fun: Fun => report.warning("[Internal error] unexpected tree in assignment, fun = " + fun.code.show + Trace.show, Trace.position) case arr: OfArray => - report.warning("[Internal error] unexpected tree in assignment, array = " + arr.show + Trace.show, Trace.position) + report.warning("[Internal error] unexpected tree in assignment, array = " + arr.show + s", owner = ${field.owner}\n" + Trace.show, Trace.position) case Cold => report.warning("Assigning to cold aliases is forbidden. " + Trace.show, Trace.position) @@ -876,8 +891,7 @@ object Objects: * @param args The arguments passsed to the constructor. */ def instantiate(outer: Value, klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo]): Contextual[Value] = log("instantiating " + klass.show + ", outer = " + outer + ", args = " + args.map(_.value.show), printer, (_: Value).show) { - outer match - + outer.filterClass(klass.owner) match case _ : Fun | _: OfArray => report.warning("[Internal error] unexpected outer in instantiating a class, outer = " + outer.show + ", class = " + klass.show + ", " + Trace.show, Trace.position) Bottom @@ -1091,6 +1105,9 @@ object Objects: instantiate(outer, cls, ctor, args) } + case TypeCast(elem, tpe) => + eval(elem, thisV, klass).filterType(tpe) + case Apply(ref, arg :: Nil) if ref.symbol == defn.InitRegionMethod => val regions2 = Regions.extend(expr.sourcePos) if Regions.exists(expr.sourcePos) then diff --git a/compiler/src/dotty/tools/dotc/transform/init/Util.scala b/compiler/src/dotty/tools/dotc/transform/init/Util.scala index 70390028e84f..1114a9efc218 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Util.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Util.scala @@ -78,6 +78,13 @@ object Util: case _ => None + object TypeCast: + def unapply(tree: Tree)(using Context): Option[(Tree, Type)] = + tree match + case TypeApply(Select(qual, _), typeArg) if tree.symbol.isTypeCast => + Some(qual, typeArg.head.tpe) + case _ => None + def resolve(cls: ClassSymbol, sym: Symbol)(using Context): Symbol = log("resove " + cls + ", " + sym, printer, (_: Symbol).show): if sym.isEffectivelyFinal then sym else sym.matchingMember(cls.appliedRef) diff --git a/tests/init-global/neg/TypeCast.scala b/tests/init-global/neg/TypeCast.scala new file mode 100644 index 000000000000..55447e9df4e2 --- /dev/null +++ b/tests/init-global/neg/TypeCast.scala @@ -0,0 +1,18 @@ +object A { + val f: Int = 10 + def m() = f +} +object B { + val f: Int = g() + def g(): Int = f // error +} +object C { + val a: A.type | B.type = if ??? then A else B + def cast[T](a: Any): T = a.asInstanceOf[T] + val c: A.type = cast[A.type](a) // abstraction for c is {A, B} + val d = c.f // treat as c.asInstanceOf[owner of f].f + val e = c.m() // treat as c.asInstanceOf[owner of f].m() + val c2: B.type = cast[B.type](a) + val g = c2.f // no error here +} + diff --git a/tests/init-global/pos/i18882.scala b/tests/init-global/pos/i18882.scala new file mode 100644 index 000000000000..0a1ea5309a58 --- /dev/null +++ b/tests/init-global/pos/i18882.scala @@ -0,0 +1,15 @@ +class A: + var a = 20 + +class B: + var b = 20 + +object O: + val o: A | B = new A + if o.isInstanceOf[A] then + o.asInstanceOf[A].a += 1 + else + o.asInstanceOf[B].b += 1 // o.asInstanceOf[B] is treated as bottom + o match + case o: A => o.a += 1 + case o: B => o.b += 1 From f091c4e5c81502a3226d2d1e352fbe6642246ea7 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Mon, 5 Feb 2024 01:21:35 -0500 Subject: [PATCH 2/8] fix error messages --- compiler/src/dotty/tools/dotc/transform/init/Objects.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 8e8eda4fac30..6ecd7441829d 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -859,7 +859,7 @@ object Objects: report.warning("[Internal error] unexpected tree in assignment, fun = " + fun.code.show + Trace.show, Trace.position) case arr: OfArray => - report.warning("[Internal error] unexpected tree in assignment, array = " + arr.show + s", owner = ${field.owner}\n" + Trace.show, Trace.position) + report.warning("[Internal error] unexpected tree in assignment, array = " + arr.show + Trace.show, Trace.position) case Cold => report.warning("Assigning to cold aliases is forbidden. " + Trace.show, Trace.position) From 124f68b9260e99bdb8ad080ddbc3f0b01b6a77e9 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Thu, 22 Feb 2024 10:05:26 -0500 Subject: [PATCH 3/8] Added tests --- tests/init-global/pos/TypeCast1.scala | 9 +++++++++ tests/init-global/pos/TypeCast2.scala | 9 +++++++++ tests/init-global/pos/TypeCast3.scala | 8 ++++++++ tests/init-global/pos/TypeCast4.scala | 9 +++++++++ 4 files changed, 35 insertions(+) create mode 100644 tests/init-global/pos/TypeCast1.scala create mode 100644 tests/init-global/pos/TypeCast2.scala create mode 100644 tests/init-global/pos/TypeCast3.scala create mode 100644 tests/init-global/pos/TypeCast4.scala diff --git a/tests/init-global/pos/TypeCast1.scala b/tests/init-global/pos/TypeCast1.scala new file mode 100644 index 000000000000..e9881c6f5e4d --- /dev/null +++ b/tests/init-global/pos/TypeCast1.scala @@ -0,0 +1,9 @@ +class A: + class B(val b: Int) + +object O: + val o: A | Array[Int] = new Array[Int](10) + o match + case a: A => new a.B(10) + case arr: Array[Int] => arr(5) + diff --git a/tests/init-global/pos/TypeCast2.scala b/tests/init-global/pos/TypeCast2.scala new file mode 100644 index 000000000000..e18c8ffca5d1 --- /dev/null +++ b/tests/init-global/pos/TypeCast2.scala @@ -0,0 +1,9 @@ +class A: + class B(val b: Int) + +object O: + val o: A | (Int => Int) = (x: Int) => x + 1 + o match + case a: A => new a.B(10) + case f: (_ => _) => f.asInstanceOf[Int => Int](5) + diff --git a/tests/init-global/pos/TypeCast3.scala b/tests/init-global/pos/TypeCast3.scala new file mode 100644 index 000000000000..08197790edd6 --- /dev/null +++ b/tests/init-global/pos/TypeCast3.scala @@ -0,0 +1,8 @@ +class A: + var x: Int = 10 + +object O: + val o: A | (Int => Int) = (x: Int) => x + 1 + o match + case a: A => a.x = 20 + case f: (_ => _) => f.asInstanceOf[Int => Int](5) diff --git a/tests/init-global/pos/TypeCast4.scala b/tests/init-global/pos/TypeCast4.scala new file mode 100644 index 000000000000..8b65bc775cc2 --- /dev/null +++ b/tests/init-global/pos/TypeCast4.scala @@ -0,0 +1,9 @@ +class A: + var x: Int = 10 + +object O: + val o: A | Array[Int] = new Array[Int](10) + o match + case a: A => a.x = 20 + case arr: Array[Int] => arr(5) + From 113002bc2b4b064e460f2d88e82a5676fa08fbf2 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Thu, 22 Feb 2024 11:13:51 -0500 Subject: [PATCH 4/8] Fixing filtering arrays --- .../dotty/tools/dotc/transform/init/Objects.scala | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 6ecd7441829d..c76f4658eecb 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -203,6 +203,7 @@ object Objects: /** * Represents a lambda expression + * @param klass The enclosing class of the anonymous function's creation site */ case class Fun(code: Tree, thisV: ThisValue, klass: ClassSymbol, env: Env.Data) extends ValueElement: def show(using Context) = "Fun(" + code.show + ", " + thisV.show + ", " + klass.show + ")" @@ -600,9 +601,10 @@ object Objects: case _ => a def filterType(tpe: Type)(using Context): Value = + // if tpe is SAMType and a is Fun, allow it val baseClasses = tpe.baseClasses if baseClasses.isEmpty then a - else filterClass(baseClasses.head) + else filterClass(baseClasses.head) // could have called ClassSymbol, but it does not handle OrType and AndType def filterClass(sym: Symbol)(using Context): Value = if !sym.isClass then a @@ -613,7 +615,11 @@ object Objects: case ref: Ref if ref.klass.isSubClass(klass) => ref case ref: Ref => Bottom case ValueSet(values) => values.map(v => v.filterClass(klass)).join - case _ => a // TODO: could be more precise for OfArray; possibly add class information for Fun + case arr: OfArray => if defn.ArrayClass.isSubClass(klass) then arr else Bottom + case fun: Fun => if defn.Function1.isSubClass(klass) then fun else Bottom + // TODO: could be more precise for OfArray; possibly add class information for Fun + // If ArrayClass.isSubClass(klass) keep the array else discard (see Definitions.scala) + // For function, if any superclass is FunctionClass (or a single abstract method interface?), allow it extension (value: Ref | Cold.type) def widenRefOrCold(height : Int)(using Context) : Ref | Cold.type = value.widen(height).asInstanceOf[ThisValue] @@ -859,7 +865,7 @@ object Objects: report.warning("[Internal error] unexpected tree in assignment, fun = " + fun.code.show + Trace.show, Trace.position) case arr: OfArray => - report.warning("[Internal error] unexpected tree in assignment, array = " + arr.show + Trace.show, Trace.position) + report.warning("[Internal error] unexpected tree in assignment, array = " + arr.show + " field = " + field + Trace.show, Trace.position) case Cold => report.warning("Assigning to cold aliases is forbidden. " + Trace.show, Trace.position) @@ -1566,7 +1572,7 @@ object Objects: report.warning("The argument should be a constant integer value", arg) res.widen(1) case _ => - res.widen(1) + res.widen(1) // TODO: changing to widen(2) causes standard library analysis to loop infinitely argInfos += ArgInfo(widened, trace.add(arg.tree), arg.tree) } From 8412f788b35be5af356e71a08207391b0d965978 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Mon, 26 Feb 2024 22:56:52 -0500 Subject: [PATCH 5/8] Adding support for filtering Fun --- .../dotty/tools/dotc/transform/init/Objects.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index c76f4658eecb..41d5bd963fec 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -601,10 +601,11 @@ object Objects: case _ => a def filterType(tpe: Type)(using Context): Value = - // if tpe is SAMType and a is Fun, allow it val baseClasses = tpe.baseClasses if baseClasses.isEmpty then a - else filterClass(baseClasses.head) // could have called ClassSymbol, but it does not handle OrType and AndType + else tpe match + case t @ SAMType(_, _) if a.isInstanceOf[Fun] => a // if tpe is SAMType and a is Fun, allow it + case _ => filterClass(baseClasses.head) // could have called ClassSymbol, but it does not handle OrType and AndType def filterClass(sym: Symbol)(using Context): Value = if !sym.isClass then a @@ -616,10 +617,9 @@ object Objects: case ref: Ref => Bottom case ValueSet(values) => values.map(v => v.filterClass(klass)).join case arr: OfArray => if defn.ArrayClass.isSubClass(klass) then arr else Bottom - case fun: Fun => if defn.Function1.isSubClass(klass) then fun else Bottom - // TODO: could be more precise for OfArray; possibly add class information for Fun - // If ArrayClass.isSubClass(klass) keep the array else discard (see Definitions.scala) - // For function, if any superclass is FunctionClass (or a single abstract method interface?), allow it + case fun: Fun => + val functionSuperCls = klass.baseClasses.filter(defn.isFunctionClass) + if functionSuperCls.nonEmpty then fun else Bottom extension (value: Ref | Cold.type) def widenRefOrCold(height : Int)(using Context) : Ref | Cold.type = value.widen(height).asInstanceOf[ThisValue] From 0a95653482d54704d59e9e1fa3b2c2e4b56b95c5 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Mon, 26 Feb 2024 22:59:03 -0500 Subject: [PATCH 6/8] Addressed comments --- compiler/src/dotty/tools/dotc/transform/init/Util.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Util.scala b/compiler/src/dotty/tools/dotc/transform/init/Util.scala index 1114a9efc218..756fd1a0a8e7 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Util.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Util.scala @@ -81,8 +81,8 @@ object Util: object TypeCast: def unapply(tree: Tree)(using Context): Option[(Tree, Type)] = tree match - case TypeApply(Select(qual, _), typeArg) if tree.symbol.isTypeCast => - Some(qual, typeArg.head.tpe) + case TypeApply(Select(qual, _), typeArgs) if tree.symbol.isTypeCast => + Some(qual, typeArgs.head.tpe) case _ => None def resolve(cls: ClassSymbol, sym: Symbol)(using Context): Symbol = log("resove " + cls + ", " + sym, printer, (_: Symbol).show): From 36392fa7f615386280670c43abe1fcb00e552a37 Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Tue, 27 Feb 2024 10:49:39 -0500 Subject: [PATCH 7/8] Minor fix --- compiler/src/dotty/tools/dotc/transform/init/Objects.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 41d5bd963fec..5c876206b111 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -27,6 +27,7 @@ import scala.collection.immutable.ListSet import scala.collection.mutable import scala.annotation.tailrec import scala.annotation.constructorOnly +import dotty.tools.dotc.core.Flags.AbstractOrTrait /** Check initialization safety of static objects * @@ -618,8 +619,7 @@ object Objects: case ValueSet(values) => values.map(v => v.filterClass(klass)).join case arr: OfArray => if defn.ArrayClass.isSubClass(klass) then arr else Bottom case fun: Fun => - val functionSuperCls = klass.baseClasses.filter(defn.isFunctionClass) - if functionSuperCls.nonEmpty then fun else Bottom + if klass.isOneOf(AbstractOrTrait) && klass.baseClasses.exists(defn.isFunctionClass) then fun else Bottom extension (value: Ref | Cold.type) def widenRefOrCold(height : Int)(using Context) : Ref | Cold.type = value.widen(height).asInstanceOf[ThisValue] @@ -1572,7 +1572,7 @@ object Objects: report.warning("The argument should be a constant integer value", arg) res.widen(1) case _ => - res.widen(1) // TODO: changing to widen(2) causes standard library analysis to loop infinitely + if res.isInstanceOf[Fun] then res.widen(2) else res.widen(1) // TODO: changing to widen(2) causes standard library analysis to loop infinitely argInfos += ArgInfo(widened, trace.add(arg.tree), arg.tree) } From 229062cd555517413b6fa3162d1c70d8b420e74f Mon Sep 17 00:00:00 2001 From: EnzeXing Date: Thu, 7 Mar 2024 17:24:35 -0500 Subject: [PATCH 8/8] address comments --- .../dotty/tools/dotc/transform/init/Objects.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala index 5c876206b111..19570f13c519 100644 --- a/compiler/src/dotty/tools/dotc/transform/init/Objects.scala +++ b/compiler/src/dotty/tools/dotc/transform/init/Objects.scala @@ -602,11 +602,12 @@ object Objects: case _ => a def filterType(tpe: Type)(using Context): Value = - val baseClasses = tpe.baseClasses - if baseClasses.isEmpty then a - else tpe match + tpe match case t @ SAMType(_, _) if a.isInstanceOf[Fun] => a // if tpe is SAMType and a is Fun, allow it - case _ => filterClass(baseClasses.head) // could have called ClassSymbol, but it does not handle OrType and AndType + case _ => + val baseClasses = tpe.baseClasses + if baseClasses.isEmpty then a + else filterClass(baseClasses.head) // could have called ClassSymbol, but it does not handle OrType and AndType def filterClass(sym: Symbol)(using Context): Value = if !sym.isClass then a @@ -614,8 +615,7 @@ object Objects: val klass = sym.asClass a match case Cold => Cold - case ref: Ref if ref.klass.isSubClass(klass) => ref - case ref: Ref => Bottom + case ref: Ref => if ref.klass.isSubClass(klass) then ref else Bottom case ValueSet(values) => values.map(v => v.filterClass(klass)).join case arr: OfArray => if defn.ArrayClass.isSubClass(klass) then arr else Bottom case fun: Fun => @@ -1572,7 +1572,7 @@ object Objects: report.warning("The argument should be a constant integer value", arg) res.widen(1) case _ => - if res.isInstanceOf[Fun] then res.widen(2) else res.widen(1) // TODO: changing to widen(2) causes standard library analysis to loop infinitely + if res.isInstanceOf[Fun] then res.widen(2) else res.widen(1) argInfos += ArgInfo(widened, trace.add(arg.tree), arg.tree) }