Reproducing an Issue
The first step is to reproduce the issue you're trying to fix. Say you want to reproduce locally issue #7710, you would first copy the code from the "Minimised Code" section of the issue to a file named e.g. local/i7710.scala
, and then try to compile it from the sbt console opened in the dotty root directory:
$ sbt
sbt:scala3> scalac -d local/out local/i7710.scala
Here, the
-d
flag specifies a directorylocal/out
where generated code will be output.
You can then verify that the local reproduction has the same behaviour as originally reported in the issue. If so, then you can start to try and fix it. Otherwise, perhaps the issue is out of date, or is missing information about how to accurately reproduce the issue.
Compiling files with scalac
Let's take a deeper look at this scalac
command we just used. As we have seen you can compile a test file either from sbt:
$ sbt
> scalac <OPTIONS> <FILE>
in the same way that you could from terminal:
$ scalac <OPTIONS> <FILE>
Here are some useful debugging <OPTIONS>
:
-Xprint:PHASE1,PHASE2,...
or-Xprint:all
: prints theAST
after each specified phase. Phase names can be found by examining thedotty.tools.dotc.transform.*
classes for theirphaseName
field e.g.,-Xprint:erasure
. You can discover all phases in thedotty.tools.dotc.Compiler
class-Ylog:PHASE1,PHASE2,...
or-Ylog:all
: enablesctx.log("")
logging for the specified phase.-Ycheck:all
verifies the consistency ofAST
nodes between phases, in particular checks that types do not change. Some phases currently can't beYcheck
ed, therefore in the tests we run:-Ycheck:tailrec,resolveSuper,mixin,restoreScopes,labelDef
.- the last frontier of debugging (before actual debugging) is the range of logging capabilities that can be enabled through the
dotty.tools.dotc.config.Printers
object. Change any of the desired printer fromnoPrinter
todefault
and this will give you the full logging capability of the compiler.
You may also want to further inspect the types of a piece of code to verify the AST. Check out the section on How to Inspect Values for a detailed guide on this.
Dotty Issue Workspace
Sometimes you will need more complex commands to reproduce an issue, and it is useful to script these, which can be done with dotty-issue-workspace. It allows to bundle sbt commands for issue reproduction in one file and then run them from the Dotty project's sbt console.
Try an Example Issue
Let's use dotty-issue-workspace to reproduce issue #7710:
-
Follow the steps in the README to install the plugin.
-
In your Issue Workspace directory (as defined in the plugin's README file, "Getting Started" section, step 2), create a subdirectory for the issue:
mkdir i7710
. -
Create a file for the reproduction:
cd i7710; touch Test.scala
. In that file, insert the code from the issue. -
In the same directory, create a file
launch.iss
with the following content:$ (rm -rv out || true) && mkdir out # clean up compiler output, create `out` dir. scala3/scalac -d $here/out $here/Test.scala
- The first line,
$ (rm -rv out || true) && mkdir out
specifies a shell command (it starts with$
), in this case to ensure that there is a freshout
directory to hold compiler output. - The next line,
scala3/scalac -d $here/out $here/Test.scala
specifies an sbt command, which will compileTest.scala
and place any output intoout
.$here
is a special variable that will be replaced by the path of the parent directory oflaunch.iss
when executing the commands.
- The first line,
-
Now, from a terminal you can run the issue from sbt in the dotty directory (See here for a reminder if you have not cloned the repo.):
$ sbt sbt:scala3> issue i7710
This will execute all the commands in the
i7710/launch.iss
file one by one. If you've set updotty-issue-workspace
as described in its README, theissue
task will know where to find the folder by its name.
Using Script Arguments
You can use script arguments inside launch.iss
to reduce the number of steps when working with issues.
Say you have an issue foo
, with two alternative files that are very similar: original.scala
, which reproduces the issue, and alt.scala
, which does not, and you want to compile them selectively?
You can achieve this via the following launch.iss
:
$ (rm -rv out || true) && mkdir out # clean up compiler output, create `out` dir.
scala3/scalac -d $here/out $here/$1.scala # compile the first argument following `issue foo <arg>`
It is similar to the previous example, except now you will compile a file $1.scala
, referring to the first argument passed after the issue name. The command invoked would look like issue foo original
to compile original.scala
, and issue foo alt
for alt.scala
.
In general, you can refer to arguments passed to the issue <issue_name>
command using the dollar notation: $1
for the first argument, $2
for the second and so on.
Multiline Commands
Inside a launch.iss
file, one command can be spread across multiple lines. For example, if your command has multiple arguments, you can put each argument on a new line.
Multiline commands can even have comments in-between lines. This is useful if you want to try variants of a command with optional arguments (such as configuration). You can put the optional arguments on separate lines, and then decide when they are passed to the command by placing #
in front to convert it to a comment (i.e. the argument will not be passed). This saves typing the same arguments each time you want to use them.
The following launch.iss
file is an example of how you can use multiline commands as a template for solving issues that run compiled code. It demonstrates configuring the scala3/scalac
command using compiler flags, which are commented out. Put your favourite flags there for quick usage.
$ (rm -rv out || true) && mkdir out # clean up compiler output, create `out` dir.
scalac # Invoke the compiler task defined by the Dotty sbt project
-d $here/out # All the artefacts go to the `out` folder created earlier
# -Xprint:typer # Useful debug flags, commented out and ready for quick usage. Should you need one, you can quickly access it by uncommenting it.
# -Ydebug-error
# -Yprint-debug
# -Yprint-debug-owners
# -Yshow-tree-ids
# -Ydebug-tree-with-id 340
# -Ycheck:all
$here/$1.scala # Invoke the compiler on the file passed as the second argument to the `issue` command. E.g. `issue foo Hello` will compile `Hello.scala` assuming the issue folder name is `foo`.
scala -classpath $here/out Test # Run main method of `Test` generated by the compiler run.
Conclusion
In this section, you have seen how to reproduce an issue locally, and next you will see how to try and detect its root cause.