-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Refine error messages for early promotion #15263
Conversation
liufengyun
commented
May 22, 2022
- Stop early promotion on first error
- Refine error messages for early promotion
The failure comes from the tree checker, due to a limitation in subtyping checker:
|
b9d3145
to
cbdda5f
Compare
The failure comes from the tree checker, due to a limitation in subtyping checker: ``` dotty.tools.dotc.transform.init.Semantic.Hot.type | (dotty.tools.dotc.transform.init.Semantic.Hot.type | dotty.tools.dotc.transform.init.Semantic.Cold.type ) <: dotty.tools.dotc.transform.init.Semantic.Hot.type | dotty.tools.dotc.transform.init.Semantic.Cold.type = false ```
cbdda5f
to
efb9776
Compare
tests/init/neg/closureLeak.check
Outdated
| | ||
| The unsafe promotion may cause the following problem: | ||
| Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. | ||
| Cannot prove the argument is fully initialized. |
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 still find these messages difficult to understand. Here's how I would explain this particular failure to someone. (We can think about making the error messages closer to such an explanation.)
- The arguments of a method call in a constructor must be fully initialized. I could not prove that the underlined argument is fully initialized.
- For a closure to be considered fully initialized, its body must access only fully initialized variables. I could not prove that the underlined access is fully initialized.
One general distinction I see is between "X must be fully initialized because that is the rule (e.g. argument to a method call in a constructor)" vs. "X must be fully initialized in order to prove that Y is fully initialized (e.g. expression in body of closure)".
tests/init/neg/inherit-non-hot.check
Outdated
| ^ | ||
| -> val bAgain = toB.getBAgain [ inherit-non-hot.scala:16 ] | ||
| ^^^ | ||
| May only assign fully initialized value. Calling trace: |
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.
- The RHS of an assignment to a field that may be called from a constructor must be fully initialized.
- This assignment may be called from a constructor because:
2a. The constructor of object Foo calls new C (line 19)
(It seems line 15 is not really relevant to the explanation.)
2b. The constructor of C calls A.toB (line 16)
2c. A.toB contains the assignment to the field. - I could not prove that the assigned value is fully initialized for the following reason.
3a. For the result of a constructor call to be fully initialized, all arguments of the call must be fully initialized. I could not prove that the argument this for the parameter a is fully initialized.
3b. I could not prove that this is fully initialized because the initializer of its field c has not yet run.
tests/init/neg/local-warm4.check
Outdated
| ^^^^^^^^^^^ | ||
| -> updateA() [ local-warm4.scala:21 ] | ||
| ^^^^^^^^^ | ||
| May only assign fully initialized value. Calling trace: |
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.
Again, I think there are two issues here:
- Why is line 18 called from a constructor? (Maybe say from the constructor of which class, in this case localWarm, through a stack of several other calls.)
- Why is newA not considered fully initialized? (In this case, because it comes from
new A(y)
, andy
is not fully initialized. Why is not fully initialized? Because the treenew A(y)
executes before the initializer ofA.y
runs. Why before? Because the constructor of B, which is a subclass of class A that contains the initializer of y, calls increment (line 23), which calls updateA (line 21), which contains the treenew A(y)
(line 17). )
tests/init/neg/promotion-loop.check
Outdated
| | ||
| The unsafe promotion may cause the following problem: | ||
| Cannot prove that the value is fully initialized. Only initialized values may be used as arguments. | ||
| Cannot prove the argument is fully initialized. |
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.
- The arguments of a method call in a constructor must be fully initialized. I could not prove that the underlined argument is fully initialized.
- The initializer of
b
instantiatesclass B
. - The constructor of
B
referencestest
, the self parameter of classTest
. - The
this
of classTest
is not yet initialized because the initializer of its fieldn
has not yet run.
While interpreting other error messages from the initialization checker, it occurred to me that it would be useful for each error message to start with a statement of which class (constructor) is being checker. The error message starts with the location where the initialization checker failed to prove that a class is safely initialized, but often this location is far removed from the class that is not safely initialized. |
This is a good improvement. Now we make sure that the top of the trace is always the class under check. The tracing and error reporting mechanism is overhauled and the low-hanging fruits are dealt with. For other suggestions, we need to discuss how to tackle them. As this PR mainly focuses on overhauling the infrastructure of error reporting, can we address the remaining concerns in other PRs? |
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.
My comments here are minor. After you consider them, this can be merged.
if allArgsPromote then | ||
Hot: Value | ||
else if errors.nonEmpty then | ||
for error <- errors do reporter.report(error) |
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.
Could be factored into a method (reportAll) in Reporter.
Hot: Value | ||
else if errors.nonEmpty then | ||
for error <- errors do reporter.report(error) | ||
Hot: 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.
Why is the ascription needed?
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.
This is due to a TreeChecker failure:
I have no idea why the subtyping failed --- it shouldn't be.
val cls = target.owner.enclosingClass.asClass | ||
val ddef = target.defTree.asInstanceOf[DefDef] | ||
val argErrors = checkArgs | ||
val argErrors = Reporter.errorsIn { args.foreach(_.promote) } |
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.
Why not Reporter.errorsIn { checkArgs } ?
def call(meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, source: Tree, needResolve: Boolean = true): Contextual[Result] = log("call " + meth.show + ", args = " + args, printer, (_: Result).show) { | ||
def checkArgs = args.flatMap(_.promote) | ||
def call(meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, needResolve: Boolean = true): Contextual[Value] = log("call " + meth.show + ", args = " + args, printer, (_: Value).show) { | ||
def checkArgs = args.foreach(_.promote) |
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.
Consider renaming to promoteArgs
. Also consider parentheses promoteArgs()
to indicate side effect and Unit return type. But many other methods here have side effects, so maybe not.
I agree that this PR is already a good improvement - the call traces are more informative. The class being checked at the top of the trace really helps. I also agree with a separate discussion and separate PR for further improvements. |