-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Rewrite resolveThis in global init checker #23282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
798fd49
to
1e0eb9f
Compare
@@ -287,11 +287,19 @@ class Objects(using Context @constructorOnly): | |||
def toScopeSet: ScopeSet = ScopeSet(values.asInstanceOf[Set[Scope]]) | |||
|
|||
case class ScopeSet(scopes: Set[Scope]): | |||
assert(scopes.forall(_.isRef) || scopes.forall(_.isEnv), "All scopes should have the same type!") | |||
def isRefSet = scopes.forall(_.isRef) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we keep an assertion that isRefSet || isEnvSet
?
@@ -1969,6 +1977,11 @@ class Objects(using Context @constructorOnly): | |||
case _ => | |||
report.warning("[Internal error] unexpected thisV = " + thisV + ", target = " + target.show + Trace.show, Trace.position) | |||
Bottom | |||
if resolveResult == Bottom && thisV.filterClass(target) == thisV then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible that target
could be a parent class of an outer class?
Here's an example of a class A {
var x = 42
def change = (x = 44)
class B {
val i = 5
def fooz = A.this.x // if this is an instance of class C, does A.this resolve to the C outer or to the B outer ???
def fooz2 = A.this.x
class D {
def bar = fooz
def bar2 = fooz2
}
}
}
class AA extends A {
def foo = {
//val y = 43
val a = if(*) new A else new AAA
class C /*outer AA*/ extends a.B /*outer A or AAA*/ {
override val i = 6
override def fooz2 = x
}
val bb: B = if(false) new a.B else new C
val d = new bb.D
d.bar // reads AA.x
d.bar2 // reads AA.x
}
}
d --outer-> {B or C} --outer-> {A or foo} --outer --> {A} |
1e0eb9f
to
8b89ae9
Compare
@olhotak @liufengyun I have added two rather complex tests, feel free to run them with scala-cli.
|
The second test |
It's not clear why The following comment might help: scala3/compiler/src/dotty/tools/dotc/transform/init/Semantic.scala Lines 1207 to 1216 in 0be2091
|
8b89ae9
to
f2f9df3
Compare
[test_scala2_library_tasty] |
val outerEnvs = envSet.joinOuterEnvs | ||
if outerEnvs != NoEnv then // Search for the outerEnvs of the current envSet | ||
resolveEnvRecur(target, outerEnvs, bySymbol) | ||
else // Search through the outerEnvs of the instances represented by `this` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this really make sense as a fallback?
We should know statically whether a symbol that we're looking for is in a local env (in that case the TermRef has NoPrefix) or whether it is in the body of the this object (or one of its outers) (in that case the TermRef has some form of the this object as prefix). It's odd to search both of these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added another test local-class.scala
to explain this case. In the test, method bar
in loca class D
tries to access c
, which is a local variable without prefix. The scope in the Config is LocalEnv(bar)
when this access happens, but c
is defined in foo
, which is not a method in D
. When we find that bar
does not have any direct enclosing method, we have to look through the outerEnv
of D
since c
could be defined there, or defined in other outer methods that encloses some other local classes that encloses D
.
// meth == NoSymbol for poly functions | ||
if meth.name == nme.tupled then | ||
value // a call like `fun.tupled` | ||
else | ||
code match | ||
case ddef: DefDef => | ||
if meth.name == nme.apply then | ||
given Scope = Env.ofDefDef(ddef, args.map(_.value), ScopeSet(Set(env))) | ||
val funEnv = scope match { | ||
case ref: Ref => Env.ofDefDef(ddef, args.map(_.value), thisV, Env.NoEnv) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it correct to pass thisV here, or should it be ref?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's supposed to pass in the value of thisV
when the closure is created, and it is recorded in Fun
. I have renamed thisV
in this line to thisVOfClosure
for clarification
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, I only have a few minor comments for consideration.
* valsMap = sym -> val // maps variables to their values | ||
* outersMap = sym -> ScopeSet // maps the possible outer scopes for a corresponding (parent) class | ||
* heap.MutableData = Scope -> (valsMap, outersMap) // heap is mutable | ||
* Config ::= (thisV: Value, scope: Scope, Heap, EnvMap) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible for scope
in Config
to be Ref
? The same question for Fun
.
These are the only place where the type Scope
appears. If we can remove one concept, that would be nice (Occam's razor).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: regarding the name LocalEnv
, maybe we can use something like EnvAddr
or EnvRef
?
@@ -187,12 +202,13 @@ class Objects(using Context @constructorOnly): | |||
|
|||
object OfClass: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor (for future): We can consider rename OfClass
to InstanceRef
to make its nature of being an abstract address clear. The same for OfArray
.
def initVar(field: Symbol, value: Value)(using Context, EnvMap.EnvMapMutableData) = log("Initialize " + field.show + " = " + value + " for " + this, printer) { | ||
assert(field.is(Flags.Mutable), "Field is not mutable: " + field.show) | ||
EnvMap.writeJoinVal(this, field, value) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor (for future): Given that Var
and Val
have same logic, we can consider generalize a little the code.
* Ref ::= ObjectRef | OfClass | OfArray // values that represent a reference to some (global or instance) object | ||
* ThisValue ::= Ref | Set(Ref) // possible values for 'this' | ||
* RefSet ::= Set(ref) // set of refs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor (for future): There is some overlapping between ValueSet
and RefSet
. It seems the former is a subset of the latter. If that is the case, we can consider introducing an opaque type with a special constructor to encode the invariant.
Edit: I see RefSet
is made a subclass of ValueSet
. That is a clever trick. Might be worth adding a comment there warning that equality
is delegated to ValueSet
thus no more fields should be added.
case outer : ThisValue => | ||
if klass.owner.is(Flags.Package) then | ||
report.warning("[Internal error] top-level class should have `Package` as outer, class = " + klass.show + ", outer = " + outer.show + ", " + Trace.show, Trace.position) | ||
Env.NoEnv | ||
(Bottom, Env.NoEnv) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor (for future): Here we use Bottom
to mean absence of value. I think it is OK. /cc: @olhotak
f2f9df3
to
b1d5eae
Compare
This PR resolves bugs in the redesigned global initialization checker; specifically, it now correctly resolves the value of
parent.this
in a child class, and removes the assertions that fail in community build projects[test_scala2_library_tasty]