Skip to content

Commit

Permalink
RakuAST: fix sequencing of BEGIN and CHECK handling
Browse files Browse the repository at this point in the history
If a node needs to perform some BEGIN time tasks after its children,
this could actually lead to those children not only running BEGIN but
also CHECK handling first. This resulted e.g. in parameters not yet
having a default type when their checks were performed (a time when the
AST should be completed).

Fix by forcing a resolve-only run on children of such nodes run and
doing a second run after all BEGIN time effects are applied.
  • Loading branch information
niner committed Apr 2, 2023
1 parent f5dda72 commit 7bcf8d1
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 4 deletions.
18 changes: 14 additions & 4 deletions src/Raku/ast/base.rakumod
Expand Up @@ -102,27 +102,37 @@ class RakuAST::Node {

# Apply any pre-children BEGIN-time effects that were not yet
# performed (and figure out if we have to do the later).
my int $needs-begin-after;
my int $needs-begin-after := nqp::istype(self, RakuAST::BeginTime) && self.is-begin-performed-after-children();
if nqp::istype(self, RakuAST::BeginTime) && self.is-begin-performed-before-children() {
self.ensure-begin-performed($resolver, $context, :phase(1));
}

# Visit children.
# Visit children. But don't run CHECK yet if this will be followed by more BEGIN handling
my int $is-scope := nqp::istype(self, RakuAST::LexicalScope);
my int $is-package := nqp::istype(self, RakuAST::Package);
$resolver.push-scope(self) if $is-scope;
$resolver.push-package(self) if $is-package;
self.visit-children(-> $child { $child.IMPL-CHECK($resolver, $context, $resolve-only) });
self.visit-children(-> $child { $child.IMPL-CHECK($resolver, $context, $resolve-only || $needs-begin-after) });
$resolver.pop-scope() if $is-scope;
$resolver.pop-package() if $is-package;

# Perform any after-children BEGIN-time effects.
if nqp::istype(self, RakuAST::BeginTime) && self.is-begin-performed-after-children() {
if $needs-begin-after {
self.ensure-begin-performed($resolver, $context, :phase(2));
}

# Unless in resolve-only mode, do other check-time activities.
unless $resolve-only {
if $needs-begin-after {
# Run children's CHECK processing. Couldn't do it earlier as
# that would have interfered with our BEGIN handling.
$resolver.push-scope(self) if $is-scope;
$resolver.push-package(self) if $is-package;
self.visit-children(-> $child { $child.IMPL-CHECK($resolver, $context, $resolve-only) });
$resolver.pop-scope() if $is-scope;
$resolver.pop-package() if $is-package;
}

if nqp::istype(self, RakuAST::SinkBoundary) && !self.sink-calculated {
self.calculate-sink();
}
Expand Down
4 changes: 4 additions & 0 deletions src/Raku/ast/begintime.rakumod
Expand Up @@ -41,6 +41,10 @@ class RakuAST::BeginTime
Nil
}

method begin-performed() {
$!begin-performed == 3
}

# Called when a BEGIN-time construct needs to evaluate code. Tries to
# interpret simple things to avoid the cost of compilation.
method IMPL-BEGIN-TIME-EVALUATE(RakuAST::Node $code, RakuAST::Resolver $resolver, RakuAST::IMPL::QASTContext $context) {
Expand Down
12 changes: 12 additions & 0 deletions src/Raku/ast/compunit.rakumod
Expand Up @@ -156,11 +156,23 @@ class RakuAST::CompUnit
}

method add-init-phaser(RakuAST::StatementPrefix::Phaser::Init $phaser) {
# Cannot rely on clear-attachments here as a node's attach can be called
# multiple times while going up and down the tree and clear-attachments will
# only be called once.
for $!init-phasers {
return Nil if $_ =:= $phaser;
}
nqp::push($!init-phasers, $phaser);
Nil
}

method add-end-phaser(RakuAST::StatementPrefix::Phaser::End $phaser) {
# Cannot rely on clear-attachments here as a node's attach can be called
# multiple times while going up and down the tree and clear-attachments will
# only be called once.
for $!end-phasers {
return Nil if $_ =:= $phaser;
}
nqp::push($!end-phasers, $phaser);
Nil
}
Expand Down

0 comments on commit 7bcf8d1

Please sign in to comment.