Skip to content

Commit ce6ba43

Browse files
author
EnzeXing
committed
Address comments; add TopWidenedValue
1 parent e8eb04d commit ce6ba43

File tree

4 files changed

+107
-32
lines changed

4 files changed

+107
-32
lines changed

compiler/src/dotty/tools/dotc/transform/init/Objects.scala

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,11 @@ class Objects(using Context @constructorOnly):
9393
* | OfClass(class, vs[outer], ctor, args, env) // instance of a class
9494
* | OfArray(object[owner], regions)
9595
* | Fun(..., env) // value elements that can be contained in ValueSet
96-
* | SafeValue // values on which method calls and fields won't cause warnings. Int, String, etc.
96+
* | SafeValue // values on which method calls and field accesses won't cause warnings. Int, String, etc.
97+
* | UnknownValue
9798
* vs ::= ValueSet(ve) // set of abstract values
9899
* Bottom ::= ValueSet(Empty)
99-
* val ::= ve | UnknownValue | vs | Package // all possible abstract values in domain
100+
* val ::= ve | TopWidenedValue | vs | Package // all possible abstract values in domain
100101
* Ref ::= ObjectRef | OfClass // values that represent a reference to some (global or instance) object
101102
* ThisValue ::= Ref | UnknownValue // possible values for 'this'
102103
*
@@ -190,7 +191,7 @@ class Objects(using Context @constructorOnly):
190191

191192
def show(using Context) =
192193
val valFields = vals.map(_.show + " -> " + _.show)
193-
"OfClass(" + klass.show + ", outer = " + outer + ", args = " + args.map(_.show) + ", vals = " + valFields + ")"
194+
"OfClass(" + klass.show + ", outer = " + outer + ", args = " + args.map(_.show) + " env = " + env.show + ", vals = " + valFields + ")"
194195

195196
object OfClass:
196197
def apply(
@@ -229,7 +230,8 @@ class Objects(using Context @constructorOnly):
229230

230231
/**
231232
* Represents common base values like Int, String, etc.
232-
* Assumption: all methods calls on such values should be pure (no side effects)
233+
* Assumption: all methods calls on such values should not trigger initialization of global objects
234+
* or read/write mutable fields
233235
*/
234236
case class SafeValue(tpe: Type) extends ValueElement:
235237
// tpe could be a AppliedType(java.lang.Class, T)
@@ -253,15 +255,21 @@ class Objects(using Context @constructorOnly):
253255
def show(using Context): String = "Package(" + packageSym.show + ")"
254256

255257
/** Represents values unknown to the checker, such as values loaded without source
258+
*/
259+
case object UnknownValue extends ValueElement:
260+
def show(using Context): String = "UnknownValue"
261+
262+
/** Represents values lost due to widening
256263
*
257264
* This is the top of the abstract domain lattice, which should not
258265
* be used during initialization.
259266
*
260-
* UnknownValue is not ValueElement since RefSet containing UnknownValue
261-
* is equivalent to UnknownValue
262-
*/
263-
case object UnknownValue extends Value:
264-
def show(using Context): String = "UnknownValue"
267+
* TopWidenedValue is not ValueElement since RefSet containing TopWidenedValue
268+
* is equivalent to TopWidenedValue
269+
*/
270+
271+
case object TopWidenedValue extends Value:
272+
def show(using Context): String = "TopWidenedValue"
265273

266274
val Bottom = ValueSet(ListSet.empty)
267275

@@ -483,8 +491,8 @@ class Objects(using Context @constructorOnly):
483491
thisV match
484492
case ref: OfClass =>
485493
ref.outer match
486-
case outer : ThisValue =>
487-
resolveEnv(meth, outer, ref.env)
494+
case outer : OfClass =>
495+
resolveEnv(meth, outer, outer.env)
488496
case _ =>
489497
// TODO: properly handle the case where ref.outer is ValueSet
490498
None
@@ -623,8 +631,8 @@ class Objects(using Context @constructorOnly):
623631
extension (a: Value)
624632
def join(b: Value): Value =
625633
(a, b) match
626-
case (UnknownValue, _) => UnknownValue
627-
case (_, UnknownValue) => UnknownValue
634+
case (TopWidenedValue, _) => TopWidenedValue
635+
case (_, TopWidenedValue) => TopWidenedValue
628636
case (Package(_), _) => UnknownValue // should not happen
629637
case (_, Package(_)) => UnknownValue
630638
case (Bottom, b) => b
@@ -640,8 +648,8 @@ class Objects(using Context @constructorOnly):
640648
case (a: Ref, b: Ref) if a.equals(b) => Bottom
641649
case _ => a
642650

643-
def widen(height: Int)(using Context): Value =
644-
if height == 0 then UnknownValue
651+
def widen(height: Int)(using Context): Value = log("widening value " + a.show + " down to height " + height, printer, (_: Value).show) {
652+
if height == 0 then TopWidenedValue
645653
else
646654
a match
647655
case Bottom => Bottom
@@ -659,6 +667,7 @@ class Objects(using Context @constructorOnly):
659667
ref.widenedCopy(outer2, args2, env2)
660668

661669
case _ => a
670+
}
662671

663672
def filterType(tpe: Type)(using Context): Value =
664673
tpe match
@@ -670,19 +679,21 @@ class Objects(using Context @constructorOnly):
670679

671680
// Filter the value according to a class symbol, and only leaves the sub-values
672681
// which could represent an object of the given class
673-
def filterClass(sym: Symbol)(using Context): Value =
674-
if !sym.isClass then a
675-
else
676-
val klass = sym.asClass
677-
a match
678-
case UnknownValue => UnknownValue
679-
case Package(_) => a
680-
case SafeValue(_) => a
681-
case ref: Ref => if ref.klass.isSubClass(klass) then ref else Bottom
682-
case ValueSet(values) => values.map(v => v.filterClass(klass)).join
683-
case arr: OfArray => if defn.ArrayClass.isSubClass(klass) then arr else Bottom
684-
case fun: Fun =>
685-
if klass.isOneOf(AbstractOrTrait) && klass.baseClasses.exists(defn.isFunctionClass) then fun else Bottom
682+
def filterClass(sym: Symbol)(using Context): Value = log("filtering value " + a.show + " through class " + sym.show, printer, (_: Value).show) {
683+
if !sym.isClass then a
684+
else
685+
val klass = sym.asClass
686+
a match
687+
case UnknownValue | TopWidenedValue => a
688+
case Package(packageSym) =>
689+
if packageSym.moduleClass.equals(sym) || (klass.denot.isPackageObject && klass.owner.equals(sym)) then a else Bottom
690+
case v: SafeValue => if v.typeref.symbol.asClass.isSubClass(klass) then a else Bottom
691+
case ref: Ref => if ref.klass.isSubClass(klass) then ref else Bottom
692+
case ValueSet(values) => values.map(v => v.filterClass(klass)).join
693+
case arr: OfArray => if defn.ArrayClass.isSubClass(klass) then arr else Bottom
694+
case fun: Fun =>
695+
if klass.isOneOf(AbstractOrTrait) && klass.baseClasses.exists(defn.isFunctionClass) then fun else Bottom
696+
}
686697

687698
extension (value: Ref | UnknownValue.type)
688699
def widenRefOrCold(height : Int)(using Context) : Ref | UnknownValue.type = value.widen(height).asInstanceOf[ThisValue]
@@ -708,6 +719,9 @@ class Objects(using Context @constructorOnly):
708719
*/
709720
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) {
710721
value.filterClass(meth.owner) match
722+
case TopWidenedValue =>
723+
report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position)
724+
Bottom
711725
case UnknownValue =>
712726
if reportUnknown then
713727
report.warning("Using unknown value. " + Trace.show, Trace.position)
@@ -898,6 +912,9 @@ class Objects(using Context @constructorOnly):
898912
*/
899913
def select(value: Value, field: Symbol, receiver: Type, needResolve: Boolean = true): Contextual[Value] = log("select " + field.show + ", this = " + value.show, printer, (_: Value).show) {
900914
value.filterClass(field.owner) match
915+
case TopWidenedValue =>
916+
report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position)
917+
Bottom
901918
case UnknownValue =>
902919
if reportUnknown then
903920
report.warning("Using unknown value. " + Trace.show, Trace.position)
@@ -984,15 +1001,21 @@ class Objects(using Context @constructorOnly):
9841001
*/
9851002
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) {
9861003
lhs.filterClass(field.owner) match
1004+
case TopWidenedValue =>
1005+
report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position)
1006+
case UnknownValue =>
1007+
if reportUnknown then
1008+
report.warning("Assigning to unknown value. " + Trace.show, Trace.position)
1009+
end if
9871010
case p: Package =>
9881011
report.warning("[Internal error] unexpected tree in assignment, package = " + p.packageSym.show + Trace.show, Trace.position)
9891012
case fun: Fun =>
9901013
report.warning("[Internal error] unexpected tree in assignment, fun = " + fun.code.show + Trace.show, Trace.position)
9911014
case arr: OfArray =>
9921015
report.warning("[Internal error] unexpected tree in assignment, array = " + arr.show + " field = " + field + Trace.show, Trace.position)
9931016

994-
case SafeValue(_) | UnknownValue =>
995-
report.warning("Assigning to base or unknown value is forbidden. " + Trace.show, Trace.position)
1017+
case SafeValue(_) =>
1018+
report.warning("Assigning to base value is forbidden. " + Trace.show, Trace.position)
9961019

9971020
case ValueSet(values) =>
9981021
values.foreach(ref => assign(ref, field, rhs, rhsTyp))
@@ -1027,8 +1050,16 @@ class Objects(using Context @constructorOnly):
10271050
report.warning("[Internal error] unexpected outer in instantiating a class, outer = " + outer.show + ", class = " + klass.show + ", " + Trace.show, Trace.position)
10281051
Bottom
10291052

1053+
case TopWidenedValue =>
1054+
report.warning("Value is unknown to the checker due to widening. " + Trace.show, Trace.position)
1055+
Bottom
1056+
10301057
case UnknownValue =>
1031-
UnknownValue
1058+
if reportUnknown then
1059+
report.warning("Assigning to unknown value. " + Trace.show, Trace.position)
1060+
Bottom
1061+
else
1062+
UnknownValue
10321063

10331064
case outer: (Ref | UnknownValue.type | Package) =>
10341065
if klass == defn.ArrayClass then
@@ -1115,7 +1146,7 @@ class Objects(using Context @constructorOnly):
11151146
case fun: Fun =>
11161147
given Env.Data = Env.ofByName(sym, fun.env)
11171148
eval(fun.code, fun.thisV, fun.klass)
1118-
case UnknownValue =>
1149+
case UnknownValue | TopWidenedValue =>
11191150
report.warning("Calling on unknown value. " + Trace.show, Trace.position)
11201151
Bottom
11211152
case _: ValueSet | _: Ref | _: OfArray | _: Package | SafeValue(_) =>
@@ -1891,6 +1922,7 @@ class Objects(using Context @constructorOnly):
18911922
thisV match
18921923
case Bottom => Bottom
18931924
case UnknownValue => UnknownValue
1925+
case TopWidenedValue => TopWidenedValue
18941926
case ref: Ref =>
18951927
val outerCls = klass.owner.lexicallyEnclosingClass.asClass
18961928
if !ref.hasOuter(klass) then
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object A:
2+
val a = f(10)
3+
def f(x: Int) = x * 2 + 5

tests/init-global/warn/widen.check

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-- Warning: tests/init-global/warn/widen.scala:13:13 -------------------------------------------------------------------
2+
13 | t.foo() // warn
3+
| ^^^^^^^
4+
| Value is unknown to the checker due to widening. Calling trace:
5+
| ├── object O: [ widen.scala:9 ]
6+
| │ ^
7+
| ├── val a = bar(new C) [ widen.scala:20 ]
8+
| │ ^^^^^^^^^^
9+
| ├── def bar(t: T) = { [ widen.scala:10 ]
10+
| │ ^
11+
| ├── new A [ widen.scala:18 ]
12+
| │ ^^^^^
13+
| ├── class A { [ widen.scala:11 ]
14+
| │ ^
15+
| ├── val b = new B [ widen.scala:16 ]
16+
| │ ^^^^^
17+
| ├── class B { [ widen.scala:12 ]
18+
| │ ^
19+
| └── t.foo() // warn [ widen.scala:13 ]
20+
| ^^^^^^^

tests/init-global/warn/widen.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
trait T {
2+
def foo(): Unit
3+
}
4+
5+
class C extends T {
6+
def foo(): Unit = println("Calling foo on an instance of C!")
7+
}
8+
9+
object O:
10+
def bar(t: T) = {
11+
class A {
12+
class B {
13+
t.foo() // warn
14+
}
15+
16+
val b = new B
17+
}
18+
new A
19+
}
20+
val a = bar(new C)

0 commit comments

Comments
 (0)