Branch: refs/heads/main
Revision: b789f5b6d242e1a93659c45c555e9d1657efa65f
Author: DanilaFe
Link: https://github.com/chapel-lang/chapel/pull/26613
Log Message:
Dyno: implement compilerError
and compilerWarning
(#26613)
This PR implements compilerError
and compilerWarning
(thus, closes
https://github.com/Cray/chapel-private/issues/6473).
This is a relatively tricky feature to implement in Dyno. In production,
it relies on the compiler's global mutable state. When an error
primitive is encountered, production traverses a stack of CallExpr
s
and finds the first suitable CallExpr
to anchor the error message to.
Notably, compilerError
can specify the depth to which to "throw" the
error message: the compiler is capable of anchoring errors arbitrarily
high in the call stack so as to skip internal implementation functions
which may lead to invoking compilerError
. For example:
proc _helper(x) {
compilerError("no", 2); // the 2 makes the error message go past the call to _helper
}
proc foo(x) do _helper(x);
proc main() {
foo(42); // error emitted here, rather than at th ecall site of _helper
}
This poses problems for Dyno, in which a global call stack does not
exist (and in fact, resolution can be occurring "out of order", with
functions notionally deep in the call stack being resolved first).
At the heart of the issue is that there are two components to
compilerError
that happen at the same in production, but cannot happen
at the same time in Dyno:
- Detecting that a compiler error occurred, and adjusting accordingly
(i.e., stopping resolving statements, signaling tocanResolve
, etc) - Print the compiler error to the user (which in Dyno can't happen
immediately because of the aforementioned out-of-order resolution)
To make (2) work, we have to track error messages' occurrences and emit
them at points where the production compiler would've stopped traversing
the call stack. To this end, this PR adjusts the ResolvedFunction
type
to have a list of diagnostics with a "time to live" equivalent, which
encodes how many more calls they can propagate past before they need to
be shown to the user. Each time a call is resolved, the
ResolvedFunction
of the callee is checked for diagnostics to
propagate. The diagnostics are then moved up into the current function's
resolution results with their "time to live" / depth reduced by one.
When the depth hits zero, the diagnostic is emitted to the user.
To make (1) work, though, something must happen at the point of the
call to compilerError
, so that runAndTrackErrors
, canResolve
, etc.
still function properly. Thus, this PR emits an error called
UserDiagnosticEncounterError
at the point of compilerError
. This
is distinct from printing the error message. In fact, the default
error reporting function ignores these errors (they are still stored in
the query system's error list). This additionally helps the case in
which compilerError
is called with a depth that exceeds the maximum
call stack depth (and thus, would never be emitted via the time-to-live
mechanism).
Another important aspect of this PR is that we cannot know whether a
function emits errors without resolving the function's body,
particularly because a number of nested calls can lead to a function
with compilerError
. As a result, we can no longer only resolve
signatures of called functions; we have to resolve the functions'
bodies. This would've been a very significant performance hit had we not
already been doing that in most cases; as it turns out, generic
functions' bodies are already resolved to collect PoI information.
An issue discovered during implementing this PR was that we resolve the
bodies of initializers before we run disambiguation. This means that
less-specific candidates (which ultimately would not be selected) are
resolved and have the opportunity to emit compiler errors. This PR
changes that by deferring resolving the function body until it is
selected as a most specific candidate.
MostSpecificCandidate::fromTypedSignature
seem like the natural place
to do this (since this helper is called precisely when a function is
chosen). This required threading through ResolutionContext
in more
cases of disambiguation.cpp
.
There was also a bug in which is generic type
(the prim) would return
true for all instantiated types (because it used their generic type base
to check for genericity). Based on @dlongnecke-cray 's comment, this was
intended to detect cases in which the production compiler reports
"generic" for mentions of generic-with-default types. I find the
production compiler's behavior surprising, and nobody seemed to
contradict me in Slack; as a result, I've removed the special casing
made by David, and changed Dyno to return "concrete" for names of
generic-with-defaults type expressions.
Reviewed by @benharsh -- thanks!
Testing
- dyno tests
- dyno tests are not significantly slowed down by new
runAndTrackErrors
- paratest (module code changed)
Diff:
M frontend/include/chpl/resolution/disambiguation.h
M frontend/include/chpl/resolution/resolution-error-classes-list.h
M frontend/include/chpl/resolution/resolution-queries.h
M frontend/include/chpl/resolution/resolution-types.h
M frontend/lib/framework/Context.cpp
M frontend/lib/resolution/Resolver.cpp
M frontend/lib/resolution/Resolver.h
M frontend/lib/resolution/disambiguation.cpp
M frontend/lib/resolution/prims.cpp
M frontend/lib/resolution/resolution-error-classes-list.cpp
M frontend/lib/resolution/resolution-queries.cpp
M frontend/lib/resolution/resolution-types.cpp
M frontend/test/framework/testQueryEquivalence.cpp
M frontend/test/resolution/CMakeLists.txt
M frontend/test/resolution/testCatch.cpp
A frontend/test/resolution/testCompilerError.cpp
M frontend/test/resolution/testManage.cpp
M frontend/test/resolution/testMethodFieldAccess.cpp
M frontend/test/resolution/testTypePropertyPrimitives.cpp
M modules/internal/ChapelDomain.chpl
M modules/internal/ChapelRange.chpl
https://github.com/chapel-lang/chapel/pull/26613.diff