docs/_docs/contributing/issues/cause.md
In this section, you will be able to answer questions such as:
You may be able to quickly find the source responsible for an issue by consulting common issue locations
As described in the compiler lifecycle, each phase transforms the trees and types that represent your code in a certain way.
To print the code as it is transformed through the compiler, use the compiler flag -Vprint:all.
After each phase group is completed, you will see the resulting trees representing the code.
It is recommended to test
-Vprint:allon a single, small file, otherwise a lot of unnecessary output will be generated.
When you see a problematic tree appear after a certain phase group, you know to isolate the rest of
your search to the code of that phase. For example if you found a problematic tree after phase
posttyper, the problem most likely appears in the code of PostTyper. We can trace the exact point
the tree was generated by looking for its unique ID, and then generating a stack trace at its creation:
-Vprint:posttyper and -Yshow-tree-ids flags.
This will only print the trees of the posttyper phase. This time you should see the tree
in question be printed alongside its ID. You'll see something like println#223("Hello World"#37).-Ydebug-tree-with-id <tree-id> flag. The compiler will print a stack trace
pointing to the creation site of the tree with the provided ID.If you are using a debugger, you can add a breakpoint after if ownId == debugId then in method allocateId of Positioned.scala (you will still need the flag), this will allow you to navigate the stack with more ease.
Do not use a conditional breakpoint, the time overhead is very significant for a method called as often as this one.
As seen above -Vprint:<phase> can be enhanced with further configuration flags, found in
ScalaSettings. For example, you can additionally print the type of a tree with -Xprint-types.
Once you have identified the phase that generated a certain tree, you can then increase logging in that phase, to try and detect erroneous states:
-Ylog compiler flag, such as
-Ylog:<phase1>,<phase2>,... for individual phases-Ylog:all for all phases.noPrinter to default and increase output specialised
to that domain.The compiler issues user facing errors for code that is not valid, such as the type mismatch
of assigning an Int to a Boolean value. Sometimes these errors do not match what is expected, which could be a bug.
To discover why such a spurious error is generated, you can trace the code that generated the error by
adding the -Ydebug-error compiler flag, e.g. scala3/scalac -Ydebug-error Test.scala.
This flag forces a stack trace to be printed each time an error happens, from the site where it occurred.
Analysing the trace will give you a clue about the objects involved in producing the error. For example, you can add some debug statements before the error is issued to discover the state of the compiler. See some useful ways to debug values.
If you navigate to the site of the error, and discover a problematic object, you will want to know why it exists in such a state, as it could be the cause of the error. You can discover the creation site of that object to understand the logic that created it.
You can do this by injecting a tracer into the class of an instance in question. A tracer is the following variable:
val tracer = Thread.currentThread.getStackTrace.mkString("\n")
When placed as a member definition at a class, it will contain a stack trace pointing at where exactly its particular instance was created.
Once you've injected a tracer into a class, you can println that tracer from the error site or
other site you've found the object in question.
val
and see its inferred type.println to print the object or use getClass on that object.val tracer = Thread.currentThread.getStackTrace.mkString("\n") to that type definition.println(x.tracer) (where x is the name of the object in question) from the original site where you
encountered the object. This will give you the stack trace pointing to the place where the
constructor of that object was invoked.Say you have a certain type assigned to a Denotation and you would like to know why it has that
specific type. The type of a denotation is defined by var myInfo: Type, and can be assigned multiple times.
In this case, knowing the creation site of that Type, as described above, is not useful; instead, you need to
know the assignment (not creation) site.
This is done similarly to how you trace the creation site. Conceptually, you need to create a proxy for that variable that will log every write operation to it. Practically, if you are trying to trace the assignments to a variable myInfo of type Type, first, rename it to myInfo_debug. Then, insert the following at the same level as that variable:
var tracer = "",
def myInfo: Type = myInfo_debug,
def myInfo_=(x: Type) = { tracer = Thread.currentThread.getStackTrace.mkString("\n"); myInfo_debug = x }