Update, 2021 And now there's RakuAST![1]
Curt's answer catches the errors chi wanted caught. This complementary answer is my initial exploration of Chi's follow on question asking:
Why, when compiling/running Curt's code, does Rakudo wait until run-time to report two of the three errors?
At the end of this initial exploration I arrive at wrapping Curt's code in a BEGIN
block. This code appears to report all errors at compile-time (one by one of course, after commenting out each prior error). (Click for runnable snippet.)[2]
This is a strawman answer, set up for Perl 6 core devs to shoot down.
An initial run of Curt's code results in:
===SORRY!=== ... Calling myprint(Distance) will never work...
The leading ===SORRY!===
signifies a "compile-time"[3] error.
But if we comment out the failing line and try again we get:
Type check failed in assignment to $d2...
This error message is a "run-time"[3] message -- it doesn't start with ===SORRY!===
.
Why did the compiler wait until "run-time" to complain?
The answer appears to lie in a combination of Perl 6's mostly-dynamic-by-default nature and the code that fails:
my Distance $d2 = $o; # Bummer! Type check fail
The my Distance $d2
part of this line is fully processed (introducing a new lexically scoped symbol $d2
) when the compiler first encounters this declaration at "compile-time". But the =
part of this line is a "run-time" operator; the initializing assignment, and corresponding type check, occur at "run-time".
But a dev may want to force the type check, and hence a type check error, to occur at compile-time. Now what?
BEGIN
time
Perl 6 supports program execution space/time travel via phasers. The first phaser is the BEGIN
phaser which "Runs at compile time, as soon as possible" and can be used like this:
BEGIN my Distance $d2 = $o;
If you re-compile with the above change, the error now appears at compile-time[3]:
===SORRY!=== Error while compiling...
An exception occurred while evaluating a BEGIN...
Type check failed in assignment to $d2...
If we now comment out the latest failing line and try again we get:
Cannot resolve caller Numeric(Distance: )...
There's no leading ===SORRY!===
so this is again a "run-time" error.
Rerunning the code with the failing line altered to:
BEGIN say $d + $o;
yields:
0
12
on stdout, and on stderr we get:
Use of uninitialized value of type Distance in numeric context...
Use of uninitialized value of type Offset in numeric context...
Uhoh. There's not only no compile-time error, there's no run-time error either! (The run-time warnings may give the game away about the 0
. Because the my...
lines declaring $d
and $o
were not prefixed with BEGIN
, these symbols have not yet been initialized at compile-time, which is the time that the BEGIN say $d + $o;
line gets run. But all of this is moot; we've clearly taken a step backwards.)
What happens if we wrap all of Curt's code in a single BEGIN
block?
BEGIN { ... Curt's code goes here ... }
BEGIN {
class Distance...
class Offset...
my Distance $d = Distance.new(12)...
sub myprint(Int $i) { say $i }
say $d + $o;...
}
Bingo! The errors are now all revealed as they were with Curt's original code but the reporting seems to happen at compile-time (one by one, after commenting out each prior error).
Footnotes
[1] In a comment I just wrote below Curt's answer, I've just written[1a]:
Hi @chi. I only just noticed your comment![1a] (Better late than never?) A Raku compiler is allowed to do as much static analysis as it likes but, 4 years after you wrote this SO, the only Raku compiler that's currently of practical note, Rakudo, currently does relatively little, because analysis makes compilation slower, and a top priority for Rakudo is to quickly start running code. That said, Rakudo is evolving, and after the RakuAST work is completed, it's reasonable to anticipate folk developing deeper static analysis modules.
[1a] Of course, I wrote my comment before I looked at my answer! This reflects my process; when I become aware that someone has upvoted (or downvoted) one of my older answers, I typically go check on the question as if I had not answered it -- ignoring my answer -- as a way to quickly get back up to speed on what was asked/said, while I still have a relatively fresh perspective that's not limited by the one I developed when I originally answered it. I had completely forgotten what my answer was, so was naturally struck by the fact it seemed I had not noticed @chi's comment, and had not replied. Further, my immediate reaction was/is that the new RakuAST work was relevant, as jnthn explains, thus my comment, and now my addition of this info to this answer.
[2] Code copied here in case glot.io ever goes away:
# See https://mcmap.net/q/1704766/-how-to-derive-own-distinguish-type-from-int
BEGIN { # to end of file
class Distance
{
has Int $!value handles<Int>;
method new($value where 0 <= * <= 32000) { self.bless(:$value) }
submethod BUILD(:$!value) {}
}
class Offset
{
has Int $!value handles<Int>;
method new($value where -32000 <= * <= 32000) { self.bless(:$value) }
submethod BUILD(:$!value) {}
}
my Distance $d = Distance.new(12); # ok
my Offset $o = Offset.new(-1); # ok
my Distance $d2 = $o; # Bummer! Type check fail
sub myprint(Int $i) { say $i }
say $d + $o; # Bummer!, can't add those objects
myprint $d; # Bummer!, $d isn't an Int, can't print
myprint Int($d); # ok, prints 12, converting with Int
}
[3] I scare quoted many references to "compile-time" and "run-time" because they have ambiguous meaning in Perl 6. Perl 6 lets user code do just about anything, including running the compiler, during run-time, and lets user code do just about anything, including run-time things, during compile-time. So from one perspective there can be one or more run-time phases within a compile-time phase and vice versa. But from a second perspective, there's the compile-time phase, i.e. when you're sitting there during a development session and you've just run the compiler. Likewise there's the run time phase, i.e. when your code is, for example, running "in production". Where I do not scare quote run-time / compile-time, I mean to refer to this second perspective.