# This file automatically generated by /friends/.rakubrew/versions/moar-main/tools/build/gen-cat.nqp #line 1 src/Perl6/Actions.nqp use NQPP6QRegex; use NQPP5QRegex; use Perl6::Pod; use Perl6::Ops; use QRegex; use QAST; my $wantwant := Mu; # block types my $para-block := 'paragraph'; my $delim-block := 'delimited'; my $abbrev-block := 'abbreviated'; # 2147483648 == 2**31. By adding 1 to it with add_i op, on 32-bit boxes it will overflow my int $?BITS := nqp::isgt_i(nqp::add_i(2147483648, 1), 0) ?? 64 !! 32; sub block_closure($code, :$regex) { my $clone := QAST::Op.new( :op('callmethod'), :name('clone'), $code ); if $regex { if nqp::getcomp('Raku').language_revision < 2 { my $marker := $*W.find_symbol(['Rakudo', 'Internals', 'RegexBoolification6cMarker']); $clone.push(QAST::WVal.new( :value($marker), :named('topic') )); } else { $clone.push(QAST::Var.new( :name('$_'), :scope('lexical'), :named('topic') )); $clone.push(QAST::Var.new( :name('$/'), :scope('lexical'), :named('slash') )); } } QAST::Op.new( :op('p6capturelex'), $clone ).annotate_self( 'past_block', $code.ann('past_block') ).annotate_self( 'code_object', $code.ann('code_object')) } sub wantall($ast, $by) { my int $e := $ast ?? nqp::elems(@($ast)) !! 0; my int $i := -1; while ++$i < $e { $ast[$i] := wanted($ast[$i], $by ~ ' wa'); } Nil; } sub WANTALL($ast, $by) { my int $e := $ast ?? nqp::elems(@($ast)) !! 0; my int $i := -1; while ++$i < $e { $ast[$i] := WANTED($ast[$i], $by ~ ' WA'); } Nil; } sub unwantall($ast, $by) { my int $e := $ast ?? nqp::elems(@($ast)) !! 0; my int $i := -1; while ++$i < $e { $ast[$i] := unwanted($ast[$i], $by ~ ' ua'); } Nil; } sub UNWANTALL($ast, $by) { my int $e := $ast ?? nqp::elems(@($ast)) !! 0; my int $i := -1; while ++$i < $e { $ast[$i] := UNWANTED($ast[$i], $by ~ ' ua'); } Nil; } # Note that these wanted/unwanted routines can return a different ast # from the one passed, so always store the result back from where # got it. (Like how wantall does it above.) sub wanted($ast,$by) { # $wantwant := nqp::getenvhash unless nqp::isconcrete($wantwant); return $ast if nqp::not_i(nqp::can($ast,'ann')) || $ast.wanted # already marked from here down || $ast.sunk; # already marked from here down my $byby := $wantwant ?? $by ~ ' u' !! $by; my $addr := nqp::where($ast); note('wanted ' ~ $addr ~ ' by ' ~ $by ~ "\n" ~ $ast.dump) if $wantwant; # if $ast.sunk { # note("Oops, already sunk node is now wanted!?! \n" ~ $ast.dump); # $ast.sunk(0); # } my $e := nqp::elems(@($ast)) - 1; $ast.annotate('BY',$byby) if $wantwant; if nqp::istype($ast,QAST::Stmt) || nqp::istype($ast,QAST::Stmts) { my $resultchild := $ast.resultchild // $e; my int $i := -1; while ++$i <= $e { $ast[$i] := $i == $resultchild ?? wanted($ast[$i], $byby) !! unwanted($ast[$i], $byby); } $ast.wanted(1); } elsif nqp::istype($ast,QAST::Block) { my int $i := 1; my $*WANTEDOUTERBLOCK := $ast; while $i <= $e { $ast[$i] := WANTED($ast[$i], $byby); ++$i; } $ast.wanted(1); } elsif nqp::istype($ast,QAST::Op) { my $op := $ast.op; if $op eq 'call' && ( !(my $name := $ast.name) || $name eq '&infix:<,>' || $name eq '&infix:' || $name eq '&infix:' || $name eq '&infix:' || $name eq '&infix:') { WANTALL($ast,$byby); } elsif $op eq 'callmethod' { WANTALL($ast,$byby); } elsif $op eq 'p6capturelex' { $ast.annotate('past_block', wanted($ast.ann('past_block'), $byby)); $ast.wanted(1); } elsif $op eq 'call' || $op eq 'callstatic' || $op eq 'handle' || $op eq 'locallifetime' || $op eq 'p6typecheckrv' || $op eq 'handlepayload' { $ast[0] := WANTED($ast[0], $byby) if nqp::elems(@($ast)); $ast.wanted(1); } elsif $op eq 'p6decontrv' || $op eq 'p6decontrv_6c' { $ast[1] := WANTED($ast[1], $byby) if nqp::elems(@($ast)); $ast.wanted(1); } elsif $op eq 'while' || $op eq 'until' || $op eq 'repeat_while' || $op eq 'repeat_until' { my $repeat := nqp::eqat($op,'repeat',0); my $while := nqp::index($op,'while',0) >= 0; # we always have a body my $cond := WANTED($ast[0],$byby); my $body := WANTED($ast[1],$byby); my $block; my $block-closure; if $body.ann('loop-already-block-first-phaser') -> $loop-goods { $block := $loop-goods[0][1][0]; $block-closure := set_first_flag(block_closure($block)); # get rid of now-useless var and other bits of the QAST. # If we .shift off all items, the QAST::Stmts gets a null # in them that I can't figure out where it's coming from, # so shove an empty QAST::Smts to replace last item. $loop-goods.shift; $loop-goods[0] := QAST::Stmts.new; } else { $block := Perl6::Actions::make_thunk_ref($body, $body.node); $block-closure := block_closure($block); } # make sure from-loop knows about existing label my $world := $*W; my $label := QAST::WVal.new( :value($world.find_single_symbol_in_setting('Any')), :named('label') ); for @($ast) { $label := $_ if nqp::istype($_, QAST::WVal) && nqp::istype($_.value, $world.find_single_symbol_in_setting('Label')); } my $past := QAST::Op.new: :node($body.node), :op, :name, QAST::WVal.new(:value($world.find_single_symbol_in_setting('Seq'))), $block-closure, $label; # Elevate statevars to enclosing thunk if $body.has_ann('has_statevar') && $block.has_ann('past_block') { Perl6::Actions::migrate_blocks( $body, $block.ann('past_block'), -> $n { nqp::istype($n, QAST::Var) && $n.decl eq 'statevar' } ) } # conditional (if not always true (or if repeat)) if $repeat || !$cond.has_compile_time_value || !$cond.compile_time_value == $while { $cond := QAST::Op.new( :op, :name, $cond ) unless $while; $block := Perl6::Actions::make_thunk_ref($cond, nqp::can($cond,'node') ?? $cond.node !! $body.node); $past.push( block_closure($block) ); } # 3rd part of loop, if any if nqp::elems(@($ast)) > 2 { $block := Perl6::Actions::make_thunk_ref($ast[2], $ast[2].node); $block.annotate('outer',$*WANTEDOUTERBLOCK) if $*WANTEDOUTERBLOCK; $past.push( UNWANTED(block_closure($block),$byby) ) } if $repeat { my $wval := QAST::WVal.new( :value($world.find_single_symbol_in_setting('True')) ); $wval.named('repeat'); $past.push($wval); } $ast := $past; $ast.wanted(1); } elsif $op eq 'if' || $op eq 'unless' || $op eq 'with' || $op eq 'without' { $ast[1] := WANTED($ast[1], $byby); $ast[2] := WANTED($ast[2], $byby) if nqp::elems(@($ast)) > 2 && nqp::istype($ast[2],QAST::Node); $ast.wanted(1); } } elsif nqp::istype($ast,QAST::Want) { $ast.wanted(1); my $node := $ast[0]; if nqp::istype($node,QAST::Op) { my $op := $node.op; if $op eq 'call' && (!$node.name || $node.name eq '&infix:') { $node := $node[0]; if nqp::istype($node,QAST::Op) && $node.op eq 'p6capturelex' { $node.annotate('past_block', WANTED($node.ann('past_block'), $byby)); } } elsif $op eq 'call' || $op eq 'handle' { $ast[0] := WANTED($node,$byby); } elsif $op eq 'callstatic' || $op eq 'hllize' { $node[0] := WANTED($node[0], $byby); } elsif $op eq 'p6for' || $op eq 'p6forstmt' { $node := $node[1]; if nqp::istype($node,QAST::Op) && $node.op eq 'p6capturelex' { $node.annotate('past_block', WANTED($node.ann('past_block'), $byby)); } } elsif $op eq 'while' || $op eq 'until' || $op eq 'repeat_while' || $op eq 'repeat_until' { return WANTED($node,$byby) if !$*COMPILING_CORE_SETTING; $node[1] := WANTED($node[1], $byby); $node.wanted(1); } elsif $op eq 'if' || $op eq 'unless' || $op eq 'with' || $op eq 'without' { $node[1] := WANTED($node[1], $byby); $node[2] := WANTED($node[2], $byby) if nqp::elems(@($node)) > 2 && nqp::istype($node[2],QAST::Node); $node.wanted(1); } } } else { $ast.wanted: 1; } $ast; } sub WANTED($ast, $by) { if nqp::istype($ast, QAST::Node) { $ast := wanted($ast, $by ~ ' W'); $ast.wanted(1); # force in case it's just a thunk } else { note("Non ast passed to WANTED: " ~ $ast.HOW.name($ast)); } $ast; } my %nosink := nqp::hash('sink',1,'push',1,'append',1,'unshift',1,'prepend',1,'splice',1); sub unwanted($ast, $by) { return $ast if nqp::not_i(nqp::can($ast,'ann')) || $ast.sunk || $ast.wanted; # probably a loose thunk just stashed somewhere random my $byby := $by ~ ' u'; my $addr := nqp::where($ast); $ast.annotate('BY',$byby) if $wantwant; my $e := nqp::elems(@($ast)) - 1; note('unwanted ' ~ $addr ~ ' by ' ~ $by ~ "\n" ~ $ast.dump) if $wantwant; if nqp::istype($ast,QAST::Stmt) || nqp::istype($ast,QAST::Stmts) { # Unwant all kids, not just last one, so we recurse into blocks and such, # don't just rely on the optimizer to default to void. my int $i := -1; while ++$i <= $e { $ast[$i] := unwanted($ast[$i], $byby); } $ast.sunk(1); $ast.push(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('True')) )) if $e >= 0 && nqp::istype($ast[$e],QAST::Op) && $ast[$e].op eq 'bind'; } elsif nqp::istype($ast,QAST::Block) { my int $i := 1; my $*WANTEDOUTERBLOCK := $ast; while $i <= $e { $ast[$i] := UNWANTED($ast[$i], $byby); ++$i; } $ast.sunk(1); } elsif nqp::istype($ast,QAST::Op) { my $op := $ast.op; if $op eq 'call' { my $name := $ast.name; if $name eq '&infix:<,>' || $name eq '&infix:' { UNWANTALL($ast,$byby); } elsif $name eq '&term:' { $ast.node.worry("Useless use of 'now' in sink context"); } $ast.sunk(1); } elsif $op eq 'p6capturelex' { $ast.annotate('past_block', unwanted($ast.ann('past_block'), $byby)); $ast.sunk(1); } elsif $op eq 'callstatic' || $op eq 'handle' || $op eq 'locallifetime' || $op eq 'p6typecheckrv' || $op eq 'handlepayload' || $op eq 'ifnull' { $ast[0] := UNWANTED($ast[0], $byby) if nqp::elems(@($ast)); $ast.sunk(1); } elsif $op eq 'hllize' { my $node := $ast[0]; if $node.op eq 'callmethod' && !$ast.nosink { if !$node.nosink && !$*COMPILING_CORE_SETTING && !%nosink{$node.name} { $ast.sunk(1); $ast := QAST::Op.new(:op, $ast); $ast.sunk(1); return $ast; } } $ast.sunk(1); } elsif $op eq 'callmethod' { if !$ast.nosink && !$*COMPILING_CORE_SETTING && !%nosink{$ast.name} { return $ast if $*ALREADY_ADDED_SINK_CALL; $ast.sunk(1); $ast := QAST::Op.new(:op, $ast); $ast.sunk(1); return $ast; } $ast[0] := UNWANTED($ast[0], $byby) if nqp::elems(@($ast)); $ast.sunk(1); } elsif $op eq 'p6decontrv' || $op eq 'p6decontrv_6c' { $ast[1] := UNWANTED($ast[1], $byby) if nqp::elems(@($ast)); $ast.sunk(1); } elsif $op eq 'while' || $op eq 'until' || $op eq 'repeat_while' || $op eq 'repeat_until' { # Do we need to force loop to produce return values for internal reasons? if !$*COMPILING_CORE_SETTING && $ast[1].ann('WANTMEPLEASE') { $ast := QAST::Op.new(:op, :name, WANTED($ast, $byby)); $ast.sunk(1); return $ast; } $ast[1] := UNWANTED($ast[1], $byby); $ast.sunk(1); } elsif $op eq 'if' || $op eq 'unless' || $op eq 'with' || $op eq 'without' { $ast[1] := UNWANTED($ast[1], $byby); $ast[2] := UNWANTED($ast[2], $byby) if nqp::elems(@($ast)) > 2 && nqp::istype($ast[2],QAST::Node); $ast.sunk(1); } elsif $op eq 'bind' { $ast.sunk(1); } elsif $op eq 'xor' { my int $i := 1; my int $elems := nqp::elems($ast); while $i <= $e { $ast[$i] := UNWANTED($ast[$i], $byby); ++$i; } $ast.sunk: 1; } } elsif nqp::istype($ast,QAST::Want) { $ast.sunk(1); my $node := $ast[0]; if nqp::istype($node,QAST::WVal) { $node.sunk(1); $ast[2].sunk(1); } elsif nqp::istype($node,QAST::Op) { my $op := $node.op; if $op eq 'call' { $node.sunk(1); if !$node.name { my $node0 := $node[0]; unwanted($node0, $byby); if nqp::istype($node0,QAST::Op) && $node0.op eq 'call' && nqp::eqat($node0.name, '&META', 0) { my $op := $node.node.Str; my $t := nqp::index($op,' '); $op := nqp::substr($op, 0, $t) if $t > 0; my $purity := 0; if $node0[0].ann('is_pure') { $purity := 1 unless $node0.name eq '&METAOP_ASSIGN'; } else { my $subname := $node0[0].name; my $subfun := try $*W.find_single_symbol($subname); if $subfun { if nqp::index($node0.name, 'ASSIGN') < 0 && nqp::can($subfun, 'is-pure') { $purity := 1; } } else { $purity := 1; # "can't happen" except in setting, so assume will be pure } } $node.node.PRECURSOR.worry("Useless use of $op in sink context") if $purity; } } else { my $infix := $node.node; if $infix { my $sym := ~$infix; if $sym eq ',' || $sym eq 'xx' { unwantall($node, $byby) } elsif $sym eq '...' || $sym eq '...^' || $sym eq '^...' || $sym eq '^...^' || $sym eq '…' || $sym eq '…^' || $sym eq '^…' || $sym eq '^…^' { $node.annotate('useless', $sym); $node.node.worry("Useless use of $sym in sink context"); } } elsif $node.name eq '&term:' { $node.annotate('useless', "'now'"); $node.node.worry("Useless use of 'now' in sink context"); } } } elsif $op eq 'hllize' { $ast[0] := UNWANTED($node,$byby); } elsif $op eq 'callmethod' { if !$node.nosink && !%nosink{$node.name} { $ast := QAST::Op.new(:op, unwanted($node, $byby)); $ast.sunk(1); return $ast; } $node.sunk(1); } elsif $op eq 'p6for' || $op eq 'p6forstmt' { $node := $node[1]; if nqp::istype($node,QAST::Op) && $node.op eq 'p6capturelex' { unless $*COMPILING_CORE_SETTING { add-sink-to-final-call($node.ann('past_block'), 1); my $*ALREADY_ADDED_SINK_CALL := 1; $node.annotate('past_block', UNWANTED($node.ann('past_block'), $byby)); } } } elsif $op eq 'while' || $op eq 'until' { if !$*COMPILING_CORE_SETTING && $node[1].ann('WANTMEPLEASE') { $ast := QAST::Op.new(:op, :name, WANTED($node, $byby)); $ast.sunk(1); return $ast; } $node[1] := UNWANTED($node[1], $byby); $node.sunk(1); } elsif $op eq 'if' || $op eq 'unless' || $op eq 'with' || $op eq 'without' { for 1,2 { if nqp::elems(@($node)) > $_ && nqp::istype($node[$_],QAST::Node) { if nqp::istype($node[$_],QAST::Op) && $node[$_].op eq 'bind' { $node[$_] := QAST::Stmts.new( $node[$_], QAST::WVal.new( :value($*W.find_single_symbol_in_setting('True')))); } $node[$_] := UNWANTED($node[$_], $byby); } } $node.sunk(1); } elsif $node.op eq 'callmethod' && $node.name eq 'new' { $node.sunk(1); } } } $ast; } sub add-sink-to-final-call($parent, $pos, $qast = $parent[$pos]) { if (nqp::istype($qast, QAST::Stmts) || nqp::istype($qast, QAST::Stmt)) && nqp::elems($qast) { add-sink-to-final-call($qast, nqp::elems($qast)-1) } elsif nqp::istype($qast, QAST::Want) { add-sink-to-final-call($parent, $pos, $qast[0]) } elsif nqp::istype($qast, QAST::Op) && $qast.op eq 'call' && !$qast.nosink { $parent[$pos] := QAST::Op.new: :op, $qast } } sub UNWANTED($ast, $by) { if nqp::istype($ast, QAST::Node) { $ast := unwanted($ast, $by ~ ' U'); $ast.sunk(1); } else { note("Non ast passed to UNWANTED: " ~ $ast.HOW.name($ast)); } $ast; } register_op_desugar('p6box_i', -> $qast { QAST::Op.new( :op('box_i'), $qast[0], QAST::Op.new( :op('hllboxtype_i') ) ) }); register_op_desugar('p6box_n', -> $qast { QAST::Op.new( :op('box_n'), $qast[0], QAST::Op.new( :op('hllboxtype_n') ) ) }); register_op_desugar('p6box_s', -> $qast { QAST::Op.new( :op('box_s'), $qast[0], QAST::Op.new( :op('hllboxtype_s') ) ) }); register_op_desugar('p6box_u', -> $qast { QAST::Op.new( :op('box_u'), $qast[0], QAST::Op.new( :op('hllboxtype_i') ) ) }); register_op_desugar('p6reprname', -> $qast { QAST::Op.new( :op('box_s'), QAST::Op.new( :op('reprname'), $qast[0]), QAST::Op.new( :op('hllboxtype_s') ) ) }); register_op_desugar('p6callmethodhow', -> $qast { $qast := QAST::Op.new(:op, :name($qast.name), |$qast.list); my $inv := $qast.shift; my $tmp := QAST::Node.unique('how_invocant'); $qast.unshift(QAST::Var.new( :name($tmp), :scope('local') )); $qast.unshift(QAST::Op.new( :op('how'), QAST::Var.new( :name($tmp), :scope('local') ) )); QAST::Stmts.new( QAST::Op.new( :op('bind'), QAST::Var.new( :name($tmp), :scope('local'), :decl('var') ), $inv ), QAST::Op.new( :op('hllize'), $qast ) ) }); register_op_desugar('p6fatalize', -> $qast { my $tmp := QAST::Node.unique('fatalizee'); QAST::Stmts.new( :resultchild(0), QAST::Op.new( :op('bind'), QAST::Var.new( :name($tmp), :scope('local'), :decl('var') ), $qast[0] ), QAST::Op.new( :op('if'), QAST::Op.new( :op('istype'), QAST::Var.new( :name($tmp), :scope('local') ), $qast[1], ), QAST::Op.new( :op('callmethod'), :name('sink'), QAST::Var.new( :name($tmp), :scope('local') ) ) )) }); register_op_desugar('p6for', -> $qast { # Figure out the execution mode. my $mode := $qast.ann('mode') || 'serial'; my $after-mode; if $mode eq 'lazy' { $after-mode := 'lazy'; $mode := 'serial'; } else { $after-mode := $qast.sunk ?? 'sink' !! 'eager'; } my $cond := $qast[0]; my $block := $qast[1]; my $label := $qast[2]; my $for-list-name := QAST::Node.unique('for-list'); my $call := QAST::Op.new( :op('if'), QAST::Op.new( :op('iscont'), QAST::Var.new( :name($for-list-name), :scope('local') ) ), QAST::Op.new( :op, :name, :node($qast), QAST::Var.new( :name($for-list-name), :scope('local') ), $block, QAST::IVal.new( :value(1), :named('item') ) ), QAST::Op.new( :op, :name, :node($qast), QAST::Op.new( :op, :name($mode), :node($qast), QAST::Var.new( :name($for-list-name), :scope('local') ) ), $block ) ); if $label { $call[1].push($label); $call[2].push($label); } my $bind := QAST::Op.new( :op('bind'), QAST::Var.new( :name($for-list-name), :scope('local'), :decl('var') ), $cond, ); QAST::Stmts.new( $bind, QAST::Op.new( :op, :name($after-mode), $call ) ); }); register_op_desugar('p6forstmt', -> $qast { my $for-target-name := QAST::Node.unique('for_target'); my $for-target := QAST::Op.new( :op('bind'), QAST::Var.new( :name($for-target-name), :scope('local'), :decl('var') ), $qast[0] ); my $iterator-name := QAST::Node.unique('for_iterator'); my $iterator := QAST::Op.new( :op('bind'), QAST::Var.new( :name($iterator-name), :scope('local'), :decl('var') ), QAST::Op.new( :op('callmethod'), :name('iterator'), QAST::Op.new( :op('if'), QAST::Op.new( :op('iscont'), QAST::Var.new( :name($for-target-name), :scope('local') ) ), QAST::Op.new( :op('callstatic'), :name('&infix:<,>'), QAST::Var.new( :name($for-target-name), :scope('local') ) ), QAST::Var.new( :name($for-target-name), :scope('local') ) ))); my $iteration-end-name := QAST::Node.unique('for_iterationend'); my $iteration-end := QAST::Op.new( :op('bind'), QAST::Var.new( :name($iteration-end-name), :scope('local'), :decl('var') ), QAST::WVal.new( :value($qast.ann('IterationEnd')) ) ); my $block-name := QAST::Node.unique('for_block'); my $block := QAST::Op.new( :op('bind'), QAST::Var.new( :name($block-name), :scope('local'), :decl('var') ), QAST::Op.new( :op('getattr'), $qast[1], QAST::WVal.new( :value($qast.ann('Code')) ), QAST::SVal.new( :value('$!do') ) ) ); my $iter-val-name := QAST::Node.unique('for_iterval'); my $loop := QAST::Op.new( :op('until'), QAST::Op.new( :op('eqaddr'), QAST::Op.new( :op('decont'), QAST::Op.new( :op('bind'), QAST::Var.new( :name($iter-val-name), :scope('local'), :decl('var') ), QAST::Op.new( :op('callmethod'), :name('pull-one'), QAST::Var.new( :name($iterator-name), :scope('local') ) ) ) ), QAST::Var.new( :name($iteration-end-name), :scope('local') ) ), QAST::Op.new( :op('call'), QAST::Var.new( :name($block-name), :scope('local') ), QAST::Var.new( :name($iter-val-name), :scope('local') ) )); if $qast[2] { $loop.push($qast[2]); } QAST::Stmts.new( $for-target, $iterator, $iteration-end, $block, $loop, QAST::WVal.new( :value($qast.ann('Nil')) ) ) }); register_op_desugar('p6scalarfromdesc', -> $qast { my $desc := QAST::Node.unique('descriptor'); my $Scalar := QAST::WVal.new( :value(nqp::gethllsym('Raku', 'Scalar')) ); my $default_cont_spec := nqp::gethllsym('Raku', 'default_cont_spec'); QAST::Stmt.new( QAST::Op.new( :op('bind'), QAST::Var.new( :name($desc), :scope('local'), :decl('var') ), $qast[0] ), QAST::Op.new( :op('unless'), QAST::Op.new( :op('isconcrete'), QAST::Var.new( :name($desc), :scope('local') ), ), QAST::Op.new( :op('bind'), QAST::Var.new( :name($desc), :scope('local') ), QAST::WVal.new( :value($default_cont_spec) ) ) ), QAST::Op.new( :op('p6bindattrinvres'), QAST::Op.new( :op('p6bindattrinvres'), QAST::Op.new( :op('create'), $Scalar ), $Scalar, QAST::SVal.new( :value('$!descriptor') ), QAST::Var.new( :name($desc), :scope('local') ) ), $Scalar, QAST::SVal.new( :value('$!value') ), QAST::Op.new( :op('callmethod'), :name('default'), QAST::Var.new( :name($desc), :scope('local') ) ) ) ) }); # The "certain" variant is allowed to assume the container descriptor is # reliably provided, so need not map it to the default one. Ideally, we'll # eventually have everything using this version of the op. register_op_desugar('p6scalarfromcertaindesc', -> $qast { my $desc := QAST::Node.unique('descriptor'); my $Scalar := QAST::WVal.new( :value(nqp::gethllsym('Raku', 'Scalar')) ); QAST::Stmt.new( QAST::Op.new( :op('bind'), QAST::Var.new( :name($desc), :scope('local'), :decl('var') ), $qast[0] ), QAST::Op.new( :op('p6bindattrinvres'), QAST::Op.new( :op('p6bindattrinvres'), QAST::Op.new( :op('create'), $Scalar ), $Scalar, QAST::SVal.new( :value('$!descriptor') ), QAST::Var.new( :name($desc), :scope('local') ) ), $Scalar, QAST::SVal.new( :value('$!value') ), QAST::Op.new( :op('callmethod'), :name('default'), QAST::Var.new( :name($desc), :scope('local') ) ) ) ) }); register_op_desugar('p6scalarwithvalue', -> $qast { my $Scalar := QAST::WVal.new( :value(nqp::gethllsym('Raku', 'Scalar')) ); QAST::Op.new( :op('p6assign'), QAST::Op.new( :op('p6bindattrinvres'), QAST::Op.new( :op('create'), $Scalar ), $Scalar, QAST::SVal.new( :value('$!descriptor') ), $qast[0] ), $qast[1] ) }); register_op_desugar('p6recont_ro', -> $qast { my $result := QAST::Node.unique('result'); my $Scalar := QAST::WVal.new( :value(nqp::gethllsym('Raku', 'Scalar')) ); QAST::Stmt.new( QAST::Op.new( :op('bind'), QAST::Var.new( :name($result), :scope('local'), :decl('var') ), $qast[0] ), QAST::Op.new( :op('if'), QAST::Op.new( :op('if'), QAST::Op.new( :op('isconcrete_nd'), QAST::Var.new( :name($result), :scope('local') ) ), QAST::Op.new( :op('isrwcont'), QAST::Var.new( :name($result), :scope('local') ) ) ), QAST::Op.new( :op('p6bindattrinvres'), QAST::Op.new( :op('create'), $Scalar ), $Scalar, QAST::SVal.new( :value('$!value') ), QAST::Op.new( :op('decont'), QAST::Var.new( :name($result), :scope('local') ) ) ), QAST::Var.new( :name($result), :scope('local') ) ) ) }); register_op_desugar('p6var', -> $qast { my $result := QAST::Node.unique('result'); my $Scalar := QAST::WVal.new( :value(nqp::gethllsym('Raku', 'Scalar')) ); my $ScalarVAR := QAST::WVal.new( :value(nqp::gethllsym('Raku', 'ScalarVAR')) ); QAST::Stmt.new( QAST::Op.new( :op('bind'), QAST::Var.new( :name($result), :scope('local'), :decl('var') ), $qast[0] ), QAST::Op.new( :op('if'), QAST::Op.new( :op('if'), QAST::Op.new( :op('isconcrete_nd'), QAST::Var.new( :name($result), :scope('local') ) ), QAST::Op.new( :op('iscont'), QAST::Var.new( :name($result), :scope('local') ) ) ), QAST::Op.new( :op('p6bindattrinvres'), QAST::Op.new( :op('create'), $ScalarVAR ), $Scalar, QAST::SVal.new( :value('$!value') ), QAST::Var.new( :name($result), :scope('local') ) ), QAST::Var.new( :name($result), :scope('local') ) ) ) }); register_op_desugar('time_i', -> $qast { QAST::Op.new( :op('div_i'), QAST::Op.new( :op('time' ) ), QAST::IVal.new( :value(1000000000) ) ) }); register_op_desugar('time_n', -> $qast { QAST::Op.new( :op('div_n'), QAST::Op.new( :op('time' ) ), QAST::NVal.new( :value(1000000000e0) ) ) }); { my $is_moar; register_op_desugar('p6decontrv_internal', -> $qast { unless nqp::isconcrete($is_moar) { $is_moar := nqp::getcomp('Raku').backend.name eq 'moar'; } if $is_moar { QAST::Op.new( :op('dispatch'), QAST::SVal.new( :value($qast[1] eq '6c' ?? 'raku-rv-decont-6c' !! 'raku-rv-decont') ), QAST::Op.new( :op('p6box'), QAST::Op.new( :op('wantdecont'), $qast[0] ) ) ) } else { my $result := QAST::Node.unique('result'); my $Scalar := QAST::WVal.new( :value(nqp::gethllsym('Raku', 'Scalar')) ); my $Iterable := QAST::WVal.new( :value(nqp::gethllsym('Raku', 'Iterable')) ); QAST::Stmt.new( QAST::Op.new( :op('bind'), QAST::Var.new( :name($result), :scope('local'), :decl('var') ), QAST::Op.new( :op('wantdecont'), $qast[0] ) ), QAST::Op.new( # If it's a container... :op('if'), QAST::Op.new( :op('if'), QAST::Op.new( :op('isconcrete_nd'), QAST::Var.new( :name($result), :scope('local') ) ), QAST::Op.new( :op('iscont'), QAST::Var.new( :name($result), :scope('local') ) ) ), # It's a container; is it an rw one? QAST::Op.new( :op('if'), QAST::Op.new( :op('isrwcont'), QAST::Var.new( :name($result), :scope('local') ) ), # Yes; does it contain an Iterable? If so, rewrap it. If # not, strip it. QAST::Op.new( :op('if'), QAST::Op.new( :op('istype'), QAST::Var.new( :name($result), :scope('local') ), $Iterable ), QAST::Op.new( :op('p6bindattrinvres'), QAST::Op.new( :op('create'), $Scalar ), $Scalar, QAST::SVal.new( :value('$!value') ), QAST::Op.new( :op('decont'), QAST::Var.new( :name($result), :scope('local') ) ) ), QAST::Op.new( :op('decont'), QAST::Var.new( :name($result), :scope('local') ) ) ), # Not rw, so leave container in place. QAST::Var.new( :name($result), :scope('local') ) ), # Not a container, so just hand back value QAST::Var.new( :name($result), :scope('local') ) ) ) } }); } { my $is_moar; register_op_desugar('p6assign', -> $qast { unless nqp::isconcrete($is_moar) { $is_moar := nqp::getcomp('Raku').backend.name eq 'moar'; } if $is_moar { my $cont := QAST::Node.unique('assign_cont'); QAST::Stmts.new( QAST::Op.new( :op('bind'), QAST::Var.new( :name($cont), :scope('local'), :decl('var') ), $qast[0] ), QAST::Op.new( :op('dispatch'), QAST::SVal.new( :value('raku-assign') ), QAST::Var.new( :name($cont), :scope('local') ), QAST::Op.new( :op('decont'), $qast[1] ) ), QAST::Var.new( :name($cont), :scope('local') ) ) } else { QAST::Op.new( :op('assign'), $qast[0], $qast[1] ) } }); } { my $is_moar; register_op_desugar('p6attrinited', -> $qast { unless nqp::isconcrete($is_moar) { $is_moar := nqp::getcomp('Raku').backend.name eq 'moar'; } if $is_moar { QAST::Op.new( :op('dispatch'), :returns(int), QAST::SVal.new( :value('raku-is-attr-inited') ), $qast[0] ) } else { QAST::Op.new( :op('callmethod'), :name('check'), QAST::WVal.new( :value(nqp::gethllsym('Raku', 'UninitializedAttributeChecker')) ), $qast[0] ) } }); } sub can-use-p6forstmt($block) { my $count := $block.ann('past_block').ann('count'); if nqp::isconcrete($count) && $count == 1 { my $code := $block.ann('code_object'); my $block_type := $*W.find_single_symbol_in_setting('Block'); if nqp::istype($code, $block_type) { my $p := nqp::getattr($code, $block_type, '$!phasers'); !nqp::ishash($p) || !(nqp::existskey($p, 'FIRST') || nqp::existskey($p, 'LAST') || nqp::existskey($p, 'NEXT') ) } else { 1 } } else { 0 } } sub monkey_see_no_eval($/) { my $msne := $*LANG.pragma('MONKEY-SEE-NO-EVAL'); nqp::defined($msne) ?? $msne # prevails if defined, can be either 1 or 0 !! $*COMPILING_CORE_SETTING || try { $*W.find_single_symbol('&MONKEY-SEE-NO-EVAL')() }; } sub set_first_flag($block) { my $temp := QAST::Node.unique('first_tmp'); QAST::Stmts.new: :resultchild(0), QAST::Op.new( :op('bind'), QAST::Var.new( :decl('var'), :scope('local'), :name($temp) ), $block ), QAST::Op.new( :op('p6setfirstflag'), QAST::Op.new( :op('getattr'), QAST::Var.new( :scope('local'), :name($temp) ), QAST::WVal.new( :value($*W.find_symbol(['Code'])) ), QAST::SVal.new( :value('$!do') ) ) ) } role STDActions { method quibble($/) { make $.ast; } method trim_heredoc($/, $doc, $stop, $origast) { $origast.pop(); $origast.pop(); my str $ws := $stop.MATCH.Str; my int $actualchars := nqp::chars($ws); my int $indent := -$actualchars; my $world := $*W; my int $tabstop := $world.find_single_symbol_in_setting('$?TABSTOP'); my int $checkidx := -1; while ++$checkidx < $actualchars { if nqp::eqat($ws, "\t", $checkidx) { $indent := $indent - ($tabstop - 1); } } my $docast := $doc.MATCH.ast; if $docast.has_compile_time_value { my str $dedented := nqp::unbox_s($docast.compile_time_value.indent($indent)); $origast.push($world.add_string_constant($dedented)); } else { # we need to remove spaces from the beginnings of only textual lines, # so we have to track after each concatenation if the spaces at the # beginning of our chunk belong to a fresh line or come after an # interpolation or something my $in-fresh-line := 1; sub descend($node) { if nqp::istype($node, QAST::Want) { if +@($node) == 3 && $node[1] eq "Ss" { my $strval := $node[0].compile_time_value; if !$in-fresh-line { if $strval ~~ /\n/ { my $strbox := nqp::box_s(nqp::x(" ", -$indent) ~ nqp::unbox_s($strval), $world.find_single_symbol_in_setting("Str")); $strval := nqp::unbox_s($strbox.indent(nqp::box_i($indent, $world.find_single_symbol_in_setting("Int")))); $in-fresh-line := 1; return $world.add_string_constant($strval); } } else { $strval := nqp::unbox_s($strval.indent(nqp::box_i($indent, $world.find_single_symbol_in_setting("Int")))); return $world.add_string_constant($strval); } } } elsif nqp::istype($node, QAST::Op) && $node.op eq 'call' && $node.name eq '&infix:<~>' { my @results; # since we have the $in-fresh-line state, we need to traverse # and replace the child nodes in order for @($node) { nqp::push(@results, descend($node.shift)) } for @results { nqp::push($node, $_) } return $node; } $in-fresh-line := 0; return $node } $origast.push(descend($docast)) } CONTROL { if nqp::getextype($_) == nqp::const::CONTROL_WARN { $/.worry(nqp::getmessage($_)); nqp::resume($_); } nqp::rethrow($_); } $origast; } } class Perl6::Actions is HLL::Actions does STDActions { #================================================================ # AMBIENT AND POD-COMMON CODE HANDLERS #================================================================ our @MAX_PERL_VERSION; # Could add to this based on signatures. our %commatrap := nqp::hash( '&categorize', 1, '&classify', 1, '&first', 2, '&grep', 2, '&map', 1, '&reduce', 1, '&sort', 1, ); INIT { # If, e.g., we support Raku up to v6.1.2, set # @MAX_PERL_VERSION to [6, 1, 2]. @MAX_PERL_VERSION[0] := 6; } sub sink($past) { QAST::Want.new( $past, 'v', QAST::Op.new( :op('p6sink'), $past ) ) } my %sinkable := nqp::hash( 'call', 1, 'callmethod', 1, 'if', 1, 'while', 1, 'unless', 1, 'until', 1, 'repeat_until', 1, 'repeat_while', 1, 'handle', 1, 'hllize', 1, ); sub autosink($past) { nqp::istype($past, QAST::Op) && %sinkable{$past.op} && $*statement_level && !$past.nosink ?? sink($past) !! $past; } method ints_to_string($ints) { if nqp::islist($ints) { my $result := ''; for $ints { $result := $result ~ nqp::chr(nqp::unbox_i($_.ast)); } $result; } else { nqp::chr(nqp::unbox_i($ints.ast)); } } sub string_to_int($src, int $base, int $chars) { my $res := nqp::radix($base, ~$src, 0, 2); $src.panic("'$src' is not a valid number" ~ (nqp::iseq_i($base, 10) ?? '' !! " in base $base")) unless nqp::iseq_i(nqp::atpos($res, 2), $chars); nqp::box_i(nqp::atpos($res, 0), $*W.find_single_symbol_in_setting('Int')); } sub string_to_bigint($src, int $base, int $chars) { my $res := nqp::radix_I($base, ~$src, 0, 2, $*W.find_single_symbol_in_setting('Int')); $src.panic("'$src' is not a valid number" ~ (nqp::iseq_i($base, 10) ?? '' !! " in base $base")) unless nqp::iseq_i(nqp::unbox_i(nqp::atpos($res, 2)), $chars); nqp::atpos($res, 0); } sub xblock_immediate_with($xblock) { $xblock[1] := pblock_immediate_with($xblock[1]); $xblock; } sub xblock_immediate($xblock) { $xblock[1] := pblock_immediate($xblock[1]); $xblock; } sub pblock_immediate_with($pblock) { my $pb := block_immediate($pblock.ann('uninstall_if_immediately_used').shift); $pb.arity(1); # gotta force this, or Block node gets optimized away $pb; } sub pblock_immediate($pblock) { block_immediate($pblock.ann('uninstall_if_immediately_used').shift); } our sub block_immediate($block) { $block.blocktype('immediate'); $block; } method deflongname($/) { if $ { my $name := ~$; for $ { my $key := ~($_ || ''); if $_ -> $cf { if $cf -> $op_name { $name := $name ~ $*W.canonicalize_pair($key, $*W.colonpair_nibble_to_str( $/, $op_name // $op_name // $op_name)); } else { $name := $name ~ ':' ~ $key; } } else { $name := $name ~ ':' ~ $key; } } make $name; } else { make $*W.dissect_deflongname($/).name( :dba("$*IN_DECL declaration"), :decl, ); } } method deftermnow($/) { # 'my \foo' style declaration if $*SCOPE ne 'my' { $*W.throw($/, 'X::Comp::NYI', feature => "$*SCOPE scoped term definitions (only 'my' is supported at the moment)"); } my $name := $.ast; my $cur_lexpad := $*W.cur_lexpad; if $cur_lexpad.symbol($name) { $*W.throw($/, ['X', 'Redeclaration'], symbol => $name); } if $*OFTYPE { my $type := $*OFTYPE.ast; $cur_lexpad[0].push(QAST::Var.new( :$name, :scope('lexical'), :decl('var'), :returns($type) )); $cur_lexpad.symbol($name, :$type, :scope, :ro(1)); } else { $cur_lexpad[0].push(QAST::Var.new(:$name, :scope('lexical'), :decl('var'))); $cur_lexpad.symbol($name, :scope('lexical'), :ro(1)); } make $.ast; } method defterm($/) { my $name := ~$; if $ { for $ { my $key := ~($_ || ''); if $_ -> $cf { if $cf -> $op_name { $name := $name ~ $*W.canonicalize_pair($key, $*W.colonpair_nibble_to_str($/, $op_name)); } else { $name := $name ~ ':' ~ $key; } } else { $name := $name ~ ':' ~ $key; } } } make $name; } # Turn $code into "for lines() { $code }" sub wrap_option_n_code($/, $code) { my $fornode := QAST::Op.new( :op, :node($/), QAST::Op.new(:op, :name<&lines>), block_closure(make_topic_block_ref($/, $code, copy => 1)), ); if can-use-p6forstmt($fornode[1]) { my $world := $*W; $fornode.op('p6forstmt'); $fornode.annotate('IterationEnd', $world.find_single_symbol_in_setting('IterationEnd')); $fornode.annotate('Nil', $world.find_single_symbol_in_setting('Nil')); $fornode.annotate('Code', $world.find_single_symbol_in_setting('Code')); } $fornode } # Turn $code into "for lines() { $code; say $_ }" # &wrap_option_n_code already does the C loop, so we just add the # C call here sub wrap_option_p_code($/, $code) { wrap_option_n_code($/, QAST::Stmts.new( $code, QAST::Op.new(:name<&say>, :op, QAST::Var.new(:name<$_>, :scope) ) ) ) } method comp_unit($/) { my $world := $*W; # Finish up code object for the mainline. if $*DECLARAND -> $declarand { $world.attach_signature($declarand, $world.create_signature( nqp::hash('parameter_objects', []))); $world.finish_code_object($declarand, $*UNIT); $world.add_phasers_handling_code($declarand, $*UNIT); } # Checks. $world.assert_stubs_defined($/); $world.sort_protos(); # Get the block for the unit mainline code. my $unit := $*UNIT; my $mainline := QAST::Stmts.new( $*POD_PAST, statementlist_with_handlers($/) ); # Errors/warnings in sinking pass should ignore highwater mark. $/.'!clear_highwater'(); unless $*NEED_RESULT { # Evaluate last statement in sink context, by pushing another # statement after it, unless we need the result. unwantall($mainline, 'comp_unit'); $mainline.push(QAST::WVal.new( :value($world.find_single_symbol_in_setting('Nil')) )); } fatalize($mainline) if $*FATAL; # Emit any worries. Note that unwanting $mainline can produce worries. if @*WORRIES { stderr().print($world.group_exception().gist()); } unless $*COMPILING_CORE_SETTING || $*WANT_RAKUAST || $world.have_outer { my $Exception := $*W.find_symbol_in_setting(['X', 'Experimental']); $world.add_object_if_no_sc($Exception); $*UNIT_OUTER[0].push( QAST::Op.new( :op, QAST::Var.new( :name, :scope, :decl), QAST::Op.new( :op, :name, QAST::Var.new( :name, :scope ), QAST::Op.new( :op, :name, QAST::WVal.new(:value($Exception)), QAST::SVal.new(:value, :named), QAST::SVal.new(:value, :named ))))); } if %*COMPILING<%?OPTIONS>

{ # also covers the -np case, like Perl $mainline[1] := QAST::Stmt.new(wrap_option_p_code($/, $mainline[1])); } elsif %*COMPILING<%?OPTIONS> { $mainline[1] := QAST::Stmt.new(wrap_option_n_code($/, $mainline[1])); } # We only install GLOBAL unless it is already there. my $global_install := QAST::Op.new( :op, QAST::Op.new( :op, QAST::SVal.new(:value('GLOBAL')) ), QAST::Op.new( :op('bindcurhllsym'), QAST::SVal.new( :value('GLOBAL') ), QAST::WVal.new( :value($*GLOBALish) ) ) ); $world.add_fixup_task(:deserialize_ast($global_install), :fixup_ast($global_install)); # Get the block for the entire compilation unit. my $outer := $*UNIT_OUTER; $outer.node($/); $*UNIT_OUTER.unshift(QAST::Var.new( :name('__args__'), :scope('local'), :decl('param'), :slurpy(1) )); $unit.name(''); $outer.name(''); # If the unit defines &MAIN, and this is in the mainline, # add a call to &RUN-MAIN if !$world.is_precompilation_mode && !$*INSIDE-EVAL && +(@*MODULES // []) == 0 && $unit.symbol('&MAIN') -> $main { $mainline := QAST::Op.new( :op('call'), :name('&RUN-MAIN'), QAST::WVal.new(:value($main)), $mainline # run the mainline and get its result ); unless nqp::getcomp('Raku').language_revision < 2 { $mainline.push( QAST::WVal.new( # $*IN as $*ARGSFILES value => $world.find_symbol(['Bool','True'], :setting-only), :named('in-as-argsfiles') ) ); } } # If our caller wants to know the mainline ctx, provide it here. # (CTXSAVE is inherited from HLL::Actions.) Don't do this when # there was an explicit {YOU_ARE_HERE}. unless $*HAS_YOU_ARE_HERE { $unit.push( self.CTXSAVE() ); } # Add the mainline code to the unit. $unit.push($mainline); # Executing the compilation unit causes the mainline to be executed. $outer.push(QAST::Op.new( :op, $unit )); # Do not want closure semantics on this outermost scope. $unit.blocktype('declaration_static'); # Wrap everything in a QAST::CompUnit. make QAST::CompUnit.new( :hll('Raku'), # Serialization related bits. :sc($world.sc()), :code_ref_blocks($world.code_ref_blocks()), :compilation_mode($world.is_precompilation_mode()), :pre_deserialize($world.load_dependency_tasks()), :post_deserialize($world.fixup_tasks()), :is_nested($world.is_nested()), :repo_conflict_resolver(QAST::Op.new( :op('callmethod'), :name('resolve_repossession_conflicts'), QAST::WVal.new( :value($world.find_symbol(['CompUnit', 'RepositoryRegistry'], :setting-only)) ) )), # If this unit is loaded as a module, we want it to automatically # execute the mainline code above after all other initializations # have occurred. :load(QAST::Op.new( :op('call'), QAST::BVal.new( :value($outer) ), )), # Finally, the outer block, which in turn contains all of the # other program elements. $outer ).annotate_self( # Pass some extra bits along to the optimizer. 'UNIT', $unit ).annotate_self( 'CAN_LOWER_TOPIC', $*CAN_LOWER_TOPIC ).annotate_self('GLOBALish', $*GLOBALish).annotate_self('W', $world) } method install_doc_phaser($/) { # Add a default DOC INIT phaser my $doc := %*COMPILING<%?OPTIONS>; if $doc { my $world := $*W; my $block := $world.push_lexpad($/); my $renderer := "Pod::To::$doc"; my $module := $world.load_module($/, $renderer, {}, $block); my $pod2text := QAST::Op.new( :op, :name, :node($/), self.make_indirect_lookup([$renderer]), QAST::Var.new(:name<$=pod>, :scope('lexical'), :node($/)) ); $block.push( QAST::Op.new( :op('if'), $pod2text, QAST::Op.new( :op, :node($/), :name('&say'), $pod2text, ), ) ); # TODO: We should print out $?USAGE too, # once it's known at compile time $block.push( QAST::Op.new( :op, :node($/), :name('&exit'), ) ); $world.pop_lexpad(); $world.add_phaser( $/, 'INIT', $world.create_code_obj_and_add_child($block, 'Block'), $block ); } } method unitstart($/) { # Use SET_BLOCK_OUTER_CTX (inherited from HLL::Actions) # to set dynamic outer lexical context and namespace details # for the compilation unit. self.SET_BLOCK_OUTER_CTX($*UNIT_OUTER); } method lang-version($/) { self.SET_BLOCK_OUTER_CTX($*UNIT_OUTER); } method statementlist($/) { my $past := QAST::Stmts.new( :node($/) ); if $ { my int $i := 0; my int $e := nqp::elems($) - 1; while $i <= $e { my $ast := $[$i].ast; if $ast { if $ast.ann('statement_level') && $*statement_level { $ast.ann('statement_level')(); } if $ast.ann('sink_ast') { $ast := QAST::Want.new($ast, 'v', $ast.ann('sink_ast')); $ast := UNWANTED($ast, 'statementlist/sink_ast') if $i < $e; } elsif $ast.ann('bare_block') { if $i < $e { $ast := UNWANTED(autosink($ast.ann('bare_block')), "statementlist/bare_block"); } elsif $*ESCAPEBLOCK { $ast := WANTED($ast.ann('bare_block'),'statementlist/escape'); } else { $ast := autosink($ast.ann('bare_block')); } } else { if nqp::istype($ast,QAST::Op) && ( (my $op := $ast.op) eq 'while' || $op eq 'until' || $op eq 'repeat_while' || $op eq 'repeat_until') { $ast := UNWANTED($ast,'statementlist/loop'); # statement level loops never want return value } elsif $i == $e && $*ESCAPEBLOCK { $ast := QAST::Stmt.new(autosink(WANTED($ast,'statementlist/else')), :returns($ast.returns)); } else { $ast := QAST::Stmt.new(autosink($ast), :returns($ast.returns)); } } $ast.node($[$i]); $past.push( $ast ); } ++$i; } } if nqp::elems($past.list) < 1 { $past.push(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) )); } else { my $pl := $past[nqp::elems($past) - 1]; if $pl.sunk { $past.push(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) )); } else { $pl.final(1); $past.returns($pl.returns); } } make $past; } # Produces a LoL from a semicolon list method semilist($/) { if $ -> $statements { my $Nil := $*W.find_single_symbol_in_setting('Nil'); my $past := QAST::Stmts.new( :node($/) ); if nqp::elems($statements) > 1 { my $l := QAST::Op.new( :name('&infix:<,>'), :op('call') ); for $statements { my $sast := $_.ast || QAST::WVal.new( :value($Nil) ); $l.push(wanted($sast, 'semilist')); } $past.push($l); $past.annotate('multislice', 1); } else { my $ast := $statements[0].ast; # an op and 6e or higher? if nqp::istype($ast,QAST::Op) && nqp::getcomp('Raku').language_revision >= 3 { sub is-pipe-pipe($ast) { nqp::istype($ast,QAST::Op) && $ast.name eq '&prefix:<|>' && nqp::istype($ast[0],QAST::Op) && $ast[0].name eq '&prefix:<|>' } if is-pipe-pipe($ast) { $ast := $ast[0][0]; # cut out the || ops $past.annotate('multislice', 1); } elsif $ast.name eq '&infix:<,>' && is-pipe-pipe($ast[0]) { $ast[0] := $ast[0][0][0]; # cut out the || ops $past.annotate('multislice', 1); } } $past.push($ast || QAST::WVal.new( :value($Nil) )); } make $past; } else { make QAST::Op.new( :op('call'), :name('&infix:<,>') ); } } method sequence($/) { my $past := QAST::Stmts.new( :node($/) ); if $ { for $ { $past.push($_.ast) if $_.ast; } } unless +@($past) { $past.push( QAST::Op.new( :op('call'), :name('&infix:<,>') ) ); } make $past; } method statement($/) { my $past; my $world := $*W; if $ { my $mc := $; my $ml := $; $past := $.ast; if $mc { if ~$mc eq 'with' { make thunkity_thunk($/,'.b',QAST::Op.new( :op('call'), :name('&infix:')),[$mc,$]); return; } elsif ~$mc eq 'without' { make thunkity_thunk($/,'.b',QAST::Op.new( :op('call'), :name('&infix:')),[$mc,$]); return; } my $mc_ast := $mc.ast; if $past.ann('bare_block') { my $cond_block := $past.ann('past_block'); remove_block($world.cur_lexpad(), $cond_block); $cond_block.blocktype('immediate'); unless $cond_block.ann('placeholder_sig') { $cond_block.arity(0); $cond_block.annotate('count', 0); } $past := $cond_block; } $mc_ast.push($past); $mc_ast.push(QAST::WVal.new( :value($world.find_single_symbol_in_setting('Empty')) )); $past := $mc_ast; } if $ml { $past.okifnil(1); $past[0].okifnil(1) if +@($past); my $cond := $ml.ast; if ~$ml eq 'given' { unless $past.ann('bare_block') { $past := make_topic_block_ref($/, $past, migrate_stmt_id => $*STATEMENT_ID); } $past := QAST::Op.new( :op('call'), block_closure($past), $cond ); } elsif ~$ml eq 'for' { unless $past.ann('past_block') { $past := make_topic_block_ref($/, $past, migrate_stmt_id => $*STATEMENT_ID); } my $fornode := QAST::Op.new( :op, :node($/), $cond, block_closure($past), ); $past := QAST::Want.new( $fornode, 'v', QAST::Op.new(:op, $fornode), ); $past[2].sunk(1); my $sinkee := $past[0]; $past.annotate('statement_level', -> { UNWANTED($sinkee, 'force for mod'); $fornode.op('p6forstmt') if can-use-p6forstmt($fornode[1]); $fornode.annotate('IterationEnd', $world.find_single_symbol_in_setting('IterationEnd')); $fornode.annotate('Nil', $world.find_single_symbol_in_setting('Nil')); $fornode.annotate('Code', $world.find_single_symbol_in_setting('Code')); }); } else { $past := QAST::Op.new($cond, $past, :op(~$ml), :node($/) ); } } } elsif $ { $past := $.ast; } elsif $ { $past := $.ast; } else { $past := 0; } if $past { my $id := $*STATEMENT_ID; $past.annotate('statement_id', $id); # only trace when running in source if $/.pragma('trace') && !$world.is_precompilation_mode { my $code := ~$/; # don't bother putting ops for activating it if $code eq 'use trace' { $past := 0; } # need to generate code else { my $line := $world.current_line($/); my $file := $world.current_file; $code := subst($code, /\s+$/, ''); # chomp! $past := QAST::Stmts.new(:node($/), QAST::Op.new( :op, QAST::Op.new(:op), QAST::Op.new( :op('encode'), QAST::SVal.new(:value("$id ($file line $line)\n$code\n")), QAST::SVal.new(:value('utf8')), QAST::Op.new( :op('callmethod'), :name('new'), QAST::WVal.new( :value($world.find_single_symbol_in_setting('Blob')) ) ) ) ), $past ); } } } make $past; } method xblock($/) { make QAST::Op.new( WANTED($.ast, 'xblock'), $.ast, :op('if'), :node($/) ); } method pblock($/) { if $ { make $.ast; } else { # Locate or build a set of parameters. my $world := $*W; my %sig_info; my @params; my $block := $.ast; if $block.ann('placeholder_sig') && $ { $world.throw($/, ['X', 'Signature', 'Placeholder'], precursor => '1', placeholder => $block.ann('placeholder_sig')[0], ); } elsif $block.ann('placeholder_sig') { @params := $block.ann('placeholder_sig'); %sig_info := @params; if $*IMPLICIT { $block[0].push(QAST::Op.new( :op('bind'), WANTED(QAST::Var.new( :name('$_'), :scope('lexical') ),'pblock/place'), WANTED(QAST::Op.new( :op('getlexouter'), QAST::SVal.new( :value('$_') ) ),'pblock/place') )); } } elsif $ { %sig_info := %*SIG_INFO; @params := %sig_info; if $*IMPLICIT { my int $declares_topic := 0; for @params { if $_ eq '$_' { $declares_topic := 1; } } unless $declares_topic { $block[0].push(QAST::Op.new( :op('bind'), WANTED(QAST::Var.new( :name('$_'), :scope('lexical') ),'pblock/sig'), WANTED(QAST::Op.new( :op('getlexouter'), QAST::SVal.new( :value('$_') ) ),'pblock/sig') )); } } $block[1] := wrap_return_type_check($block[1], $*DECLARAND); } else { if $*IMPLICIT { my $optional := $*IMPLICIT == 1; @params.push(hash( :variable_name('$_'), :$optional, :type($world.find_single_symbol_in_setting('Mu')), :default_from_outer($optional), :is_raw(1), )); } elsif !$block.symbol('$_') { $block[0].push(QAST::Op.new( :op('bind'), WANTED(QAST::Var.new( :name('$_'), :scope('lexical'), :decl('var') ),'pblock/sawone'), WANTED(QAST::Op.new( :op('getlexouter'), QAST::SVal.new( :value('$_') ) ),'pblock/sawone') )); $block.symbol('$_', :scope('lexical'), :type($world.find_single_symbol_in_setting('Mu'))); } %sig_info := @params; } # Create signature object if we didn't already, and set up binding. my $signature := $*SIG_OBJ // $world.create_signature_and_params( $, %sig_info, $block, 'Mu'); add_signature_binding_code($block, $signature, @params); # We'll install PAST in current block so it gets capture_lex'd. # Then evaluate to a reference to the block (non-closure - higher # up stuff does that if it wants to). $world.push_inner_block(my $uninst := QAST::Stmts.new($block)); my $declarand := $*DECLARAND; Perl6::Pod::document($/, $declarand, $*POD_BLOCK, :leading); $world.attach_signature($declarand, $signature); $world.finish_code_object($declarand, $block); $world.add_phasers_handling_code($declarand, $block); make reference_to_code_object($declarand, $block).annotate_self( 'uninstall_if_immediately_used', $uninst ) } } method block($/) { my $world := $*W; my $block := $.ast; if $block.ann('placeholder_sig') { my $name := $block.ann('placeholder_sig')[0]; unless $name eq '%_' || $name eq '@_' { $name := nqp::concat(nqp::substr($name, 0, 1), nqp::concat('^', nqp::substr($name, 1))); } $world.throw( $/, ['X', 'Placeholder', 'Block'], placeholder => $name, ); } $world.push_inner_block(my $uninst := QAST::Stmts.new($block)); my $declarand := $*DECLARAND; $world.attach_signature($declarand, $world.create_signature(nqp::hash('parameter_objects', []))); $world.finish_code_object($declarand, $block); $world.add_phasers_handling_code($declarand, $block); my $ref := reference_to_code_object($declarand, $block); $ref.annotate('uninstall_if_immediately_used', $uninst); make $ref; } method blockoid($/) { if $ { my $past := statementlist_with_handlers($/); my $BLOCK := $*CURPAD; $BLOCK.blocktype('declaration_static'); $BLOCK.push($past); $BLOCK.node($/); if %*HANDLERS -> %handlers { $BLOCK.annotate('handlers', %handlers); } fatalize($past) if $*FATAL; make $BLOCK; } else { if $*HAS_YOU_ARE_HERE { $/.panic('{YOU_ARE_HERE} may only appear once in a setting'); } $*HAS_YOU_ARE_HERE := 1; make $.ast; } } sub statementlist_with_handlers($/) { my $past := $.ast; my $ret := %*SIG_INFO; $past.push(QAST::WVal.new(:value($ret))) if nqp::isconcrete($ret) || $ret.HOW.name($ret) eq 'Nil'; if %*HANDLERS { $past := QAST::Op.new( :op('handle'), $past ); my %handlers := %*HANDLERS; for sorted_keys(%handlers) { $past.push($_); $past.push(%handlers{$_}); } } $past } # Under "use fatal", re-write all calls to fatalize their return value # unless we can see they are in a boolean context. my %boolify_first_child_ops := nqp::hash( 'if', 1, 'unless', 1, 'defor', 1, 'hllbool', 1, 'while', 1, 'until', 1, 'repeat_while', 1, 'repeat_until', 1, ); my %boolify_first_child_calls := nqp::hash( '&prefix:', 1, '&prefix:', 1, '&prefix:', 1, '&prefix:', 1, '&defined', 1 ); sub fatalize($ast, $bool-context = 0) { if nqp::istype($ast, QAST::Op) { my str $op := $ast.op; if $op eq 'p6fatalize' { # We've been here before (tree with shared bits, presumably). } elsif nqp::existskey(%boolify_first_child_ops, $op) || $op eq 'call' && nqp::existskey(%boolify_first_child_calls, $ast.name) { my int $first := 1; for @($ast) { if $first { fatalize($_, 1); $first := 0; } else { fatalize($_); } } } elsif $op eq 'hllize' { fatalize($_, $bool-context) for @($ast); } else { fatalize($_) for @($ast); if !$bool-context && ($op eq 'call' || $op eq 'callmethod') { if $ast.name eq '&fail' { $ast.name('&die'); } else { my $new-node := QAST::Op.new( :node($ast.node), :$op, :name($ast.name), :returns($ast.returns) ); $new-node.push($ast.shift) while @($ast); $ast.op('p6fatalize'); $ast.push($new-node); $ast.push(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Failure')) )); } } } } elsif nqp::istype($ast, QAST::Stmt) || nqp::istype($ast, QAST::Stmts) || nqp::istype($ast, QAST::Want) { fatalize($_) for @($ast); } } method you_are_here($/) { make self.CTXSAVE(); } method newpad($/) { $*W.cur_lexpad().annotate_self('IN_DECL', $*IN_DECL); } method finishpad($/) { # Generate the $_, $/, and $! lexicals for routines if they aren't # already declared. For blocks, $_ will come from the outer if it # isn't already declared. my $world := $*W; my $BLOCK := $world.cur_lexpad(); my $type := $BLOCK.ann('IN_DECL'); if $type eq 'mainline' && %*COMPILING<%?OPTIONS> eq 'NULL.c' { # Don't do anything in the case where we are in the mainline of # the setting; we don't have any symbols (Scalar, etc.) yet. return 1; } my $is_routine := $type eq 'sub' || $type eq 'method' || $type eq 'submethod' || $type eq 'mainline'; if $is_routine { # Generate the lexical variable except if... # (1) the block already has one, or # (2) the variable is '$_' and $*IMPLICIT is set # (this case gets handled by getsig) unless $BLOCK.symbol('$_') || $*IMPLICIT { $world.install_lexical_magical($BLOCK, '$_'); } for <$/ $! $¢> { unless $BLOCK.symbol($_) { $world.install_lexical_magical($BLOCK, $_); } } } else { unless $BLOCK.symbol('$_') { $BLOCK[0].push(QAST::Var.new( :name('$_'), :scope('lexical'), :decl('var') )); unless $*IMPLICIT { $BLOCK[0].push(QAST::Op.new( :op('bind'), WANTED(QAST::Var.new( :name('$_'), :scope('lexical') ),'finishpad'), WANTED(QAST::Op.new( :op('getlexouter'), QAST::SVal.new( :value('$_') ) ),'finishpad') )); } $BLOCK.symbol('$_', :scope('lexical'), :type($world.find_single_symbol_in_setting('Mu'))); } } } ## Statement control method statement_control:sym($/) { my $count := nqp::elems($) - 1; my $past; (my $empty := QAST::WVal.new: :value($*W.find_symbol: ['Empty']) ).annotate: 'ok_to_null_if_sunk', 1; if ~$[$count] ~~ /with/ { $past := xblock_immediate_with( $[$count].ast ); $past.op('with'); $past.push: $ ?? pblock_immediate_with($.ast) !! $empty; } else { $past := xblock_immediate( $[$count].ast ); $past.op('if'); $past.push: $ ?? pblock_immediate($.ast) !! $empty; } # build if/then/elsif structure while $count > 0 { $count--; my $else := $past; if ~$[$count] ~~ /with/ { $past := xblock_immediate_with( $[$count].ast ); $past.op('with'); } else { $past := xblock_immediate( $[$count].ast ); $past.op('if'); } $past.push($else); } make $past; } method statement_control:sym($/) { my $past := xblock_immediate( $.ast ); $past.push(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Empty')) )); $past.op('unless'); make $past; } method statement_control:sym($/) { my $past := xblock_immediate_with( $.ast ); $past.push(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Empty')) )); $past.op('without'); make $past; } method statement_control:sym($/) { my $past := $.ast; $past.op(~$); make tweak_loop($past); } method statement_control:sym($/) { my $op := 'repeat_' ~ ~$; my $past; if $ { $past := $.ast; $past.op($op); } else { $past := QAST::Op.new( $.ast, $.ast, :op($op), :node($/) ); } make tweak_loop($past); } method statement_control:sym($/) { my $xblock := $.ast; my $fornode := QAST::Op.new( :op, :node($/), $xblock[0], block_closure($xblock[1]), ); my $past := QAST::Want.new( $fornode, 'v', QAST::Op.new(:op, $fornode), ); if $*LABEL { my $label := QAST::WVal.new( :value($*W.find_single_symbol($*LABEL)), :named('label') ); $past[0].push($label); } $past[2].sunk(1); my $sinkee := $past[0]; $past.annotate('statement_level', -> { UNWANTED($sinkee,'force for'); if can-use-p6forstmt($fornode[1]) { $fornode.op('p6forstmt'); $fornode.annotate('IterationEnd', $*W.find_single_symbol_in_setting('IterationEnd')); $fornode.annotate('Nil', $*W.find_single_symbol_in_setting('Nil')); $fornode.annotate('Code', $*W.find_single_symbol_in_setting('Code')); } }); make $past; } method statement_control:sym($/) { my $xblock := $.ast; make QAST::Op.new( :op, :name<&WHENEVER>, :node($/), $xblock[0], block_closure($xblock[1]) ); } method statement_control:sym($/) { my $cond := $ ?? WANTED($.ast, 'statement_control/e2') !! QAST::IVal.new( :value(1) ); my $loop := QAST::Op.new( $cond, :op('while'), :node($/) ); $loop.push($.ast); if $ { $loop.push(UNWANTED($.ast, 'statement_control/e3')); } $loop := tweak_loop($loop); if $ { $loop := QAST::Stmts.new( UNWANTED($.ast, 'statement_control/e1'), $loop, :node($/) ); } my $sinkee := $loop[1]; $loop.annotate('statement_level', -> { UNWANTED($sinkee,'force loop'); if $ { $loop.push(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) )); } }); make $loop; } sub tweak_loop($loop) { my $world := $*W; if $*LABEL { $loop.push(QAST::WVal.new( :value($world.find_single_symbol($*LABEL)), :named('label') )); } # Handle any loopy phasers. my $code := $loop[1].ann('code_object'); my $Block := $world.find_single_symbol_in_setting('Block'); my $phasers := nqp::getattr($code, $Block, '$!phasers'); if nqp::ishash($phasers) { my $node := $loop.node; if nqp::existskey($phasers, 'NEXT') { my $phascode := $world.run_phasers_code($code, $loop[1], $Block, 'NEXT'); if +@($loop) == 2 { $loop.push($phascode); } else { $loop[2] := QAST::Stmts.new: :$node, $phascode, $loop[2]; } } if nqp::existskey($phasers, 'FIRST') { my $tmp := QAST::Node.unique('LOOP_BLOCK'); my $var := QAST::Var.new: :$node, :name($tmp), :scope; $loop := QAST::Stmts.new(:$node, QAST::Op.new(:$node, :op, $var.decl_as('var'), set_first_flag($loop[1])), $loop); $loop[1][1] := QAST::Op.new(:$node, :op, $var ).annotate_self: 'loop-already-block-first-phaser', $loop; } else { $loop[1] := pblock_immediate($loop[1]); } if nqp::existskey($phasers, 'LAST') { $loop := QAST::Stmts.new(:$node, :resultchild(0), $loop, $world.run_phasers_code: $code, $loop[1], $Block, 'LAST'); } } else { # no phasers or a lone LEAVE phaser $loop[1] := pblock_immediate($loop[1]); } $loop } method statement_control:sym($/) { my $past := QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) ); make $past; } method statement_control:sym($/) { # NB: Grammar already passed arglist directly to World, but this seems soon enough to want it. if $ && $ { WANTED($.ast, 'import'); } my $past := QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) ); make $past; } method statement_control:sym($/) { my $past := QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) ); if $ { $past := $.ast; } elsif $ { WANTED($.ast, 'use'); } elsif $ { # TODO: replace this by code that doesn't always die with # a useless error message # my $i := -1; # for $ { # ++$i; # if $_ ne '*' && $_ < @MAX_PERL_VERSION[$i] { # last; # } elsif $_ > @MAX_PERL_VERSION[$i] { # my $mpv := nqp::join('.', @MAX_PERL_VERSION); # $/.panic("Perl $ required--this is only v$mpv") # } # } } make $past; } method statement_control:sym($/) { my $past := QAST::Stmts.new(:node($/)); my $compunit_past; my $target_package; my $has_file; my $longname; my $*SCOPE := 'my'; my $world := $*W; if $ { for $ -> $colonpair { if ~$colonpair eq 'file' { $has_file := $colonpair.ast[2]; last; } } $longname := $world.dissect_longname($); $target_package := $longname.name_past; } if $ && nqp::defined($has_file) == 0 { my $short_name := nqp::clone($target_package); $short_name.named('short-name'); my $spec := QAST::Op.new( :op('callmethod'), :name('new'), $world.symbol_lookup(['CompUnit', 'DependencySpecification'], $/), $short_name, ); $compunit_past := QAST::Op.new( :op('callmethod'), :name('need'), QAST::Op.new( :op('callmethod'), :name('head'), $world.symbol_lookup(['CompUnit', 'RepositoryRegistry'], $/), ), $spec, ); } else { my $file_past := WANTED(($has_file ?? $has_file !! $.ast), 'require/name'); $compunit_past := QAST::Op.new( :op('callmethod'), :name('load'), QAST::Op.new( :op('callmethod'), :name('head'), $world.symbol_lookup(['CompUnit', 'RepositoryRegistry'], $/), ), QAST::Op.new( :op('callmethod'), :name('IO'), $file_past, ), ); } my $lexpad := $world.cur_lexpad(); my $block := $lexpad.ann('code_object'); $block := $world.blocks[+$world.blocks - 2] if $block.HOW.name($block) eq 'Code'; my $req-sym-name := '%?REQUIRE-SYMBOLS'; if !$lexpad.symbol($req-sym-name) { my $Stash := $world.find_single_symbol_in_setting('Stash'); my $st := $Stash.new; $world.add_object_if_no_sc($st); $lexpad[0].push( QAST::Op.new( :op, QAST::Var.new(:name($req-sym-name), :scope, :decl, :value($st)), QAST::Op.new(:op, :name, QAST::WVal.new(:value($Stash))))); $lexpad.symbol($req-sym-name, :scope, :value($st)); $world.mark_lexical_used_implicitly($lexpad, $req-sym-name); } my $require_past := WANTED(QAST::Op.new(:node($/), :op, :name<&REQUIRE_IMPORT>, $compunit_past, ),'require'); # An list of the components of the pre-existing outer symbols name (if any) my $existing_path := $world.symbol_lookup(['Any'], $/); # The top level package object of the pre-existing outer package (if any) my $top-existing := $world.symbol_lookup(['Any'], $/); # The name of the lexical stub we insert (if any) my $lexical_stub; if $target_package && !$longname.contains_indirect_lookup() { my $current; my @components := nqp::clone($longname.components); my $top := @components.shift; $existing_path := QAST::Op.new(:op, :name('&infix:<,>')); my $existing := try $world.find_single_symbol($top); if $existing =:= NQPMu { my $stub := $world.pkg_create_mo($/, $/.how('package'), :name($top)); $world.pkg_compose($/, $stub); $world.install_lexical_symbol($lexpad,$top,$stub); $current := nqp::who($stub); $lexical_stub := QAST::SVal.new(:value($top)); } else { $top-existing := QAST::WVal.new(:value($existing)); $current := nqp::who($existing); $existing_path.push: QAST::SVal.new(:value($top)); } for @components -> $component { if nqp::existskey($current,$component) { $current := nqp::who($current{$component}); $existing_path.push: QAST::SVal.new(:value($component)); } else { $lexical_stub := QAST::SVal.new(:value($component)) unless $lexical_stub; my $stub := $world.pkg_create_mo($/, $/.how('package'), :name($component)); $world.pkg_compose($/, $stub); $current{$component} := $stub; $current := nqp::who($stub); } } } $require_past.push($existing_path); $require_past.push($top-existing); $require_past.push($lexical_stub // $world.symbol_lookup(['Any'], $/)); if $ { my $p6_argiter := $world.compile_time_evaluate($/, WANTED($.ast,'require')).eager.iterator; my $IterationEnd := $world.find_single_symbol_in_setting('IterationEnd'); while !((my $arg := $p6_argiter.pull-one) =:= $IterationEnd) { my str $symbol := nqp::unbox_s($arg.Str()); $world.throw($/, ['X', 'Redeclaration'], :$symbol) if $lexpad.symbol($symbol); $world.install_lexical_symbol( $lexpad, $symbol, $world.find_symbol(['Metamodel', 'GenericHOW']).new_type(:name($symbol)) ); $require_past.push($world.add_string_constant($symbol)); } } $past.push($require_past); my $unwanted := $past.shallow_clone(); $past.push($ ?? self.make_indirect_lookup($longname.components()) !! $.ast); make QAST::Want.new( $past, 'v', $unwanted ); } method statement_control:sym($/) { my $past := $.ast; $past.push($past.shift); # swap [0] and [1] elements $past[0] := block_closure($past[0]); $past.op('call'); make $past; } method statement_control:sym($/) { # Get hold of the smartmatch expression and the block. my $xblock := $.ast; my $sm_exp := $xblock.shift; my $pblock := $xblock.shift; check_smartmatch($,$sm_exp); # Use the smartmatch result as the condition for running the block, # and ensure continue/succeed handlers are in place and that a # succeed happens after the block. $pblock := pblock_immediate($pblock); make QAST::Op.new( :op('if'), :node( $/ ), QAST::Op.new( :op('callmethod'), :name('Bool'), QAST::Op.new( :op('callmethod'), :name('ACCEPTS'), $sm_exp, WANTED(QAST::Var.new( :name('$_'), :scope('lexical') ),'when'))), when_handler_helper($pblock) ).annotate_self('when_block', 1); } method statement_control:sym($/) { # We always execute this, so just need the block, however we also # want to make sure we succeed after running it. make when_handler_helper($.ast); } method statement_control:sym($/) { if nqp::existskey(%*HANDLERS, 'CATCH') { $*W.throw($/, ['X', 'Phaser', 'Multiple'], block => 'CATCH'); } my $block := $.ast; set_block_handler($/, $block, 'CATCH'); make QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) ); } method statement_control:sym($/) { if nqp::existskey(%*HANDLERS, 'CONTROL') { $*W.throw($/, ['X', 'Phaser', 'Multiple'], block => 'CONTROL'); } my $block := $.ast; set_block_handler($/, $block, 'CONTROL'); make QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) ); } method statement_control:sym($/) { my $block := $.ast; # Take exception as parameter and bind into $_. my $past := $block.ann('past_block'); $past[0].push(QAST::Op.new( :op('bind'), WANTED(QAST::Var.new( :name('$_'), :scope('lexical') ),'QUIT'), WANTED(QAST::Var.new( :name('__param'), :scope('local'), :decl('param') ),'QUIT') )); # If the handler has a succeed handler, then make sure we sink # the exception it will produce. if $past.ann('handlers') && nqp::existskey($past.ann('handlers'), 'SUCCEED') { my $suc := $past.ann('handlers'); $suc[0] := QAST::Stmts.new( sink(QAST::Op.new( :op('getpayload'), QAST::Op.new( :op('exception') ) )), QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) ) ); } # If we don't handle the exception by succeeding, we'll return it. if $past.ann('handlers') { $past[1][0].push(WANTED(QAST::Var.new( :name('$_'), :scope('lexical') ),'QUIT/handlers')); } else { $past[1].push(WANTED(QAST::Var.new( :name('$_'), :scope('lexical') ),'QUIT/handlers')); } # Add as a QUIT phaser, which evaluates to Nil. make $*W.add_phaser($/, 'QUIT', $block.ann('code_object')); } method statement_prefix:sym($/) { my $qast_block := $.ast.ann('past_block'); begin_time_lexical_fixup($qast_block); $qast_block.annotate('BEGINISH', 1); make $*W.add_phaser($/, 'BEGIN', wanted($.ast,'BEGIN').ann('code_object')); } method statement_prefix:sym($/) { my $qast_block := $.ast.ann('past_block'); begin_time_lexical_fixup($qast_block); $qast_block.annotate('BEGINISH', 1); make $*W.add_phaser($/, 'CHECK', wanted($.ast,'CHECK').ann('code_object')); } method statement_prefix:sym($/) { make $*W.add_phaser($/, 'INIT', wanted($.ast,'INIT').ann('code_object'), ($.ast).ann('past_block')); } method statement_prefix:sym($/) { make $*W.add_phaser($/, 'ENTER', wanted($.ast,'ENTER').ann('code_object')); } method statement_prefix:sym($/) { make $*W.add_phaser($/, 'FIRST', wanted($.ast,'FIRST').ann('code_object')); } method statement_prefix:sym($/) { make $*W.add_phaser($/, 'END', unwanted($.ast,'END').ann('code_object')); } method statement_prefix:sym($/) { make $*W.add_phaser($/, 'LEAVE', unwanted($.ast,'LEAVE').ann('code_object')); } method statement_prefix:sym($/) { make $*W.add_phaser($/, 'KEEP', unwanted($.ast,'KEEP').ann('code_object')); } method statement_prefix:sym($/) { make $*W.add_phaser($/, 'UNDO', unwanted($.ast,'UNDO').ann('code_object')); } method statement_prefix:sym($/) { make $*W.add_phaser($/, 'NEXT', unwanted($.ast,'NEXT').ann('code_object')); } method statement_prefix:sym($/) { make $*W.add_phaser($/, 'LAST', unwanted($.ast,'LAST').ann('code_object')); } method statement_prefix:sym

($/)   { make $*W.add_phaser($/, 'PRE', wanted($.ast,'PRE').ann('code_object'), ($.ast).ann('past_block')); }
    method statement_prefix:sym($/)  { make $*W.add_phaser($/, 'POST', wanted($.ast,'POST').ann('code_object'), ($.ast).ann('past_block')); }
    method statement_prefix:sym($/) { make $*W.add_phaser($/, 'CLOSE', unwanted($.ast,'CLOSE').ann('code_object')); }

    method statement_prefix:sym($/)   {
        if %*COMPILING<%?OPTIONS> {
            make $*W.add_phaser($/, ~$, ($.ast).ann('code_object'), wanted($.ast,'DOC').ann('past_block'));
        }
        else {
            make QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) );
        }
    }

    method statement_prefix:sym($/) {
        make QAST::Op.new( :op('call'), $.ast );
    }

    method statement_prefix:sym($/) {
        my $past := unwanted($.ast,'gather');
        $past.ann('past_block').push(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) ));
        make QAST::Op.new( :op('call'), :name('&GATHER'), $past );
    }

    method statement_prefix:sym($/) {
        # Case-analyze what's inside of the Supply, to spot cases that can be
        # turned into something cheap rather than needing the whole supply
        # concurrency control mechanism.
        my $past := $.ast;
        my $block := $past.ann('past_block');
        if $*WHENEVER_COUNT == 0 {
            my $stmts := $block[1];
            if nqp::istype($stmts, QAST::Stmts) && nqp::elems($stmts) == 1 {
                my $stmt := $stmts[0];
                $stmt := $stmt[0] if nqp::istype($stmt, QAST::Want);
                if nqp::istype($stmt, QAST::Op) && $stmt.op eq 'call' && $stmt.name eq '&emit'
                    && nqp::elems($stmt.list) == 1 {
                    # Single statement emit; make block just return the expression
                    # (or die) and pass it to something that'll cheaply do a one
                    # shot emit.
                    $stmts[0] := $stmt[0];
                    make QAST::Op.new( :op('call'), :name('&SUPPLY-ONE-EMIT'), $past );
                    return 1;
                }
            }
        }
        elsif single_top_level_whenever($block) {
            $past.ann('past_block').push(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) ));
            make QAST::Op.new( :op('call'), :name('&SUPPLY-ONE-WHENEVER'), $past );
            return 1;
        }
        $past.ann('past_block').push(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) ));
        make QAST::Op.new( :op('call'), :name('&SUPPLY'), $past );
    }

    sub single_top_level_whenever($block) {
        if $*WHENEVER_COUNT == 1
        && nqp::getcomp('Raku').language_revision > 1 {
            my $stmts := $block[1];
            if nqp::istype($stmts, QAST::Stmts) {
                my @stmts := $stmts.list;
                my $last := @stmts[nqp::elems(@stmts) - 1];
                if nqp::istype($last, QAST::Stmt) {
                    return 0 if nqp::elems($last.list) != 1;
                    $last := $last[0];
                }
                if nqp::istype($last, QAST::Want) {
                    $last := $last[0];
                }
                if nqp::istype($last, QAST::Op) && $last.op eq 'call' && $last.name eq '&WHENEVER' {
                    return 1;
                }
            }
        }
        0
    }

    method statement_prefix:sym($/) {
        my $past := $.ast;
        my $block := $past.ann('past_block');
        if single_top_level_whenever($block) {
            $past.ann('past_block').push(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) ));
            make QAST::Op.new( :op('call'), :name('&REACT-ONE-WHENEVER'), $past );
        }
        else {
            $past.ann('past_block').push(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) ));
            make QAST::Op.new( :op('call'), :name('&REACT'), $past );
        }
    }

    method statement_prefix:sym($/) {
        # create state variable to remember whether we ran the block
        my $world := $*W;
        my $pad := $world.cur_lexpad();
        my $sym := $pad.unique('once_');
        my $mu := $world.find_single_symbol_in_setting('Mu');
        my $descriptor := $world.create_container_descriptor($mu, $sym);
        my %info;
        %info := %info := $world.find_single_symbol_in_setting('Scalar');
        %info := %info := %info := %info := $mu;
        $world.install_lexical_container($pad, $sym, %info, $descriptor, :scope('state'));
        for @($pad[0]) {
            if nqp::istype($_, QAST::Var) && $_.name eq $sym {
                # Mark, so we can migrate the once into a correct scope.
                $_.annotate('statement_id', $*STATEMENT_ID);
                $_.annotate('in_stmt_mod', $*IN_STMT_MOD);
                last;
            }
        }

        # generate code that runs the block only once
        make QAST::Op.new(
          :op('decont'),
          QAST::Op.new(
            :op('if'),
            QAST::Op.new( :op('p6stateinit') ),
            QAST::Op.new(
              :op('p6store'),
              WANTED(QAST::Var.new(:name($sym), :scope('lexical')),'once'),
              QAST::Op.new( :op('call'), wanted($.ast,'once') )
            ),
            WANTED(QAST::Var.new( :name($sym), :scope('lexical') ),'once')
          )
        );
    }

    method statement_prefix:sym($/) {
        my $world := $*W;
        my $block := $.ast.ann('past_block');
        unless $block.symbol('$/') {
            $world.install_lexical_magical($block, '$/');
        }
        unless $block.symbol('$!') {
            $world.install_lexical_magical($block, '$!');
        }
        my $qast := QAST::Op.new(
            :op('callmethod'),
            :name('start'),
            :returns($world.find_single_symbol_in_setting('Promise')),
            QAST::WVal.new( :value($world.find_single_symbol_in_setting('Promise')) ),
            $.ast
        );
        if nqp::getcomp('Raku').language_revision > 1 {
            $qast.push(QAST::WVal.new(
                :value($world.find_symbol(['Bool', 'True'])),
                :named('report-broken-if-sunk')
            ));
        }
        make $qast;
    }

    method statement_prefix:sym($/) {
        if $ {
            my $ast := $.ast;
            $ast[0].annotate('mode', 'lazy');
            $ast.annotate('statement_level', NQPMu);
            make $ast;
        }
        else {
            make QAST::Op.new(
                :op('callmethod'), :name('lazy'),
                QAST::Op.new( :op('call'), $.ast )
            );
        }
    }

    method statement_prefix:sym($/) {
        make QAST::Op.new(
            :op('callmethod'), :name('eager'),
            QAST::Op.new( :op('call'), $.ast )
        );
    }

    method statement_prefix:sym($/) {
        if $ {
            my $ast := $.ast;
            $ast[0].annotate('mode', 'hyper');
            $ast.annotate('statement_level', NQPMu);
            make $ast;
        }
        else {
            make QAST::Op.new(
                :op('callmethod'), :name('hyper'),
                QAST::Op.new( :op('call'), $.ast )
            );
        }
    }

    method statement_prefix:sym($/) {
        if $ {
            my $ast := $.ast;
            $ast[0].annotate('mode', 'race');
            $ast.annotate('statement_level', NQPMu);
            make $ast;
        }
        else {
            make QAST::Op.new(
                :op('callmethod'), :name('race'),
                QAST::Op.new( :op('call'), $.ast )
            );
        }
    }

    method statement_prefix:sym($/) {
        my $qast :=
        QAST::Stmts.new: :node($/),
          QAST::Op.new(:op, :name,
            QAST::Op.new(:op, $.ast)),
          QAST::Var.new: :name, :scope;

        # if user is trying to sink a variable, don't complain about uselessness
        my $block-stmts := $.ast.ann('past_block')[1];
        $block-stmts[0] := WANTED($block-stmts[0], 'statement_prefix/sink')
          if nqp::istype($block-stmts[0], QAST::Var);

        make $qast;
    }

    method statement_prefix:sym($/) {
        my $block := $.ast;
        my $past;
        if $block.ann('past_block').ann('handlers') && $block.ann('past_block').ann('handlers') {
            # we already have a CATCH block, nothing to do here
            $past := QAST::Op.new( :op('call'), $block );
        } else {
            $block := QAST::Op.new(:op, $block);
            $past := QAST::Op.new(
                :op('handle'),

                # Success path puts Any into $! and evaluates to the block.
                QAST::Stmt.new(
                    :resultchild(0),
                    $block,
                    QAST::Op.new(
                        :op('p6store'),
                        QAST::Var.new( :name<$!>, :scope ),
                        QAST::Var.new( :name, :scope )
                    )
                ),

                # On failure, capture the exception object into $!.
                'CATCH', QAST::Stmts.new(
                    QAST::Op.new(
                        :op('p6store'),
                        QAST::Var.new(:name<$!>, :scope),
                        QAST::Op.new(
                            :name<&EXCEPTION>, :op,
                            QAST::Op.new( :op('exception') )
                        ),
                    ),
                    QAST::WVal.new(
                        :value( $*W.find_single_symbol_in_setting('Nil') ),
                    ),
                )
            );
        }
        make $past;
    }

    method statement_prefix:sym($/) {
        make QAST::Op.new(
            :op('handle'),
            QAST::Op.new( :op('call'), $.ast ),
            'WARN',
            QAST::Op.new( :op('resume'), QAST::Op.new( :op('exception') ) )
        );
    }

    method blorst($/) {
        my $block;
        if $ {
            $block := $.ast;
        }
        else {
            my $stmt := $.ast;
            $block := make_thunk_ref($stmt, $/);
            migrate_blocks($*W.cur_lexpad, $block.ann('past_block'),
                -> $b { ($b.ann('statement_id') // -1) == $stmt.ann('statement_id') });
        }
        make block_closure($block);
    }

    # Statement modifiers

    method modifier_expr($/) { make WANTED($.ast, 'modifier_expr'); }

    method statement_mod_cond:sym($/)     {
        make QAST::Op.new( :op, $.ast, :node($/) );
    }

    method statement_mod_cond:sym($/) {
        make QAST::Op.new( :op, $.ast, :node($/) );
    }

    method statement_mod_cond:sym($/) {
        my $pat := $.ast;
        check_smartmatch($,$pat);
        make QAST::Op.new( :op,
            QAST::Op.new( :name('ACCEPTS'), :op('callmethod'),
                          $pat,
                          WANTED(QAST::Var.new( :name('$_'), :scope('lexical') ),'when') ),
            :node($/)
        );
    }

    method statement_mod_cond:sym($/)    { make $.ast; }
    method statement_mod_cond:sym($/) { make $.ast; }

    method smexpr($/) { make WANTED($.ast, 'smexpr'); }

    method statement_mod_loop:sym($/)  { make $.ast; }
    method statement_mod_loop:sym($/)  { make $.ast; }
    method statement_mod_loop:sym($/)    { make $.ast; }
    method statement_mod_loop:sym($/)  { make $.ast; }

    ## Terms

    method term:sym($/)           { make $.ast; }
    method term:sym($/)          { make $.ast; }
    method term:sym($/)           { make $.ast; }
    method term:sym($/) { make $.ast; }
    method term:sym($/)   { make $.ast; }
    method term:sym($/) { make $.ast; }
    method term:sym($/)   { make $.ast; }
    method term:sym($/)   { make $.ast; }
    method term:sym($/)    { make $.ast; }
    method term:sym($/)          { make $.ast; }
    method term:sym($/)   { make $.ast; }
    method term:sym($/)            { make $.ast; }
    method term:sym($/) {
        my $ast   := $.ast;
        my $block := $ast.ann('past_block');







        make block_closure($ast);
    }
    method term:sym($/) {
        make QAST::Unquote.new(:position(nqp::elems(@*UNQUOTE_ASTS)));
        @*UNQUOTE_ASTS.push($.ast);
    }

    method name($/) { }

    method fatarrow($/) {
        make make_pair($/,$.Str, wanted($.ast, 'fatarrow'));
    }

    method coloncircumfix($/) {
        make $
            ?? $.ast
            !! QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Nil')) );
    }

    method colonpair($/) {
        if $*key {
            if $ {
                make make_pair($/,$*key, $.ast);
            }
            elsif $ {
                make make_pair($/,$*key, $*W.add_numeric_constant($/, 'Int', $*value));
            }
            elsif $*value ~~ NQPCapture {
                my $val_ast := $*value.ast;
                if nqp::istype($val_ast, QAST::Stmts) && +@($val_ast) == 1 {
                    $val_ast := $val_ast[0];
                }
                make make_pair($/,$*key, $val_ast);
            }
            else {
                make make_pair($/,$*key, QAST::Op.new(
                    :op('hllbool'),
                    QAST::IVal.new( :value($*value) )
                ));
            }
        }
        elsif $ {
            make $.ast;
        }
        else {
            make $*value.ast;
        }
    }

    method colonpair_variable($/) {
        if $ {
            make QAST::Op.new(
                :op('call'),
                :name('&postcircumfix:<{ }>'),
                QAST::Var.new(:name('$/'), :scope('lexical')),
                $*W.add_string_constant(~$)
            );
        }
        else {
            make make_variable($/, [~$/]);
        }
    }

    sub make_pair($/, $key_str, $value, :$no-sink = 1) {
        my $key := $*W.add_string_constant($key_str);
        my $Pair := $*W.find_single_symbol_in_setting('Pair');
        my $pair := QAST::Op.new(
            :op('callmethod'), :name('new'), :returns($Pair), :node($/),
            QAST::WVal.new( :value($Pair), :node($/) ),
            $key, WANTED($value, 'make_pair')
        );
        $pair.nosink(1) if $no-sink;
        $pair;
    }

    method desigilname($/) {
        if $ {
            make QAST::Op.new( :op('callmethod'), wanted($.ast, 'desigilname') );
        }
    }

    method variable($/) {
        my $past;
        my $sigil := ~$;
        if $ {
            $past := QAST::Op.new(
                :op('call'),
                :name('&postcircumfix:<[ ]>'),
                QAST::Var.new(:name('$/'), :scope('lexical')),
                $*W.add_constant('Int', 'int', nqp::radix(10, $, 0, 0)[0]),
            );
            if $sigil eq '@' || $sigil eq '%' {
                my $name := $sigil eq '@' ?? 'list' !! 'hash';
                $past := QAST::Op.new( :op('callmethod'), :name($name), $past );
            }
        }
        elsif $ {
            $past := $.ast;
            $past.unshift( QAST::Var.new( :name('$/'), :scope('lexical') ) );
            if $sigil eq '@' || $sigil eq '%' {
                my $name := $sigil eq '@' ?? 'list' !! 'hash';
                $past := QAST::Op.new( :op('callmethod'), :name($name), $past );
            }
        }
        elsif $ {
            $past := $.ast;
        }
        elsif $ {
            my $name := '&infix' ~ $*W.canonicalize_pair('', $.Str);
            $past := QAST::Op.new(
                :op('ifnull'),
                QAST::Var.new( :name($name), :scope('lexical') ),
                QAST::Op.new(
                    :op('die_s'),
                    QAST::SVal.new( :value("Could not find sub $name") )
                ));
        }
        elsif $ {
            $past := $.ast;
            $past.name($sigil eq '@' ?? 'cache' !!
                       $sigil eq '%' ?? 'hash' !!
                                        'item');
        }
        else {
            my $indirect;
            if $ && $ {
                my $longname := $*W.dissect_longname($);
                if $longname.contains_indirect_lookup() {
                    if $*IN_DECL {
                        $*W.throw($/, ['X', 'Syntax', 'Variable', 'IndirectDeclaration']);
                    }
                    $past := self.make_indirect_lookup($longname.components(), ~$);
                    $indirect := 1;
                }
                else {
                    $past := make_variable($/, $longname.attach_adverbs.variable_components(
                        $sigil, $ ?? ~$ !! ''));
                }
            }
            else {
                my $name := ~$/;
                if !$*IN_DECL && nqp::chars($name) == 1 && $name eq ~$ {
                    my $*IN_DECL := 'variable';
                    my $*SCOPE := 'state';
                    my $*OFTYPE;  # should default to Mu/Mu/Any
                    $past := QAST::Var.new( :node($/) );
                    $past := declare_variable($/, $past, $name, '', '', []);
                    $past.nosink(1);
                }
                else {
                    $past := make_variable($/, [$name]);
                }
            }
        }
        if $*IN_DECL eq 'variable' {
            $past.sinkok(1);
        }

        make $past;
    }

    method contextualizer($/) {
        my $past := $.ast;
        my $has_magic := nqp::getcomp('Raku').language_revision < 2 && $ eq '';
        my $sigil := ~$;

        if $has_magic && $sigil eq '$' { # for '$()'
            my $result_var := $past.unique('sm_result');
            $past := QAST::Stmt.new(
                # Evaluate RHS and call ACCEPTS on it, passing in $_. Bind the
                # return value to a result variable.
                QAST::Op.new( :op('bind'),
                    QAST::Var.new( :name($result_var), :scope('local'), :decl('var') ),
                    QAST::Op.new(
                        :op('if'),
                        # condition
                        QAST::Op.new(
                            :op('callmethod'), :name('ast'),
                            QAST::Var.new( :name('$/'), :scope('lexical') )
                        ),
                        # when true
                        QAST::Op.new(
                            :op('callmethod'), :name('ast'),
                            QAST::Var.new( :name('$/'), :scope('lexical') )
                        ),
                        # when false
                        QAST::Op.new(
                            :op('callmethod'), :name('Str'),
                            QAST::Var.new( :name('$/'), :scope('lexical') )
                        )
                    )
                ),
                # And finally evaluate to the smart-match result.
                WANTED(QAST::Var.new( :name($result_var), :scope('local') ),'make_smartmatch')
            );
            $past := QAST::Op.new( :op('locallifetime'), $past, $result_var );
        }
        else {
            my $name := $sigil eq '@' ?? 'cache' !!
                        $sigil eq '%' ?? 'hash' !!
                                         'item';
            # @() and %()
            $past := QAST::Var.new( :name('$/'), :scope('lexical') ) if $has_magic;

            $past := QAST::Op.new( :op('callmethod'), :name($name), $past );
        }
        make WANTED($past, 'contextualizer');
    }

    sub make_variable($/, @name) {
        make_variable_from_parts($/, @name, ~$, ~$, ~$);
    }

    # System variables which are considered deprecated. Keys are variable names, values are list of revision which
    # deprecates the variable, and alternative to be used instead.
    my %variable_deprecations := nqp::hash(
        '$*PERL', nqp::list(3, '$*RAKU'),
    );

    sub make_variable_from_parts($/, @name, $sigil, $twigil, $desigilname) {
        my $past := QAST::Var.new( :name(@name[+@name - 1]), :node($/));
        my $name := $past.name();
        my $world := $*W;
        my @deprecation;

        if nqp::existskey(%variable_deprecations, $name)
            && nqp::isge_i(
                nqp::getcomp('Raku').language_revision,
                %variable_deprecations{$name}[0])
        {
            @deprecation := nqp::clone(%variable_deprecations{$name});
            nqp::push(@deprecation, $world.current_file);
            nqp::push(@deprecation, $world.current_line($/));
        }

        if $twigil eq '*' {
            if +@name > 1 {
                $world.throw($/, 'X::Dynamic::Package', symbol => ~$/);
            }
            $past := QAST::Op.new(
                :op('call'), :name('&DYNAMIC'),
                $world.add_string_constant($name));
            if +@deprecation {
                $world.add_object_if_no_sc(@deprecation);
                $past.push(QAST::WVal.new(:value(@deprecation)));
            }
        }
        elsif $twigil eq '?' && $*IN_DECL eq 'variable' && !$*COMPILING_CORE_SETTING {
            $world.throw($/, 'X::Syntax::Variable::Twigil',
              name       => $name,
              twigil     => $twigil,
              scope      => $*SCOPE,
              additional => ' because it is reserved'
            );
        }
        elsif $twigil eq '!' {
            # In a declaration, don't produce anything here.
            if $*IN_DECL ne 'variable' {
                setup_attr_var($/, $past);
            }
        }
        elsif ($twigil eq '.' || $twigil eq '.^') && $*IN_DECL ne 'variable' {
            if !$*HAS_SELF {
                $world.throw($/, ['X', 'Syntax', 'NoSelf'], variable => $name);
            } elsif $*HAS_SELF eq 'partial' {
                $world.throw($/, ['X', 'Syntax', 'VirtualCall'], call => $name);
            }
            # Need to transform this to a method call.
            $past := $ ?? $.ast !! QAST::Op.new();
            if $twigil eq '.^' {
                $past.op('p6callmethodhow');
            }
            else {
                $past.op('callmethod');
            }
            $past.name($desigilname);
            $past.unshift(QAST::Var.new( :name('self'), :scope('lexical') ));
            # Contextualize based on sigil.
            $past := QAST::Op.new(
                :op('callmethod'),
                :name($sigil eq '@' ?? 'list' !!
                      $sigil eq '%' ?? 'hash' !!
                      'item'),
                $past);
        }
        elsif $twigil eq '^' || $twigil eq ':' {
            $past := add_placeholder_parameter($/, $sigil, $desigilname,
                                :named($twigil eq ':'), :full_name($name));
        }
        elsif $twigil eq '~' {
            $past := QAST::Op.new(
                :op, :name, :returns($world.find_single_symbol_in_setting('Slang')),
                QAST::Var.new( :name, :scope ));
            my $g := $/.slang_grammar($desigilname);
            $world.add_object_if_no_sc($g);
            my $a := $/.slang_actions($desigilname);
            if !nqp::isnull($g) {
                my $wval := QAST::WVal.new( :value($g) );
                $wval.named('grammar');
                $past.push($wval);
                $wval := QAST::WVal.new( :value($a) );
                $wval.named('actions');
                $past.push($wval);
            }
        }
        elsif $twigil eq '=' && $desigilname ne 'pod' && $desigilname ne 'finish' {
            $world.throw($/,
              'X::Comp::NYI', feature => 'Pod variable ' ~ $name);
        }
        elsif $name eq '@_' {
            if $world.nearest_signatured_block_declares('@_') {
                $past.scope('lexical');
            }
            else {
                $past := add_placeholder_parameter($/, '@', '_',
                                :pos_slurpy(1), :full_name($name));
            }
        }
        elsif $name eq '%_' {
            if $world.nearest_signatured_block_declares('%_') || $*METHODTYPE {
                $past.scope('lexical');
            }
            else {
                $past := add_placeholder_parameter($/, '%', '_', :named_slurpy(1),
                                :full_name($name));
            }
        }
        elsif $name eq '$?LANG' || $name eq '$?LINE' || $name eq '$?FILE' {
            if $*IN_DECL eq 'variable' {
                $world.throw($/, 'X::Syntax::Variable::Twigil',
                  name       => $name,
                  twigil     => '?',
                  scope      => $*SCOPE,
                  additional => " because it is reserved",
                );
            }
            if $name eq '$?LANG' {
                my $cursor := $/;
                $world.add_object_if_no_sc($cursor);
                $past := QAST::WVal.new(:value($cursor));
            }
            elsif $name eq '$?LINE' {
                $past := $world.add_constant('Int', 'int', $world.current_line($/));
            }
            else {
                $past := $world.add_string_constant($world.current_file);
            }
        }
        elsif $name eq '%?RESOURCES' {
            my $resources := nqp::getlexdyn('$*RESOURCES');
            unless $resources {
                my $Resources := $world.find_symbol(['Distribution', 'Resources']);
                $resources := $Resources.from-precomp();
            }
            if $resources {
                $past := QAST::WVal.new( :value($resources) );
                if nqp::isnull(nqp::getobjsc($resources)) {
                    $world.add_object_if_no_sc($resources);
                }
            }
            else {
                $past := QAST::WVal.new( :value($world.find_single_symbol_in_setting('Nil')) );
            }
        }
        elsif $name eq '$?DISTRIBUTION' {
            my $distribution := nqp::getlexdyn('$*DISTRIBUTION');
            unless $distribution {
                my $Distribution := $world.find_symbol(['CompUnit', 'Repository', 'Distribution']);
                $distribution := $Distribution.from-precomp();
            }
            if $distribution {
                $past := QAST::WVal.new( :value($distribution) );
                if nqp::isnull(nqp::getobjsc($distribution)) {
                    $world.add_object_if_no_sc($distribution);
                }
            }
            else {
                $past := QAST::WVal.new( :value($world.find_single_symbol_in_setting('Nil')) );
            }
        }
        elsif $name eq '&?BLOCK' || $name eq '&?ROUTINE' {
            if $*IN_DECL eq 'variable' {
                $world.throw($/, 'X::Syntax::Variable::Twigil',
                  name       => $name,
                  twigil     => '?',
                  scope      => $*SCOPE,
                  additional => " because it is reserved",
                );
            }
            my $Routine := $world.find_single_symbol_in_setting('Routine');
            if $name eq '&?BLOCK' || nqp::istype($*CODE_OBJECT, $Routine) {
                # Just need current code object.
                $past := QAST::Op.new( :op('getcodeobj'), QAST::Op.new( :op('curcode') ) );
            }
            else {
                my int $scopes := 0;
                my int $done := 0;
                $past := QAST::Op.new( :op('ctx') );
                until $done {
                    my $co := $world.get_code_object(:$scopes);
                    if nqp::istype($co, $Routine) {
                        $past := QAST::Op.new(
                            :op('getcodeobj'),
                            QAST::Op.new( :op('ctxcode'), $past )
                        );
                        $done := 1;
                    }
                    elsif !nqp::isconcrete($co) {
                        # Spit out a lexical that we'll fail to look up. Can't just
                        # go and throw because if we're in an interpolated string
                        # we should not complain about this.
                        $past := QAST::Var.new( :name('&?ROUTINE'), :scope('lexical') );
                        $done := 1;
                    }
                    else {
                        $past := QAST::Op.new( :op('ctxouterskipthunks'), $past );
                    }
                    $scopes++;
                }
            }
        }
        elsif +@name > 1 {
            $past := $world.symbol_lookup(@name, $/, :lvalue(1));
        }
        elsif $*IN_DECL ne 'variable' && (my $attr_alias := $world.is_attr_alias($name)) {
            $past.name($attr_alias);
            setup_attr_var($/, $past);
        }
        elsif $*IN_DECL ne 'variable' {
            # Expect variable to have been declared somewhere.
            # Locate descriptor and thus type.
            $past.scope('lexical');
            try {
                my $type := $world.find_lexical_container_type($name);
                $past.returns($type);
                if nqp::objprimspec($type) && !$world.is_lexical_marked_ro($past.name) {
                    $past.scope('lexicalref');
                }
            }

            # If it's a late-bound sub lookup, we may not find it, so be sure
            # to handle the case where the lookup comes back null.
            if $sigil eq '&' {
                $past := QAST::Op.new(
                    :op('ifnull'), $past,
                    QAST::WVal.new( :value($world.find_single_symbol_in_setting('Nil')) ));
            }
        }
        $past
    }

    sub setup_attr_var($/, $past) {
        unless $*HAS_SELF {
            $*W.throw($/, ['X', 'Syntax', 'NoSelf'], variable => $past.name());
        }
        my $attr := $*W.get_attribute_meta_object($/, $past.name(), $past);
        my $type := $attr ?? $attr.type !! NQPMu;
        $past.returns($type) if $attr;
        $past.scope(nqp::objprimspec($type) ?? 'attributeref' !! 'attribute');
        $past.unshift(instantiated_type(['$?CLASS'], $/));
        $past.unshift(QAST::Var.new( :name('self'), :scope('lexical') ));
    }

    method package_declarator:sym($/) { make $.ast; }
    method package_declarator:sym($/)  { make $.ast; }
    method package_declarator:sym($/)   { make $.ast; }
    method package_declarator:sym($/) { make $.ast; }
    method package_declarator:sym($/)    { make $.ast; }
    method package_declarator:sym($/) { make $.ast; }
    method package_declarator:sym($/)  { make $.ast; }

    method package_declarator:sym($/) {
        $*W.apply_trait($/, '&trait_mod:', $/.package, $.ast);
    }

    method package_declarator:sym($/) {
        $*W.apply_traits($, $*DECLARAND);
    }

    method package_def($/) {
        my $world := $*W;
        my $*LEAF := $/;
        my $package := $*LEAF.package;
        # Get the body block AST.
        my $block;
        if $ {
            $block := $.ast;
        }
        else {
            $block := $*CURPAD;
            $block.push($.ast);
            $block.node($/);
        }
        $block.blocktype('immediate');

        if $*PKGDECL ne 'role' && $block.ann('placeholder_sig') {
            my $name := $block.ann('placeholder_sig')[0];
            unless $name eq '%_' || $name eq '@_' {
                $name := nqp::concat(nqp::substr($name, 0, 1),
                        nqp::concat('^', nqp::substr($name, 1)));
            }
            $world.throw( $/, ['X', 'Placeholder', 'Block'],
                placeholder => $name,
            );
        }

        # If it's a stub, add it to the "must compose at some point" list,
        # then just evaluate to the type object. Don't need to do any more
        # just yet.
        if is_yada($/) {
            unless $*PKGDECL eq 'role' {
                $world.add_stub_to_check($package);
            }
            $block.blocktype('declaration');
            make QAST::Stmts.new( $block, QAST::WVal.new( :value($package) ) );
            return 1;
        }

        # Handle parametricism for roles.
        if $*PKGDECL eq 'role' {
            # Set up signature. Needs to have $?CLASS as an implicit
            # parameter, since any mention of it is generic.
            my %sig_info := $ ?? $.ast !! hash(parameters => []);
            my @params := %sig_info;
            @params.unshift(hash(
                is_multi_invocant => 1,
                type_captures     => nqp::list_s('$?CLASS', '::?CLASS')
            ));
            my $sig := $world.create_signature_and_params($, %sig_info, $block, 'Mu');
            add_signature_binding_code($block, $sig, @params);
            $block.blocktype('declaration_static');

            # Role bodies run at BEGIN time, so need fixup.
            begin_time_lexical_fixup($block);

            # As its last act, it should grab the current lexpad so that
            # we have the type environment, and also return the parametric
            # role we're in (because if we land it through a multi-dispatch,
            # we won't know).
            $block[1].push(QAST::Op.new(
                :op('list'),
                QAST::WVal.new( :value($package) ),
                QAST::Op.new( :op('curlexpad') )));

            # Finish code object and add it as the role's body block.
            my $code := $*CODE_OBJECT;
            $world.attach_signature($code, $sig);
            $world.finish_code_object($code, $block, 0);
            $world.add_phasers_handling_code($code, $block);
            $world.pkg_set_role_body_block($/, $package, $code, $block);

            # Compose before we add the role to the group, so the group sees
            # it composed.
            $world.pkg_compose($/, $package);

            # Add this role to the group if needed.
            my $group := $package.HOW.group($package);
            unless $group =:= $package {
                $world.pkg_add_role_group_possibility($/, $group, $package);
            }
        }
        else {
            # Compose.
            $world.pkg_compose($/, $package);

            # If we have a grammar on our hands, precompute some NFAs
            if $*PKGDECL eq 'grammar' {
                if nqp::can($package, '!precompute_nfas') {
                    $package.'!precompute_nfas'();
                }
            }

            # Finish code object for the block.
            my $code := $*CODE_OBJECT;
            $world.attach_signature($code, $world.create_signature(nqp::hash('parameter_objects', [])));
            $world.finish_code_object($code, $block, 0);
            $world.add_phasers_handling_code($code, $block);
        }

        # check up any private attribute usage
        for %*ATTR_USAGES {
            my $name   := $_.key;
            my @usages := $_.value;
            for @usages {
                my $past := $_;
                my $attr := $world.get_attribute_meta_object($past.node, $name);
                $past.returns($attr.type);
            }
        }

        # Document
        Perl6::Pod::document($/, $package, $*POD_BLOCK, :leading);
        if ~$*POD_BLOCK ne '' {
            $*POD_BLOCK.set_docee($package);
        }

        make QAST::Stmts.new(
            $block, QAST::WVal.new( :value($package) )
        );
    }

    # When code runs at BEGIN time, such as role bodies and BEGIN
    # blocks, we need to ensure we get lexical outers fixed up
    # properly when deserializing after pre-comp. To do this we
    # make a list of closures, which each point to the outer
    # context. These survive serialization and thus point at what
    # has to be fixed up.
    sub begin_time_lexical_fixup($block) {
        my $has_nested_blocks := 0;
        for @($block[0]) {
            if nqp::istype($_, QAST::Block) {
                $has_nested_blocks := 1;
                last;
            }
        }
        return 0 unless $has_nested_blocks;

        my $throwaway_block_past := QAST::Block.new(
            :blocktype('declaration'), :name('!LEXICAL_FIXUP'),
            WANTED(QAST::Var.new( :name('$_'), :scope('lexical'), :decl('var') ),'btlf')
        );
        $throwaway_block_past.annotate('outer', $block);
        $block[0].push($throwaway_block_past);
        my $world := $*W;
        my $throwaway_block := $world.create_code_object($throwaway_block_past,
            'Block', $world.create_signature(nqp::hash('parameter_objects', [])));
        my $fixup := $world.create_lexical_capture_fixup();
        $fixup.push(QAST::Op.new(
                :op('p6capturelex'),
                QAST::Op.new(
                    :op('callmethod'), :name('clone'),
                    QAST::WVal.new( :value($throwaway_block) )
                )));
        $block[0].push($fixup);
    }

    method scope_declarator:sym($/)      { make $.ast; }
    method scope_declarator:sym($/)     { make $.ast; }
    method scope_declarator:sym($/)     { make $.ast; }
    method scope_declarator:sym($/)    { make $.ast; }
    method scope_declarator:sym($/) { make $.ast; }
    method scope_declarator:sym($/)   { make $.ast; }
    method scope_declarator:sym($/)    { make $.ast; }
    method scope_declarator:sym($/)     {
        my $scoped := $.ast;
        my $attr   := $scoped.ann('metaattr');
        if $attr.package.REPR ne 'CStruct'
        && $attr.package.REPR ne 'CPPStruct'
        && $attr.package.REPR ne 'CUnion' {
            $*W.throw($/, ['X', 'Attribute', 'Scope', 'Package'], :scope,
                :allowed('classes with CStruct, CPPStruct and CUnion representation are supported'),
                :disallowed('package with ' ~ $attr.package.REPR ~ ' representation'));
        }
        if nqp::objprimspec($attr.type) != 0 {
            $/.worry('Useless use of HAS scope on ' ~ $attr.type.HOW.name($attr.type) ~ ' typed attribute.');
        }

        if $attr.type.REPR eq 'CArray' {
            if $ -> $semilist {
                my @dimensions := nqp::list_i();
                for $semilist -> $dimension {
                    my $elems := nqp::unbox_i($*W.compile_time_evaluate($/, $dimension.ast, :mark-wanted));
                    nqp::push_i(@dimensions, $elems);
                }
                nqp::bindattr($attr, $attr.WHAT, '$!dimensions', @dimensions);
            }
        }

        # Mark $attr as inlined, that's why we do all this.
        nqp::bindattr_i($attr, $attr.WHAT, '$!inlined', 1);
        make $scoped;
    }

    method declarator($/) {
        if    $  { make $.ast  }
        elsif $    { make $.ast    }
        elsif $     { make $.ast     }
        elsif $ {
            my $world := $*W;
            my $past := $.ast;
            if $ {
                my $orig_past := $past;
                my $initast := $.ast;
                if $*SCOPE eq 'has' {
                    if $ eq '=' {
                        self.install_attr_init($,
                            $past.ann('metaattr'),
                            $initast, $*ATTR_INIT_BLOCK);
                    }
                    elsif $ eq '.=' {
                        my $type := nqp::defined($*OFTYPE)
                          ?? $world.maybe-nominalize($*OFTYPE.ast) !! $world.find_symbol: ['Any'];
                          # ?? $world.maybe-definite-how-base($*OFTYPE.ast) !! $world.find_symbol: ['Any'];
                        my $dot_equals := $initast;
                        $dot_equals.unshift(QAST::WVal.new(:value($type)));
                        $dot_equals.returns($type);
                        self.install_attr_init($,
                            $past.ann('metaattr'),
                            $dot_equals, $*ATTR_INIT_BLOCK);
                    }
                    else {
                        $/.panic("Cannot use " ~ $ ~
                            " to initialize an attribute");
                    }
                }
                elsif $ eq '=' {
                    if $past.ann('init_removal') -> $remove {
                        $remove();
                    }
                    $past := assign_op($/, $past, $initast, :initialize);
                }
                elsif $ eq '.=' {
                    $past := make_dot_equals($past, $initast);
                }
                else {
                    if nqp::istype($past, QAST::Var) {
                        find_var_decl($world.cur_lexpad(), $past.name).decl('var');
                    }
                    $past := bind_op($/, $past, $initast,
                        $ eq '::=');
                }
                if $*SCOPE eq 'state' {
                    if nqp::istype( (my $outer_block := $world.cur_lexpad), QAST::Block) {
                        $outer_block.annotate('has_statevar', $outer_block[0]);
                    }

                    $past := QAST::Op.new( :op('if'),
                        QAST::Op.new( :op('p6stateinit') ),
                        $past,
                        $orig_past);
                    $past.nosink(1);
                }
            }
            # No initializer, check that the (specified) type accepts the default value.
            elsif $ eq '$' {
                if nqp::istype($past, QAST::Var) {
                    if $world.cur_lexpad.symbol($past.name) -> %sym {
                        if %sym {
                            check_default_value_type($/, %sym, %sym, 'variable');
                        }
                    }
                }
                elsif $past.ann('metaattr') -> $attr {
                    if !$attr.required && !$attr.type.HOW.archetypes($attr.type).generic {
                        check_default_value_type($/, $attr.container_descriptor, $attr.type, 'attribute');
                    }
                }
            }
            make $past;
        }
        elsif $ {
            # Go over the params and declare the variable defined in them.
            my $list      := QAST::Op.new( :op('call'), :name('&infix:<,>') );
            my @params    := $.ast;
            my $common_of := $*OFTYPE;
            my @nosigil;
            for @params {
                my $*OFTYPE := $common_of;
                if nqp::existskey($_, 'of_type') && nqp::existskey($_, 'of_type_match') {
                    if $common_of {
                        ($_ // $).typed_sorry(
                            'X::Syntax::Variable::ConflictingTypes',
                            outer => $common_of.ast, inner => $_);
                    }
                    $*OFTYPE := $_;
                    $*OFTYPE.make($_);
                }

                my $post := $_ ?? $_ !! [];
                if $_ {
                    my $name := $_;
                    my $sigil := $_;
                    my $twigil := $_;
                    my $desigilname := $_;
                    if $desigilname {
                        ensure_unused_in_scope($/, $name, $twigil)
                    }
                    my $past := QAST::Var.new( :$name );
                    $past := declare_variable($/, $past, $sigil, $twigil,
                        $desigilname, $, :$post);
                    unless nqp::istype($past, QAST::Op) && $past.op eq 'null' {
                        $list.push($past);
                        if $sigil eq '' {
                            nqp::push(@nosigil, ~$desigilname);
                        }
                    }
                }
                else {
                    my $world := $*W;
                    $world.handle_OFTYPE_for_pragma($/,'parameters');
                    my %cont_info := $world.container_type_info($/, :$post,
                      $_ || '$', $*OFTYPE ?? [$*OFTYPE.ast] !! [], []);
                    $list.push($world.build_container_past(
                      %cont_info,
                      $world.create_container_descriptor(
                        %cont_info, 'anon', %cont_info)));
                }
            }

            if $ {
                my $orig_list := $list;
                my $initast := $.ast;
                if $ eq '=' {
                    $/.panic("Cannot assign to a list of 'has' scoped declarations")
                        if $*SCOPE eq 'has';
                    $list := assign_op($/, $list, $initast);
                    if @nosigil {
                        $list := QAST::Stmts.new( :resultchild(0), $list );
                        for @nosigil {
                            $list.push(QAST::Op.new(
                                :op('bind'),
                                QAST::Var.new( :name($_), :scope('lexical') ),
                                QAST::Op.new(
                                    :op('p6recont_ro'),
                                    QAST::Var.new( :name($_), :scope('lexical') )
                                )));
                        }
                    }
                }
                elsif $ eq '.=' {
                    $/.panic("Cannot use .= initializer with a list of declarations");
                }
                else {
                    my %sig_info := $.ast;
                    my $signature := $*W.create_signature_and_params($/, %sig_info, $*W.cur_lexpad(), 'Mu');
                    $list := QAST::Op.new(
                        :op('p6bindcaptosig'),
                        QAST::WVal.new( :value($signature) ),
                        QAST::Op.new(
                            :op('callmethod'), :name('Capture'),
                            $initast
                        )
                    );
                }
                if $*SCOPE eq 'state' {
                    $list := QAST::Op.new( :op('if'),
                        QAST::Op.new( :op('p6stateinit') ),
                        $list, $orig_list);
                }
            }
            elsif @nosigil {
                $/.typed_panic('X::Syntax::Term::MissingInitializer');
            }
            else {
                $list := QAST::Want.new($list, 'v', QAST::Op.new( :op('null')));
            }

            make $list;
        }
        elsif $ {
            my $world := $*W;
            # 'my \foo' style declaration
            my $name      := $.ast;
            my $init-qast := $.ast;

            $init-qast.unshift:
              QAST::WVal.new: value => nqp::defined($*OFTYPE)
                ?? $world.maybe-nominalize($*OFTYPE.ast) !! $world.find_symbol: ['Mu']
                # ?? $world.maybe-definite-how-base($*OFTYPE.ast) !! $world.find_symbol: ['Mu']
            if $ eq '.=';

            my $qast;
            if $*OFTYPE {
                my $type := $*OFTYPE.ast;
                $qast := QAST::Op.new(
                    :op,
                    QAST::Var.new(:$name, :scope),
                    $type =:= $world.find_single_symbol_in_setting('Mu')
                        ?? WANTED($init-qast, 'declarator/deftermnow3')
                        !! QAST::Op.new(
                            :op('p6bindassert'),
                            WANTED($init-qast, 'declarator/deftermnow2'),
                            QAST::WVal.new( :value($type) ),
                        )
                );
            }
            else {
                $qast := QAST::Op.new(
                    :op,
                    QAST::Var.new(:$name, :scope),
                        WANTED($init-qast, 'declarator/bind')
                );
            }
            make $ eq '.='
              ?? $qast.annotate_self: 'fake_infix_adverb_target', $init-qast
              !! $qast
        }
        else {
            $/.panic('Unknown declarator type');
        }
    }

    sub check_default_value_type($/, $descriptor, $bind_constraint, $what) {
        my $matches;
        my $maybe := 0;
        try {
            $matches := nqp::istype($descriptor.default, $bind_constraint);
            CATCH {
                $maybe := 1;
                my $pl := nqp::getpayload($_);
                if nqp::istype($pl, $*W.find_single_symbol_in_setting('Exception')) {
                    @*SORROWS.push($pl); # XXX Perhaps a method on Grammer similar to typed_sorry but which accepts an exception?
                } else {
                    # Don't be too verbose, report only the actual line with the error.
                    $/.sorry(nqp::getmessage($_), "\n", nqp::shift(nqp::backtracestrings($_)));
                }
            }
        }
        unless $matches {
            $/.typed_sorry('X::Syntax::Variable::MissingInitializer',
                what     => $what,
                type     => nqp::how($bind_constraint).name($bind_constraint),
                maybe    => $maybe,
                implicit => !nqp::istype($*OFTYPE, NQPMatch) || !$*OFTYPE || $*OFTYPE && !$*OFTYPE.ast && !$*OFTYPE.ast
                         ?? ':' ~ $/.pragma($what ~ 's') ~ ' by pragma'
                         !! 0
            );
        }
    }

    method multi_declarator:sym($/) { make $ ?? $.ast !! $.ast }
    method multi_declarator:sym($/) { make $ ?? $.ast !! $.ast }
    method multi_declarator:sym($/)  { make $ ?? $.ast !! $.ast }
    method multi_declarator:sym($/)  { make $.ast }

    method scoped($/) {
        make $.ast;
    }

    method variable_declarator($/) {
        my $qast   := $.ast;
        my $sigil  := $;
        my $twigil := $;
        my $desigilname := ~$;
        my $name := $sigil ~ $twigil ~ $desigilname;

        # Don't know why this doesn't work all the time.
        if $desigilname ~~ /\w ':' / {
            $name   := $.ast.name;  # is already canonicalized in 
            $desigilname := nqp::substr($name, nqp::chars($sigil ~ $twigil));
        }

        if $ {
            my $lex := $*W.cur_lexpad();
            if $lex.symbol($name) {
                $/.typed_worry('X::Redeclaration', symbol => $name);
                unless $name eq '$_' {
                    $qast.scope('lexical') if nqp::istype($qast, QAST::Var) && !$qast.scope;
                    make $qast;
                    return;
                }
            }
            else {
                ensure_unused_in_scope($/, $name, $twigil);
            }
        }
        if nqp::elems($) > 1 {
            $/.panic('Multiple shapes not yet understood');
        }
        my @post;
        for $ {
            @post.push($_.ast);
        }
        make declare_variable($/, $qast, ~$sigil, ~$twigil, $desigilname, $, $, :@post);
    }

    sub ensure_unused_in_scope($/, $name, $twigil) {
        my $lex := $*W.cur_lexpad();
        if $lex.ann('also_uses') && $lex.ann('also_uses'){$name} {
            if ~$twigil eq '*' {
                $/.typed_sorry('X::Dynamic::Postdeclaration', symbol => $name);
            }
            else {
                $/.typed_sorry('X::Redeclaration::Outer', symbol => $name);
            }
        }
    }

    sub declare_variable($/, $past, $sigil, $twigil, $desigilname, $trait_list, $shape?, :@post) {
        my $world := $*W;
        my $name  := $sigil ~ $twigil ~ $desigilname;
        my $BLOCK := $world.cur_lexpad();
        my $package := $/.package;

        my $of_type := nqp::null;
        my $is_type := nqp::null;

        $world.handle_OFTYPE_for_pragma($/, $*SCOPE eq 'has' ?? 'attributes' !! 'variables');
        if $*OFTYPE {
            $of_type := $*OFTYPE.ast;
            my $archetypes := $of_type.HOW.archetypes($of_type);
            unless $archetypes.nominal
                || $archetypes.nominalizable
                || $archetypes.generic
                || $archetypes.definite
                || $archetypes.coercive {
                $*OFTYPE.typed_sorry('X::Syntax::Variable::BadType', type => $of_type);
            }
        }

        sub check_type($thrower, $outer, $inner) {
            unless nqp::isnull($outer) {
                $thrower.typed_sorry(
                  'X::Syntax::Variable::ConflictingTypes',
                  outer => $outer, inner => $inner
                );
            }
            $inner
        }

        # Process traits for `is Type` and `of Type`, which get special
        # handling by the compiler.
        my @late_traits;
        for $trait_list {
            my $trait := $_.ast;
            if $trait {
                my str $mod := $trait.mod;
                if $mod eq '&trait_mod:' {
                    $of_type := check_type($_, $of_type, $trait.args[0]);
                    next;
                }

                # see if we need to handle "my $a is Foo"
                elsif $mod eq '&trait_mod:' {
                    if nqp::elems($trait.args) -> $elems {
                        my $type := $trait.args[0];

                        # an actual type
                        unless nqp::isconcrete($type) {

                            # is Foo
                            if $elems == 1 {
                                $is_type := check_type($_, $is_type, $type);
                                next;  # handled the trait now
                            }

                            # is Foo[Bar]
                            elsif $elems == 2 && $trait.args[1] -> $params {
                                if nqp::istype($params,$world.find_single_symbol_in_setting('List')) {
                                    $is_type := check_type(
                                      $_, $is_type,
                                      $type.HOW.parameterize(
                                        $type, |$params.FLATTENABLE_LIST)
                                    );
                                    next;  # handled the trait now
                                }
                            }
                        }
                    }
                }
                nqp::push(@late_traits, $_);
            }
        }

        my $scope := $*SCOPE;
        if $scope eq 'has' {
            # Ensure current package can take attributes.
            unless nqp::can($package.HOW, 'add_attribute') {
                if $*PKGDECL {
                    $world.throw($/, ['X', 'Attribute', 'Package'],
                        package-kind => $*PKGDECL,
                        :$name,
                    );
                } else {
                    $world.throw($/, ['X', 'Attribute', 'NoPackage'], :$name);
                }
            }

            # Create container descriptor and decide on any default value.
            if $desigilname eq '' {
                $/.panic("Cannot declare an anonymous attribute");
            }
            my $attrname   := ~$sigil ~ '!' ~ $desigilname;
            my %cont_info  := $world.container_type_info($/, $sigil,
                nqp::isnull($of_type) ?? [] !! [$of_type],
                nqp::isnull($is_type) ?? [] !! [$is_type],
                $shape, :@post);
            my $descriptor := $world.create_container_descriptor(
              %cont_info, $attrname, %cont_info);

            # Create meta-attribute and add it.
            my $metaattr := $world.resolve_mo($/, $*PKGDECL ~ '-attr');
            my %config := hash(
                name => $attrname,
                has_accessor => $twigil eq '.',
                container_descriptor => $descriptor,
                type => %cont_info,
                package => $world.find_single_symbol('$?CLASS'));
            if %cont_info {
                %config := $*W.create_thunk($/,
                    %cont_info);
            }
            my $attr := $world.pkg_add_attribute($/, $package, $metaattr,
                %config, %cont_info, $descriptor);

            # Document it
            Perl6::Pod::document($/, $attr, $*POD_BLOCK, :leading);
            if ~$*POD_BLOCK ne '' {
                $*POD_BLOCK.set_docee($attr);
            }

            # Set it up for trailing declarations
            $*PRECEDING_DECL := $attr;

            # If no twigil, note $foo is an alias to $!foo.
            if $twigil eq '' {
                $BLOCK.symbol($name, :attr_alias($attrname));
            }

            # Apply any traits.
            $world.apply_traits(@late_traits, $attr);

            # Nothing to emit here; hand back a Nil.
            $past := QAST::WVal.new( :value($world.find_single_symbol_in_setting('Nil')) );
            $past.annotate('metaattr', $attr);
        }
        elsif $scope eq 'my' || $scope eq 'our' || $scope eq 'state' {
            # Some things can't be done to our vars.
            my $varname;
            if $scope eq 'our' {
                if !nqp::isnull($of_type) || @post {
                    $/.panic("Cannot put a type constraint on an 'our'-scoped variable");
                }
                elsif $shape {
                    $/.panic("Cannot put a shape on an 'our'-scoped variable");
                }
                elsif $desigilname eq '' {
                    $/.panic("Cannot have an anonymous 'our'-scoped variable");
                }
                if nqp::can($package.HOW, 'archetypes') && $package.HOW.archetypes.parametric {
                    $world.throw($/, 'X::Declaration::OurScopeInRole',
                        declaration => 'variable'
                    );
                }
            }
            elsif $desigilname eq '' {
                if $twigil {
                    $/.panic("Cannot have an anonymous variable with a twigil");
                }
                $name    := QAST::Node.unique($sigil ~ 'ANON_VAR_');
                $varname := $sigil;
            }

            # Create a container descriptor. Default to rw and set a
            # type if we have one; a trait may twiddle with that later.
            my %cont_info  := $world.container_type_info($/, $sigil,
                nqp::isnull($of_type) ?? [] !! [$of_type],
                nqp::isnull($is_type) ?? [] !! [$is_type],
                $shape, :@post);
            my $descriptor := $world.create_container_descriptor(
              %cont_info, $varname || $name, %cont_info);

            # Install the container.
            my $cont := $world.install_lexical_container($BLOCK, $name, %cont_info, $descriptor,
                :scope($scope), :package($package), :init_removal($past));

            # Set scope and type on container, and if needed emit code to
            # reify a generic type or create a fresh container.
            if nqp::istype($past, QAST::Var) {
                my $bind_type := %cont_info;
                $past.name($name);
                $past.returns($bind_type);
                $past.scope(nqp::objprimspec($bind_type) ?? 'lexicalref' !! 'lexical');
                if %cont_info.HOW.archetypes(%cont_info).generic {
                    $past := QAST::Op.new(
                        :op('callmethod'), :name('instantiate_generic'),
                        QAST::Op.new( :op('p6var'), $past ),
                        QAST::Op.new( :op('curlexpad') ));
                }
                elsif %cont_info {
                    if $scope eq 'state' {
                        $past := QAST::Op.new( :op('if'),
                            QAST::Op.new( :op('p6stateinit') ),
                            QAST::Op.new( :op('bind'), $past, %cont_info ),
                            $past);
                    }
                    else {
                        $past := QAST::Op.new( :op('bind'), $past, %cont_info );
                    }
                }

                if $scope eq 'our' {
                    $BLOCK[0].push(QAST::Op.new(
                        :op('bind'),
                        $past,
                        $world.symbol_lookup([$name], $/, :package_only(1), :lvalue(1))
                    ));
                }
            }

            # Twigil handling.
            if $twigil eq '.' {
                add_lexical_accessor($/, $past, $desigilname, $*W.cur_lexpad());
                $name := $sigil ~ $desigilname;
            }
            elsif $twigil eq '!' {
                $world.throw($/, ['X', 'Syntax', 'Variable', 'Twigil'],
                    twigil => $twigil,
                    scope  => $scope,
                );
            }

            # Apply any traits.
            if @late_traits {
                my $Variable := $world.find_single_symbol_in_setting('Variable');
                my $varvar   := nqp::create($Variable);
                nqp::bindattr_s($varvar, $Variable, '$!name', $name);
                nqp::bindattr_s($varvar, $Variable, '$!scope', $scope);
                nqp::bindattr($varvar, $Variable, '$!var', $cont);
                nqp::bindattr($varvar, $Variable, '$!block', $*CODE_OBJECT);
                nqp::bindattr($varvar, $Variable, '$!slash', $/);
                nqp::assign(
                    nqp::getattr($varvar, $Variable, '$!implicit-lexical-usage'),
                    $world.find_symbol(['Bool', 'True']));
                $world.apply_traits(@late_traits, $varvar);
                if $varvar.implicit-lexical-usage {
                    $world.mark_lexical_used_implicitly($BLOCK, $name);
                }
            }
        }
        elsif $scope eq '' {
            $world.throw($/, 'X::Declaration::Scope',
                    scope       => '(unknown scope)',
                    declaration => 'variable',
            );
        }
        else {
            $world.throw($/, 'X::Comp::NYI',
                feature => "$scope scoped variables");
        }

        $past
    }

    sub add_lexical_accessor($/, $var_past, $meth_name, $install_in) {
        my $world := $*W;
        # Generate and install code block for accessor.
        my $a_past := $world.push_lexpad($/);
        $a_past.name($meth_name);
        $a_past.blocktype('declaration_static');
        $a_past.push($var_past);
        $world.pop_lexpad();
        $install_in[0].push($a_past);

        # Produce a code object and install it.
        my $invocant_type := $world.find_single_symbol($world.is_lexical('$?CLASS') ?? '$?CLASS' !! 'Mu');
        my %sig_info := hash(parameters => []);
        my $signature := $world.create_signature_and_params($/, %sig_info, $a_past, 'Any',
            :method, :$invocant_type);
        my $code := methodize_block($/, $world.stub_code_object('Method'),
            $a_past, $signature, %sig_info);
        $world.add_phasers_handling_code($code, $a_past);

        install_method($/, $meth_name, 'has', $code, $install_in, :gen-accessor);
    }

    method routine_declarator:sym($/) { make $.ast; }
    method routine_declarator:sym($/) { make $.ast; }
    method routine_declarator:sym($/) { make $.ast; }

    sub decontrv_op() {
        (my $comp := nqp::getcomp('Raku')).language_revision < 2 && $comp.backend.name eq 'moar'
            ?? 'p6decontrv_6c'
            !! 'p6decontrv'
    }

    method routine_def($/) {
        my $world := $*W;
        my $block;

        if $ {
            $block := $.ast;
        }
        else {
            if $ {
                $block := WANTED($.ast,'&defoid');
            } else {
                $block := $*CURPAD;
                $block.blocktype('declaration_static');
                $block.push(WANTED($.ast,'&def'));
                $block.node($/);
            }
            if $*MAY_USE_RETURN {
                $block[1] := wrap_return_handler($block[1]);
            }
            else {
                $block[1] := QAST::Op.new(
                    :op(decontrv_op()),
                    QAST::WVal.new( :value($*DECLARAND) ),
                    $block[1]);
                $block[1] := wrap_return_type_check($block[1], $*DECLARAND);
            }
        }
        $block.blocktype('declaration_static');

        # Attach signature, building placeholder if needed.
        my @params;
        my $signature;
        if $*SIG_OBJ {
            if $block.ann('placeholder_sig') {
                $world.throw($/, ['X', 'Signature', 'Placeholder'],
                    precursor => '1',
                    placeholder => $block.ann('placeholder_sig')[0],
                );
            }
            @params    := %*SIG_INFO;
            $signature := $*SIG_OBJ;
        }
        else {
            @params := $block.ann('placeholder_sig') || [];
            $signature := $world.create_signature_and_params($/,
                nqp::hash('parameters', @params), $block, 'Any');
        }
        add_signature_binding_code($block, $signature, @params, :multi($*MULTINESS ne ''));

        # Needs a slot that can hold a (potentially unvivified) dispatcher;
        # if this is a multi then we'll need it to vivify to a MultiDispatcher.













        # If it's a proto but not an onlystar, need some variables for the
        # {*} implementation to use (except on MoarVM, which relies on the
        # MoarVM dispatcher mechanism).















        # Set name if we have one
        if $ {
            my $name := ~$.ast;
            $block.name($name);

            # Check for 'is rw' parameters if MAIN
            if $name eq 'MAIN' {
                for $signature.params.FLATTENABLE_LIST -> $param {
                    $/.worry("'is rw' on parameters of 'sub MAIN' usually cannot be satisfied.\nDid you mean 'is copy'?")
                      if $param.rw;
                }
            }
        }

        # Finish code object, associating it with the routine body.
        my $declarand := $*DECLARAND;
        $world.attach_signature($declarand, $signature);
        $world.finish_code_object($declarand, $block, $*MULTINESS eq 'proto', :yada(is_yada($/)));

        # attach return type
        if $*OFTYPE {
            my $sig := $declarand.signature;
            if $sig.has_returns {
                my $prev_returns := $sig.returns;
                $world.throw($*OFTYPE, 'X::Redeclaration',
                    what    => 'return type for',
                    symbol  => $declarand.name,
                    postfix => "(previous return type was "
                                ~ $prev_returns.HOW.name($prev_returns)
                                ~ ')',
                );
            }
            $sig.set_returns($*OFTYPE.ast);
        }
        # and mixin the parameterize callable for type checks
        if $signature.has_returns {
            my $callable := $world.find_single_symbol_in_setting('Callable');
            $declarand.HOW.mixin($declarand, $callable.HOW.parameterize($callable, $signature.returns));
        }

        # Document it
        Perl6::Pod::document($/, $declarand, $*POD_BLOCK, :leading);
        if ~$*POD_BLOCK ne '' {
            $*POD_BLOCK.set_docee($declarand);
        }

        # Install PAST block so that it gets capture_lex'd correctly and also
        # install it in the lexpad.
        my $outer := $world.cur_lexpad();
        my $clone := !($outer =:= $*UNIT);

        $world.push_inner_block(QAST::Stmt.new($block));

        my $scope := $*SCOPE;
        if $ {
            # If it's a multi, need to associate it with the surrounding
            # proto.
            # XXX Also need to auto-multi things with a proto in scope.
            my $name := '&' ~ ~$.ast;
            if $*MULTINESS eq 'multi' {
                # Do we have a proto in the current scope?
                my $proto;
                if $outer.symbol($name) {
                    $proto := $world.force_value($outer.symbol($name), $name, 0);
                }
                else {
                    unless $scope eq '' || $scope eq 'my' {
                        $world.throw($/, 'X::Declaration::Scope::Multi',
                            scope       => $*SCOPE,
                            declaration => 'multi',
                        );
                    }
                    # None; search outer scopes.
                    my $new_proto;
                    try {
                        $proto := $world.find_single_symbol($name);
                    }
                    if $proto && $proto.is_dispatcher {
                        # Found in outer scope. Need to derive.
                        $new_proto := $world.derive_dispatcher($proto);
                    }
                    else {
                        $new_proto := self.autogenerate_proto($/, $block.name, $outer[0]);
                    }

                    # Install in current scope.
                    $world.install_lexical_symbol($outer, $name, $new_proto, :$clone);
                    $proto := $new_proto;
                }

                # Ensure it's actually a dispatcher.
                unless nqp::can($proto, 'is_dispatcher') && $proto.is_dispatcher {
                    $world.throw($/, ['X', 'Redeclaration'],
                        what    => 'routine',
                        symbol  => ~$.ast,
                    );
                }

                # Install the candidate.
                $world.add_dispatchee_to_proto($proto, $declarand);
            }
            elsif $scope eq 'anon' {
                # don't install anything
            }
            else {
                # Always install in lexpad unless predeclared.
                my $predeclared := $outer.symbol($name);
                if $predeclared {
                    my $Routine := $world.find_single_symbol_in_setting('Routine');
                    unless nqp::istype($predeclared, $Routine)
                        && $predeclared.yada {
                        $world.throw($/, ['X', 'Redeclaration'],
                                symbol => ~$.ast,
                                what   => 'routine',
                        );
                    }
                }
                $world.install_lexical_symbol($outer, $name, $declarand, :$clone);

                if $scope eq 'our' || $scope eq 'unit' {
                    # Also install in package, and set up code to
                    # re-bind it per invocation of its outer.
                    $world.install_lexical_symbol($outer, $name, $declarand, :$clone);
                    my $package := $/.package;
                    if nqp::existskey($package.WHO, $name) {
                        $world.throw($/, ['X', 'Redeclaration'],
                            symbol  => ~$.ast,
                            what    => 'routine',
                            postfix => '(already defined in package ' ~ $package.HOW.name($package) ~ ')'
                        );
                    }
                    $world.install_package_symbol($/, $package, $name, $declarand, 'sub');
                    $outer[0].push(QAST::Op.new(
                        :op('bindkey'),
                        QAST::Op.new( :op('who'), QAST::WVal.new( :value($package) ) ),
                        QAST::SVal.new( :value($name) ),
                        QAST::Var.new( :name($name), :scope('lexical') )
                    ));
                }
                elsif $scope ne '' && $scope ne 'my' {
                    $world.throw($/, 'X::Declaration::Scope',
                        scope       => $scope,
                        declaration => 'sub',
                    );
                }
            }
        }
        elsif $*MULTINESS {
            $world.throw($/, 'X::Anon::Multi', multiness => $*MULTINESS);
        }

        # Apply traits.
        $world.apply_traits($, $declarand);
        if $ {
            # Protect with try; won't work when declaring the initial
            # trait_mod proto in CORE.setting!
            try $world.apply_trait($/, '&trait_mod:', $*DECLARAND, :onlystar(1));
        }

        # Handle any phasers.
        $world.add_phasers_handling_code($declarand, $block);

        # Add inlining information if it's inlinable; also mark soft if the
        # appropriate pragma is in effect.
        if $ {
            if $/.pragma('soft') {
                $world.find_single_symbol_in_setting('&infix:')($declarand, $world.find_single_symbol_in_setting('SoftRoutine'));
            }
            else {
                self.maybe_add_inlining_info($/, $declarand, $signature, $block, @params);
            }
        }

        # If it's a proto, add it to the sort-at-CHECK-time queue.
        if $*MULTINESS eq 'proto' {
            $world.add_proto_to_sort($declarand);
        }

        make block_closure(
            reference_to_code_object($declarand, $block)
        ).annotate_self('sink_ast', QAST::Op.new( :op('null') ));
    }

    method autogenerate_proto($/, $name, $install_in) {
        my $world := $*W;
        my $p_past := $world.push_lexpad($/);
        $p_past.name(~$name);
        $p_past.is_thunk(1);

        $p_past.push(QAST::Op.new(
            :op('dispatch'),
            QAST::SVal.new( :value('boot-resume') ),
            QAST::IVal.new( :value(nqp::const::DISP_ONLYSTAR) )));
























        $world.pop_lexpad();
        $install_in.push(QAST::Stmt.new($p_past));
        my @p_params := [hash(is_capture => 1, type => $world.find_single_symbol_in_setting('Mu') )];
        my $p_sig := $world.create_signature(nqp::hash('parameter_objects',
            [$world.create_parameter($/, @p_params[0])]));
        add_signature_binding_code($p_past, $p_sig, @p_params);
        my $code := $world.create_code_object($p_past, 'Sub', $p_sig, 1);
        $world.apply_trait($/, '&trait_mod:', $code, :onlystar(1));
        $world.add_proto_to_sort($code);
        $code
    }

    # There are two kinds of inlining that happen: that done by the virtual
    # machine we run on, and that done by Rakudo. The VM can do it based on
    # discovered hot paths, and in far more situations than we can here. At
    # the same time, we can generate much better code as a starting point
    # for the VM if we inline various operators on natively typed things. So,
    # that is our distinction: if all of the arguments of the sub are native
    # types, we figure there's a good chance it's a high-value inline and we
    # can try to do it at compile time, so we generate inlining info here. In
    # any other cases, we will not.
    my $SIG_ELEM_IS_COPY := 512;
    method maybe_add_inlining_info($/, $code, $sig, $past, @params) {
        # Cannot inline things with custom invocation handler or phasers.
        return 0 if nqp::can($code, 'CALL-ME');

        return 0 if nqp::isconcrete(nqp::getattr(
          $code, $*W.find_single_symbol_in_setting('Block'), '$!phasers'
        ));

        # Make sure the block has the common structure we expect
        # (decls then statements).
        return 0 unless nqp::elems($past) == 2;

        # Ensure all parameters are native, simple, and build placeholders for
        # them. No parameters also means no inlining.
        return 0 unless @params;

        my $world  := $*W;
        my $Param  := $world.find_single_symbol_in_setting('Parameter');
        my @p_objs := nqp::getattr($sig, $world.find_single_symbol_in_setting('Signature'), '@!params');
        my %arg_placeholders;
        my int $n  := nqp::elems(@params);
        my int $i  := -1;
        while ++$i < $n {
            my %info := @params[$i];
            return 0 unless nqp::objprimspec(%info); # non-native
            return 0 if %info || %info ||  %info ||
                %info || %info || %info;
            my $param_obj := @p_objs[$i];
            my int $flags := nqp::getattr_i($param_obj, $Param, '$!flags');
            return 0 if $flags +& $SIG_ELEM_IS_COPY;
            %arg_placeholders{%info} :=
                QAST::InlinePlaceholder.new( :position($i) );
        }

        # Ensure nothing extra is declared and there are no inner blocks.
        for @($past[0]) {
            if nqp::istype($_, QAST::Var) && $_.scope eq 'lexical' {
                my $name := $_.name;
                return 0 if $name ne '$*DISPATCHER' && $name ne '$_' &&
                    $name ne '$/' && $name ne '$¢' && $name ne '$!' &&
                    !nqp::existskey(%arg_placeholders, $name);
            }
            elsif nqp::istype($_, QAST::Block) {
                return 0;
            }
            elsif nqp::istype($_, QAST::Stmt) && nqp::istype($_[0], QAST::Block) {
                return 0;
            }
        }

        # If all is well, we try to build the QAST for inlining. This dies
        # if we fail.
        my $PseudoStash;
        try $PseudoStash := $world.find_single_symbol_in_setting('PseudoStash');
        sub clear_node($qast) {
            $qast.node(nqp::null());
            $qast.clear_annotations();
            $qast
        }
        sub node_walker($node) {
            # Simple values are always fine; just return them as they are, modulo
            # removing any :node(...).
            if nqp::istype($node, QAST::IVal) || nqp::istype($node, QAST::SVal)
            || nqp::istype($node, QAST::NVal) {
                return $node.node ?? clear_node($node.shallow_clone()) !! $node;
            }

            # WVal is OK, though special case for PseudoStash usage (which means
            # we are doing funny lookup stuff).
            elsif nqp::istype($node, QAST::WVal) {
                if $node.value =:= $PseudoStash {
                    nqp::die("Routines using pseudo-stashes are not inlinable");
                }
                else {
                    return $node.node ?? clear_node($node.shallow_clone()) !! $node;
                }
            }

            # Operations need checking for their inlinability. If they are OK in
            # themselves, it comes down to the children.
            elsif nqp::istype($node, QAST::Op) {
                if nqp::getcomp('QAST').operations.is_inlinable('Raku', $node.op) {
                    my $replacement := $node.shallow_clone();
                    my int $n := nqp::elems($node);
                    my int $i := -1;
                    while ++$i < $n {
                        $replacement[$i] := node_walker($node[$i]);
                    }
                    return clear_node($replacement);
                }
                else {
                    nqp::die("Non-inlinable op '" ~ $node.op ~ "' encountered");
                }
            }

            # Variables are fine *if* they are arguments.
            elsif nqp::istype($node, QAST::Var) && ($node.scope eq 'lexical' || $node.scope eq '') {
                if nqp::existskey(%arg_placeholders, $node.name) {
                    my $replacement := %arg_placeholders{$node.name};
                    if $node.named || $node.flat {
                        $replacement := $replacement.shallow_clone();
                        if $node.named { $replacement.named($node.named) }
                        if $node.flat { $replacement.flat($node.flat) }
                    }
                    return $replacement;
                }
                else {
                    nqp::die("Cannot inline with non-argument variables");
                }
            }

            # Statements need to be cloned and then each of the nodes below them
            # visited.
            elsif nqp::istype($node, QAST::Stmt) || nqp::istype($node, QAST::Stmts) {
                my $replacement := $node.shallow_clone();
                my int $n := nqp::elems($node);
                my int $i := -1;
                while ++$i < $n {
                    $replacement[$i] := node_walker($node[$i]);
                }
                return clear_node($replacement);
            }

            # Want nodes need copying and every other child visiting.
            elsif nqp::istype($node, QAST::Want) {
                my $replacement := $node.shallow_clone();
                my int $i := 0;
                my int $n := nqp::elems($node);
                while $i < $n {
                    $replacement[$i] := node_walker($node[$i]);
                    $i := $i + 2;
                }
                return clear_node($replacement);
            }

            # Otherwise, we don't know what to do with it.
            else {
                nqp::die("Unhandled node type; won't inline");
            }
        };
        my $inline_info;
        try $inline_info := node_walker($past[1]);
        return 0 unless nqp::istype($inline_info, QAST::Node);

        # Attach inlining information.
        $world.apply_trait($/, '&trait_mod:', $code, inlinable => $inline_info)
    }

    method method_def($/) {
        my $world := $*W;
        my $past;
        if $ {
            $past := $.ast;
        }
        else {
            $past := WANTED($.ast,'method_def');
            if $*MAY_USE_RETURN {
                $past[1] := wrap_return_handler($past[1]);
            }
            else {
                $past[1] := QAST::Op.new(
                    :op(decontrv_op()),
                    QAST::WVal.new( :value($*DECLARAND) ),
                    $past[1]);
                $past[1] := wrap_return_type_check($past[1], $*DECLARAND);
            }
        }
        $past.blocktype('declaration_static');

        my $name;
        if $ -> $ln {
            if $ln {
                $name := ~$ln;
                for $ln {
                    my $key := ~($_ || '');
                    if $_ -> $cf {
                        if $cf -> $op_name {
                            $name := $name ~ $world.canonicalize_pair($key, $world.colonpair_nibble_to_str(
                                $ln, $op_name // $op_name // $op_name));
                        }
                        else {
                            $name := $name ~ ':' ~ $key;
                        }
                    }
                    else {
                        $name := $name ~ ':' ~ $key;
                    }
                }
            }
            else {
                my $longname := $world.dissect_longname($);
                $name := $longname.name(:dba('method name'), :decl);
            }
        }
        elsif $ {
            my $sigil := ~$;
               if $sigil eq '@' { $name := 'postcircumfix:<[ ]>' }
            elsif $sigil eq '%' { $name := 'postcircumfix:<{ }>' }
            elsif $sigil eq '&' { $name := 'postcircumfix:<( )>' }
            else {
                $/.PRECURSOR.panic("Cannot use $sigil sigil as a method name");
            }
        }
        $past.name($name ?? $name !! '');

        if $past.ann('placeholder_sig') {
            my $placeholders := nqp::iterator($past.ann('placeholder_sig'));
            my @non-placeholder-names;
            my $method-name := $past.name;
            while $placeholders {
                my $placeholder := nqp::shift($placeholders);
                my $name := $placeholder;
                my $non-placeholder-name;
                if $placeholder || $placeholder {
                    $non-placeholder-name := nqp::concat('*', $name);
                } elsif $placeholder {
                    $non-placeholder-name := nqp::concat(':', nqp::concat(nqp::substr($name, 0, 1), nqp::substr($name, 2)));
                } else {
                    $non-placeholder-name := nqp::concat(nqp::substr($name, 0, 1), nqp::substr($name, 2));
                }
                nqp::push( @non-placeholder-names, $non-placeholder-name);
            }

            my $non-placeholder-names := nqp::join(', ', @non-placeholder-names);

            my $first-placeholder := $past.ann('placeholder_sig')[0];
            my $first-placeholder-name := $first-placeholder;

            $first-placeholder.PRECURSOR.panic("Placeholder variables (eg. $first-placeholder-name) cannot be used in a method.\nPlease specify an explicit signature, like $*METHODTYPE $method-name ($non-placeholder-names) \{ ... \}");
        }

        my $code := methodize_block($/, $*DECLARAND, $past, $*SIG_OBJ,
            %*SIG_INFO, :yada(is_yada($/)));

        # If it's a proto but not an onlystar, need some variables for the
        # {*} implementation to use on non-MoarVM.

















        # attach return type
        if $*OFTYPE {
            my $sig := $code.signature;
            if $sig.has_returns {
                my $prev_returns := $sig.returns;
                $world.throw($*OFTYPE, 'X::Redeclaration',
                    what    => 'return type for',
                    symbol  => $code.name,
                    postfix => "(previous return type was "
                                ~ $prev_returns.HOW.name($prev_returns)
                                ~ ')',
                );
            }
            $sig.set_returns($*OFTYPE.ast);
        }


        # Document it
        Perl6::Pod::document($/, $code, $*POD_BLOCK, :leading);
        if ~$*POD_BLOCK ne '' {
            $*POD_BLOCK.set_docee($code);
        }

        # Install PAST block so that it gets capture_lex'd correctly.
        my $outer := $world.cur_lexpad();
        $outer[0].push($past);

        # Apply traits.
        $world.apply_traits($, $code);
        if $ {
            $world.apply_trait($/, '&trait_mod:', $*DECLARAND, :onlystar(1));
        }
        $world.add_phasers_handling_code($code, $past);

        # Install method.
        if $name {
            my $meta := $ && ~$ eq '^';
            install_method($/, $name, $*SCOPE, $code, $outer, :$meta,
                :private($ && ~$ eq '!'));
        }
        elsif $*MULTINESS {
            $world.throw($/, 'X::Anon::Multi',
                multiness       => $*MULTINESS,
                routine-type    => 'method',
            );
        }

        # If it's a proto, add it to the sort-at-CHECK-time queue.
        if $*MULTINESS eq 'proto' {
            $world.add_proto_to_sort($code);
        }

        make block_closure(
            reference_to_code_object($code, $past)
        ).annotate_self('sink_ast', QAST::Op.new( :op('null') ))
    }

    method macro_def($/) {
        my $world := $*W;
        my $block;

        $block := $.ast;
        $block.blocktype('declaration_static');
        $block[1] := wrap_return_handler($block[1]);

        # Obtain parameters, create signature object and generate code to
        # call binder.
        if $block.ann('placeholder_sig') && $ {
            $world.throw($/, 'X::Signature::Placeholder',
                precursor => '1',
                placeholder => $block.ann('placeholder_sig')[0],
            );
        }
        my %sig_info;
        if $ {
            %sig_info := $.ast;
        }
        else {
            %sig_info := $block.ann('placeholder_sig') ?? $block.ann('placeholder_sig') !!
                                                                [];
        }
        my @params := %sig_info;
        my $signature := $world.create_signature_and_params($ ?? $ !! $/,
            %sig_info, $block, 'Any');
        add_signature_binding_code($block, $signature, @params, :multi($*MULTINESS ne ''));

        # Finish code object, associating it with the routine body.
        if $ {
            $block.name(~$.ast);
        }
        my $declarand := $*DECLARAND;
        $world.attach_signature($declarand, $signature);
        $world.finish_code_object($declarand, $block, $*MULTINESS eq 'proto');

        # Document it
        Perl6::Pod::document($/, $declarand, $*POD_BLOCK, :leading);

        # Install PAST block so that it gets capture_lex'd correctly and also
        # install it in the lexpad.
        my $outer := $world.cur_lexpad();
        $outer[0].push(QAST::Stmt.new($block));

        if $ {
            my $name := '&' ~ ~$.ast;
            # Install.
            if $outer.symbol($name) {
                $/.PRECURSOR.panic("Illegal redeclaration of macro '" ~
                    ~$.ast ~ "'");
            }
            my $scope := $*SCOPE;
            if $scope eq '' || $scope eq 'my' {
                $world.install_lexical_symbol($outer, $name, $declarand);
            }
            elsif $scope eq 'our' {
                # Install in lexpad and in package, and set up code to
                # re-bind it per invocation of its outer.
                $world.install_lexical_symbol($outer, $name, $declarand);
                $world.install_package_symbol($/, $/.package, $name, $declarand, 'macro');
                $outer[0].push(QAST::Op.new(
                    :op('bind'),
                    $world.symbol_lookup([$name], $/, :package_only(1)),
                    QAST::Var.new( :name($name), :scope('lexical') )
                ));
            }
            else {
                $/.PRECURSOR.panic("Cannot use '$scope' scope with a macro");
            }
        }
        elsif $*MULTINESS {
            $/.PRECURSOR.panic('Cannot put ' ~ $*MULTINESS ~ ' on anonymous macro');
        }

        # Apply traits.
        $world.apply_traits($, $declarand);
        $world.add_phasers_handling_code($declarand, $block);

        make block_closure(
            reference_to_code_object($declarand, $block)
        ).annotate_self('sink_ast', QAST::Op.new( :op('null') ))
    }

    sub methodize_block($/, $code, $past, $signature, %sig_info, :$yada) {
        my $world := $*W;
        my $*LEAF := $/;
        # Add signature binding code.
        add_signature_binding_code($past, $signature, %sig_info);

        # Place to store invocant.
        $past[0].unshift(QAST::Var.new( :name('self'), :scope('lexical'), :decl('var') ));
        $past.symbol('self', :scope('lexical'));

        # Needs a slot to hold a multi or method dispatcher.









        # Finish up code object.
        $world.attach_signature($code, $signature);
        $world.finish_code_object($code, $past, $*MULTINESS eq 'proto', :yada($yada));

        $code
    }

    # Installs a method into the various places it needs to go.
    sub install_method($/, $name, $scope, $code, $outer, :$private, :$meta, :$gen-accessor) {
        my $world := $*W;
        my $meta_meth;
        my $package := $/.package;
        if $private {
            if $*MULTINESS { $/.PRECURSOR.panic("Private multi-methods are not supported"); }
            $meta_meth := 'add_private_method';
        }
        elsif $meta {
            if $*MULTINESS { $/.PRECURSOR.panic("Meta multi-methods are not supported"); }
            $meta_meth := 'add_meta_method';
        }
        else {
            $meta_meth := $*MULTINESS eq 'multi' ?? 'add_multi_method' !! 'add_method';
        }
        if $scope eq '' || $scope eq 'has' {
            # Ensure that current package supports methods, and if so
            # add the method.
            if nqp::can($package.HOW, $meta_meth) {
                $world.pkg_add_method($/, $package, $meta_meth, $name, $code);
            }
            elsif $gen-accessor {
                $/.PRECURSOR.worry("Useless generation of accessor method in " ~
                    ($*PKGDECL || "mainline"));
            }
            else {
                my $nocando := $*MULTINESS eq 'multi' ?? 'multi-method' !! 'method';
                $/.PRECURSOR.worry(
                    "Useless declaration of a has-scoped $nocando in " ~
                    ($*PKGDECL || "mainline") ~ " (did you mean 'my $*METHODTYPE $name'?)");
            }
        }
        elsif $scope eq 'my' {
            my $mang-name := '&' ~ $name;
            if $outer.symbol($mang-name) {
                $world.throw($/, ['X', 'Redeclaration'], symbol => $name, what => 'method');
            }
            $world.install_lexical_symbol($outer, $mang-name, $code, :clone(1));
        }
        elsif $scope eq 'our' {
            my $mang-name := '&' ~ $name;
            if $outer.symbol($mang-name) {
                $world.throw($/, ['X', 'Redeclaration'], symbol => $name, what => 'method');
            }
            $world.install_lexical_symbol($outer, $mang-name, $code, :clone(1));
            if nqp::existskey($package.WHO, $name) {
                $world.throw($/, ['X', 'Redeclaration'],
                    symbol  => $name,
                    what    => 'method',
                    postfix => '(already defined in package ' ~ $package.HOW.name($package) ~ ')'
                );
            }
            $world.install_package_symbol($/, $package, '&' ~ $name, $code, 'method');
        }
    }

    sub is_yada($/) {
        $
          && nqp::elems($) == 1
          && ~$[0]
               ~~ /^ \s* ['...'|'???'|'!!!'|'…'] \s* $/;
    }

    method onlystar($/) {
        my $BLOCK       := $*CURPAD;
        my @pad_entries := $BLOCK[0];

        # Remove special variables; no need for them in onlystar.
        my int $n := +@pad_entries;
        my int $i := -1;
        my $null  := QAST::Op.new(:op('null'));
        while ++$i < $n {
            @pad_entries[$i] := $null
              if nqp::istype((my $consider := @pad_entries[$i]), QAST::Var)
              && ((my $name := $consider.name) eq '$_'
                     || $name eq '$/'
                     || $name eq '$!'
                     || $name eq '$¢'
                 )
        }

        # Add dispatching code.

        $BLOCK.push(QAST::Op.new(
            :op('dispatch'),
            QAST::SVal.new( :value('boot-resume') ),
            QAST::IVal.new( :value(nqp::const::DISP_ONLYSTAR) )));
























        $BLOCK.node($/);
        $BLOCK.is_thunk(1);
        make $BLOCK;
    }

    method regex_declarator:sym($/, $key?) {
        make $.ast;
    }

    method regex_declarator:sym($/, $key?) {
        make $.ast;
    }

    method regex_declarator:sym($/, $key?) {
        make $.ast;
    }

    method regex_def($/) {
        my $coderef;
        my $name := ~%*RX;
        $*CURPAD.blocktype('declaration_static');
        my %sig_info := $ ?? $[0].ast !! hash(parameters => []);

        my $declarand := $*DECLARAND;
        if $*MULTINESS eq 'proto' {
            unless $ {
                $/.PRECURSOR.panic("Proto regex body must be \{*\} (or <*> or <...>, which are deprecated)");
            }
            my $proto_body := QAST::Op.new(
                :op('callmethod'), :name('!protoregex'),
                QAST::Var.new( :name('self'), :scope('local') ),
                QAST::SVal.new( :value($name) ));
            $coderef := regex_coderef($/, $declarand, $proto_body, $*SCOPE, $name, %sig_info, $*CURPAD, $, :proto(1));
        } elsif $.ast {
            $coderef := regex_coderef($/, $declarand, $.ast, $*SCOPE, $name, %sig_info, $*CURPAD, $);
        }
        else {
            $/.typed_panic("X::Syntax::Regex::NullRegex");
        }

        # Document it
        Perl6::Pod::document($/, $declarand, $*POD_BLOCK, :leading);
        if ~$*POD_BLOCK ne '' {
            $*POD_BLOCK.set_docee($declarand);
        }

        # Return closure if not in sink context.
        make block_closure($coderef, :regex).annotate_self(
            'sink_ast', QAST::Op.new( :op('null') ))
    }

    sub regex_coderef($/, $code, $qast, $scope, $name, %sig_info, $block, $traits?, :$proto) {
        my $world := $*W;
        # Regexes can't have place-holder signatures.
        if $qast.ann('placeholder_sig') {
            $/.PRECURSOR.panic('Placeholder variables cannot be used in a regex');
        }

        # Create a code reference from a regex qast tree
        my $past;
        if $proto {
            $block[1] := $qast;
            $past := $block;
        }
        else {
            $world.install_lexical_magical($block, '$¢');
            $world.install_lexical_magical($block, '$/');
            $past := %*RX
                ?? $/.slang_actions('P5Regex').qbuildsub($qast, $block, code_obj => $code)
                !! $/.slang_actions('Regex').qbuildsub($qast, $block, code_obj => $code);
        }
        $past.name($name);
        $past.annotate_self('statement_id', $*STATEMENT_ID
            ).annotate_self( 'in_stmt_mod', $*IN_STMT_MOD,
            ).annotate_self(       'outer', $world.cur_lexpad
            ).blocktype("declaration_static");

        # Install a $?REGEX (mostly for the benefit of <~~>).
        $block[0].push(QAST::Op.new(
            :op('bind'),
            QAST::Var.new(:name<$?REGEX>, :scope, :decl('var')),
            QAST::Op.new(
                :op('getcodeobj'),
                QAST::Op.new( :op('curcode') )
            )));
        $block.symbol('$?REGEX', :scope);

        # Do the various tasks to turn the block into a method code object.
        my $invocant_type := $world.find_single_symbol( # XXX Maybe Cursor below, not Mu...
            $name && $*SCOPE ne 'my' && $*SCOPE ne 'our' && $world.is_lexical('$?CLASS') ?? '$?CLASS' !! 'Mu');
        my $signature := $world.create_signature_and_params($/, %sig_info, $past, 'Any',
            :method, :$invocant_type);
        methodize_block($/, $code, $past, $signature, %sig_info);
        $world.add_phasers_handling_code($code, $past);

        # Need to put self into a register for the regex engine.
        $past[0].push(QAST::Op.new(
            :op('bind'),
            QAST::Var.new( :name('self'), :scope('local'), :decl('var') ),
            QAST::Var.new( :name('self'), :scope('lexical') )));

        # Install PAST block so that it gets capture_lex'd correctly.
        my $outer := $world.cur_lexpad();
        find_block_calls_and_migrate($outer, $past, $qast);
        $outer[0].push($past);

        # Apply traits.
        $world.apply_traits($traits, $code) if $traits;

        # Install in needed scopes.
        install_method($/, $name, $scope, $code, $outer) if $name ne '';

        # Bind original source to $!source
        my $Regex  := $world.find_single_symbol_in_setting('Regex');
        my str $source := ($*METHODTYPE ?? $*METHODTYPE ~ ' ' !! '') ~ $/;
        my $match  := $source ~~ /\s+$/;

        if $match {
            $source := nqp::substr($source, 0, $match.from());
        }
        nqp::bindattr_s($code, $Regex, '$!source', $source);

        # Return a reference to the code object
        reference_to_code_object($code, $past);
    }

    method type_declarator:sym($/) {
        my $world := $*W;
        # Get, or find, enumeration base type and create type object with
        # correct base type.
        my $name;
        my @name_parts;
        if $ {
            my $longname := $world.dissect_longname($);
            $name        := $longname.name();
            @name_parts  := $longname.type_name_parts('enum name', :decl(1));
        }
        else {
            $name       := ~($ || '');
        }
        my $package := $/.package;

        my $type_obj;
        my sub make_type_obj($base_type) {
            $type_obj := $world.pkg_create_mo($/, $world.resolve_mo($/, 'enum'), :$name, :$base_type);
            # Add roles (which will provide the enum-related methods).
            $world.apply_trait($/, '&trait_mod:', $type_obj, $world.find_single_symbol('Enumeration'));

            if istype($type_obj, $world.find_single_symbol('Numeric')) {
                $world.apply_trait($/, '&trait_mod:', $type_obj, $world.find_single_symbol(
                    istype($type_obj, $world.find_single_symbol('Stringy')) # handle allomorphs
                        ?? 'NumericStringyEnumeration'
                        !! 'NumericEnumeration'
                ));
            }
            elsif istype($type_obj, $world.find_single_symbol('Stringy')) {
                $world.apply_trait($/, '&trait_mod:', $type_obj,
                  $world.find_single_symbol('StringyEnumeration'));
            }

            # Apply traits, compose and install package.
            $world.apply_traits($, $type_obj);
            $world.pkg_compose($/, $type_obj);
        }
        my $base_type;
        my int $has_base_type := 0;
        if $*OFTYPE {
            $base_type     := $*OFTYPE.ast;
            $has_base_type := 1;
            make_type_obj($base_type);
        }

        if $ {
            $world.throw($/, 'X::Comp::NYI',
                feature => "Variable case of enums",
            );
        }

        # Get list of either values or pairs; fail if we can't.
        my $Pair := $world.find_single_symbol_in_setting('Pair');
        my @values;
        my $term_ast := $.ast;

        # remove val call on a single item
        if nqp::istype($term_ast, QAST::Op) && $term_ast.name eq '&val' {
            $term_ast := $term_ast[0];
        }

        if nqp::istype($term_ast, QAST::Stmts) && +@($term_ast) == 1 {
            $term_ast := $term_ast[0];
        }
        $term_ast := WANTED($term_ast,'enum');
        if $*COMPILING_CORE_SETTING {  # must handle bootstrapping enums here
            if nqp::istype($term_ast, QAST::Op)
            && $term_ast.name eq '&infix:<,>' {
                wantall($term_ast, 'enum');
                for @($term_ast) {
                    my $item_ast := $_;
                    if nqp::istype($item_ast, QAST::Op)
                    && $item_ast.name eq '&val' {
                        $item_ast := $item_ast[0];
                    }

                    if istype($item_ast.returns(), $Pair) && $item_ast[1].has_compile_time_value {
                        @values.push($item_ast);
                    }
                    elsif $item_ast.has_compile_time_value {
                        @values.push($item_ast);
                    }
                    else {
                        @values.push($world.compile_time_evaluate($, $item_ast));
                    }
                }
            }
        }
        # After we finish compiling the setting, everyone uses the same evaluator without special cases.
        unless @values {
            @values := $world.compile_time_evaluate($, $term_ast).List.FLATTENABLE_LIST;
        }

        # Now we have them, we can go about computing the value
        # for each of the keys, unless they have them supplied.
        # XXX Should not assume integers, and should use lexically
        # scoped &postfix:<++> or so.
        my $cur_value := nqp::box_i(-1, $world.find_single_symbol_in_setting('Int'));
        my @redecl;
        my $block := $world.cur_lexpad();
        my $index := -1;
        unless nqp::elems(@values) {
            my $term := $.Str;
            $term := nqp::substr($term,1,nqp::chars($term) - 2);
            $.worry("No values supplied to enum (does $term need to be declared constant?)") if $term ~~ /\w/;
        }
        for @values {
            # If it's a pair, take that as the value; also find
            # key.
            my $cur_key;
            if nqp::istype($_, QAST::Node) && istype($_.returns(), $Pair) {
                $cur_key := $_[1].compile_time_value;
                $cur_value := $world.compile_time_evaluate($, $_[2]);
                if $has_base_type {
                    unless istype($cur_value, $base_type) {
                        $/.panic("Type error in enum. Got '"
                                ~ $cur_value.HOW.name($cur_value)
                                ~ "' Expected: '"
                                ~ $base_type.HOW.name($base_type)
                                ~ "'"
                        );
                    }
                }
                else {
                    $base_type     :=  $cur_value.WHAT;
                    $has_base_type := 1;
                    make_type_obj($base_type);
                }
            }
            else {
                unless $has_base_type {
                    if nqp::istype($_, $Pair) {
                        $base_type := $_.value.WHAT;
                    }
                    else {
                        $base_type := $world.find_single_symbol_in_setting('Int');
                    }
                    make_type_obj($base_type);
                    $has_base_type := 1;
                }

                if nqp::istype($_, QAST::Node) {
                    $cur_key := $world.compile_time_evaluate($, $_);
                    $cur_value := $cur_value.succ();
                }
                elsif nqp::istype($_, $Pair) {
                    $cur_key := $_.key;
                    $cur_value := $_.value;
                }
                else {
                    $cur_key := $_;
                    $cur_value := $cur_value.succ();
                }
            }
            unless nqp::istype($cur_key, $world.find_single_symbol_in_setting('Str')) {
                $cur_key := $cur_key.Str;
            }
            unless nqp::defined($cur_value) {
                $world.throw($/, 'X::Comp::NYI',
                    feature => "Using a type object as a value for an enum",
                );
            }

            # Create and install value.
            my $val_obj := $world.create_enum_value($type_obj, $cur_key, $cur_value, $index := $index + 1);
            $cur_key    := nqp::unbox_s($cur_key);
            $world.install_package_symbol_unchecked($type_obj, $cur_key, $val_obj);
            if $block.symbol($cur_key) {
                nqp::push(@redecl, $cur_key);
                $world.install_lexical_symbol($block, $cur_key,
                    $world.find_single_symbol_in_setting('Failure').new(
                        $world.find_symbol(['X', 'PoisonedAlias'], :setting-only).new(
                            :alias($cur_key), :package-type, :package-name($name)
                        )
                    )
                );
            }
            else {
                $world.install_lexical_symbol($block, $cur_key, $val_obj);
            }
            if $*SCOPE eq '' || $*SCOPE eq 'our' {
                $world.install_package_symbol_unchecked($package, $cur_key, $val_obj);
            }
        }

        if +@redecl -> $amount {
            if $amount > 2 {
                @redecl[$amount - 2] := @redecl[$amount - 2] ~ ' and ' ~ nqp::pop(@redecl);
                $/.typed_worry('X::Redeclaration', symbol => nqp::join(', ', @redecl));
            }
            elsif $amount > 1 {
                $/.typed_worry('X::Redeclaration', symbol => nqp::join(' and ', @redecl));
            }
            else {
                $/.typed_worry('X::Redeclaration', symbol => @redecl[0]);
            }
        }


        # create a type object even for empty enums
        make_type_obj($world.find_single_symbol_in_setting('Int')) unless $has_base_type;

        $world.install_package($/, @name_parts,
            ($*SCOPE || 'our'), 'enum', $package, $block, $type_obj);

        # Compose the added enum values.
        $type_obj.HOW.compose_values($type_obj);

        # Document it
        Perl6::Pod::document($/, $type_obj, $*POD_BLOCK, :leading);
        if ~$*POD_BLOCK ne '' {
            $*POD_BLOCK.set_docee($type_obj);
        }
        # Set it up for trailing declarations
        $*PRECEDING_DECL := $type_obj;

        # We evaluate to the enums values, if we need to.
        make QAST::Op.new(
            :op('call'), :name('&ENUM_VALUES'), $term_ast
        ).annotate_self('sink_ast', QAST::Op.new( :op('null') ))
    }

    method type_declarator:sym($/) {
        my $world := $*W;
        # We refine Any by default; "of" may override.
        my $refinee := $world.find_single_symbol(~($*OFTYPE // 'Any'));

        # If we have a refinement, make sure it's thunked if needed. If none,
        # just always true.
        my $refinement := $ ?? make_where_block($, $.ast) !! nqp::null();

        # Create the meta-object.
        my $subset;
        my $longname := $ && $world.dissect_longname($);
        my @name := $longname ?? $longname.type_name_parts('subset name', :decl(1)) !! [];
        if @name {
            my $target_package := $longname.is_declared_in_global()
                ?? $*GLOBALish
                !! $/.package;
            my $fullname := $longname.fully_qualified_with($target_package);
            $subset := $world.create_subset($world.resolve_mo($/, 'subset'), $refinee, $refinement,
                :name($fullname));
            $world.install_package($/, @name, ($*SCOPE || 'our'), 'subset',
                $target_package, $world.cur_lexpad(), $subset);
        }
        else {
            $subset := $world.create_subset($world.resolve_mo($/, 'subset'), $refinee, $refinement);
        }

        # Apply traits.
        $world.apply_traits($, $subset);

        # Document it
        Perl6::Pod::document($/, $subset, $*POD_BLOCK, :leading);
        if ~$*POD_BLOCK ne '' {
            $*POD_BLOCK.set_docee($subset);
        }
        # Set it up for trailing declarations
        $*PRECEDING_DECL := $subset;

        # We evaluate to the refinement type object.
        make QAST::WVal.new( :value($subset) );
    }

    method type_declarator:sym($/) {
        my $world := $*W;
        my $value_ast := $.ast;
        my $sigil := '';

        # Provided it's named, install it.
        my $name;
        if $ {
            $name := $.ast;
        }
        elsif $ {
            if $ {
                $sigil := ~$;
            }
            if $ {
                my $twigil := ~$;
                if $twigil eq '?' {
                    unless $*COMPILING_CORE_SETTING {
                        $world.throw($/, 'X::Comp::NYI',
                          feature => "Constants with a '$twigil' twigil"
                        );
                    }
                }

                elsif $twigil eq '*' {
                    $world.throw($/, 'X::Syntax::Variable::Twigil',
                      name       => ~$,
                      what       => 'constant',
                      twigil     => $twigil,
                      scope      => $*SCOPE,
                      additional => ' because values cannot be constant and dynamic at the same time',
                    );
                }

                # Don't handle other twigil'd case yet.
                else {
                    $world.throw($/, 'X::Comp::NYI',
                      feature => "Constants with a '$twigil' twigil");
                }
            }
            $name := ~$;
        }

        # Get constant value.
        my $Mu := $world.find_symbol: ['Mu'];
        my $type := nqp::defined($*OFTYPE) ?? $*OFTYPE.ast !! $Mu;
        if $ eq '.=' {
            my $init-type := $world.maybe-nominalize: $type;
            # my $init-type := $world.maybe-definite-how-base: $type;
            $value_ast.unshift: QAST::WVal.new: :value($init-type);
            $value_ast.returns: $init-type;
        }
        else {
            $value_ast.returns($type);
        }

        my $con_block := $world.pop_lexpad();
        my $value;
        if $value_ast.has_compile_time_value {
            $value := $value_ast.compile_time_value;
        }
        else {
            $con_block.push($value_ast);
            $con_block.annotate('BEGINISH', 1);
            my $value_thunk := $world.create_code_obj_and_add_child($con_block, 'Block');
            $value := $world.handle-begin-time-exceptions($/, 'evaluating a constant', $value_thunk);
            $world.add_constant_folded_result($value);
        }

        sub check-type ($expected) {
            nqp::istype($value, $expected)
            || $world.throw: $/, 'X::TypeCheck', :operation(
                "constant declaration of " ~ ($name || '')
              ), :$expected, :got($value);
        }
        sub check-type-maybe-coerce($meth, $expected) {
            unless nqp::istype($value, $expected) {
                $value := $value."$meth"();
                check-type($expected);
            }
        }
        if $sigil eq '%' {
            nqp::defined($*OFTYPE) && $world.throw: $/, 'X::ParametricConstant';
            nqp::getcomp('Raku').language_revision < 2
              ?? check-type($world.find_symbol: ['Associative'])
              !! check-type-maybe-coerce('Map', $world.find_symbol: ['Associative'])
        }
        elsif $sigil eq '@' {
            nqp::defined($*OFTYPE) && $world.throw: $/, 'X::ParametricConstant';
            check-type-maybe-coerce('cache', $world.find_symbol: ['Positional']);
        }
        elsif $sigil eq '&' {
            nqp::defined($*OFTYPE) && $world.throw: $/, 'X::ParametricConstant';
            check-type($world.find_symbol: ['Callable']);
        }
        elsif !($type =:= $Mu) && ! nqp::objprimspec($type) {
            check-type($type);
        }

        if $name {
            my $cur_pad := $world.cur_lexpad();
            if $cur_pad.symbol($name) {
                $world.throw($/, ['X', 'Redeclaration'], symbol => $name);
            }

            $world.install_package($/, [$name], ($*SCOPE || 'our'),
                'constant', $/.package, $cur_pad, $value);
        }
        for $ {
            $_.ast.apply($value, :SYMBOL($name)) if $_.ast;
        }

        # Evaluate to the constant.
        make QAST::WVal.new( :value($value), :returns($type) );
    }

    method initializer:sym<=>($/) {
        make WANTED($.ast, 'init=');
    }
    method initializer:sym<:=>($/) {
        make WANTED($.ast, 'init:=');
    }
    method initializer:sym<::=>($/) {
        make WANTED($.ast, 'init::=');
    }
    method initializer:sym<.=>($/) {
        make WANTED($.ast, 'init.=');
    }

    method initializer:sym<.>($/) {
        make WANTED($.ast, 'init.');
    }

    method capterm($/) {
        my $past := $
            ?? QAST::Op.new( $.ast )
            !! $.ast;
        wantall($past, 'capterm');
        $past.unshift(QAST::WVal.new( :value($*W.find_single_symbol_in_setting('Capture') ) ));
        $past.op('callmethod');
        $past.name('from-args');
        make $past;
    }

    method multisig($/) {
        make $.ast;
    }

    method fakesignature($/) {
        my $world := $*W;
        my $fake_pad := $world.pop_lexpad();
        for <$/ $! $_> {
            unless $fake_pad.symbol($_) {
                $world.install_lexical_magical($fake_pad, $_);
            }
        }
        my $sig := $world.create_signature_and_params($/, $.ast,
            $fake_pad, 'Mu', :no_attr_check(1));

        %*PARAM_INFO := $sig.returns;
        $world.cur_lexpad()[0].push($fake_pad);
        $world.create_code_object($fake_pad, 'Block', $sig);

        make QAST::WVal.new( :value($sig) );
    }

    method signature($/) {
        # Fix up parameters with flags according to the separators.
        my %signature;
        my @parameter_infos;
        my int $param_idx := 0;
        for $ {
            my %info := $_.ast;
            my $sep := @*seps[$param_idx];
            if ~$sep eq ':' {
                if $param_idx != 0 {
                    $*W.throw($/, 'X::Syntax::Signature::InvocantMarker')
                }
                unless $*ALLOW_INVOCANT {
                    $*W.throw($/, 'X::Syntax::Signature::InvocantNotAllowed')
                }
                %info := 1;
            }
            @parameter_infos.push(%info);
            $param_idx := $param_idx + 1;
        }
        %signature := @parameter_infos;
        if $ {
            %signature := $.ast;
        }
        elsif $ {
            %signature := $.ast.compile_time_value;
        }

        # Mark current block as having a signature.
        $*W.mark_cur_lexpad_signatured();

        # Result is set of parameter descriptors.
        make %signature;
    }

    method parameter($/) {
        # Sanity checks.
        my $quant := ~$;
        my %param_info := %*PARAM_INFO;
        if $ {
            my $name := %param_info // '';
            if $quant eq '*'  || $quant eq '|'
            || $quant eq '**' || $quant eq '+' {
                $/.typed_sorry('X::Parameter::Default', how => 'slurpy',
                            parameter => $name);
            }
            if $quant eq '!' {
                $/.typed_sorry('X::Parameter::Default', how => 'required',
                            parameter => $name);
            }
            my $val := WANTED($[0].ast, 'parameter/def');
            if $val.has_compile_time_value {
                my $value := $val.compile_time_value;
                check_param_default_type($/, $value);
                %param_info := $value;
                %param_info := 1;
            }
            else {
                my $maybe_code_obj := $val.ann('code_object');
                if nqp::isconcrete($maybe_code_obj) {
                    $val.annotate('past_block', WANTED($val.ann('past_block'), 'parameters'));
                    check_param_default_type($/, $maybe_code_obj);
                }
                %param_info :=
                    $*W.create_thunk($[0], $val, $*CURTHUNK);
            }
        }

        # Set up various flags.
        %param_info := $quant eq '*' && %param_info eq '@';
        %param_info    := $quant eq '**' && %param_info eq '@';
        %param_info := $quant eq '*' && %param_info eq '%';
        %param_info     := $quant eq '?' || $ || ($ && $quant ne '!');
        %param_info       := $quant eq '\\' || ($quant eq '+' && !%param_info);
        %param_info   := $quant eq '|';
        %param_info   := $quant eq '+';

        # Stash any traits.
        %param_info := $;

        if $ {
            if %param_info || %param_info || %param_info {
                $/.typed_sorry('X::Parameter::TypedSlurpy', kind => 'positional');
            }
            elsif %param_info {
                $/.typed_sorry('X::Parameter::TypedSlurpy', kind => 'named');
            }
            elsif %param_info eq '&' && nqp::existskey(%param_info, 'subsig_returns')
                    && !(%param_info =:= $*W.find_single_symbol_in_setting('Mu')) {
                $/.'!fresh_highexpect'();
                $*W.throw($/, 'X::Redeclaration',
                    what    => 'return type for',
                    symbol  => $.Str,
                    postfix => "(previous return type was "
                                ~ $[0].Str
                                ~ ')',
                );
            }
        }

        # Result is the parameter info hash.
        make %param_info;
    }

    sub check_param_default_type($/, $value) {
        if nqp::existskey(%*PARAM_INFO, 'type') {
            my $expected := %*PARAM_INFO;
            if nqp::objprimspec($expected) == 0 {
                unless nqp::istype($value, $expected) {
                    # Ensure both types are composed before complaining,
                    # or we give spurious errors on stubbed things or
                    # things we're in the middle of compiling.
                    my $got_comp := nqp::can($value.HOW, "is_composed") && $value.HOW.is_composed($value);
                    my $exp_comp := nqp::can($expected.HOW, "is_composed") && $expected.HOW.is_composed($expected);
                    if $got_comp && $exp_comp {
                        $[0].typed_sorry(
                            'X::Parameter::Default::TypeCheck',
                            got => $value,
                            expected => %*PARAM_INFO);
                    }
                }
            }
        }
    }

    method param_var($/) {
        my $world := $*W;
        my %param_info := %*PARAM_INFO;
        if $ {
            if nqp::existskey(%param_info, 'sub_signature_params') {
                $/.panic('Cannot have more than one sub-signature for a parameter');
            }
            %param_info := $.ast;
            if nqp::eqat(~$/, '[', 0) {
                %param_info := '@';
                %param_info := $world.find_single_symbol_in_setting('Positional');
            }
        }
        else {
            # Set name, if there is one.
            if $ {
                %param_info := ~$;
                %param_info := ~($ // $);
            }
            %param_info := my $sigil := ~$;

            # Depending on sigil, use appropriate role.
            my int $need_role;
            my $role_type;
            if $sigil eq '@' {
                $role_type := $world.find_single_symbol_in_setting('Positional');
                $need_role := 1;
            }
            elsif $sigil eq '%' {
                $role_type := $world.find_single_symbol_in_setting('Associative');
                $need_role := 1;
            }
            elsif $sigil eq '&' {
                $role_type := $world.find_single_symbol_in_setting('Callable');
                $need_role := 1;
            }
            if $need_role {
                if nqp::existskey(%param_info, 'type') {
                    %param_info := $world.parameterize_type_with_args($/,
                        $role_type, [%param_info], nqp::hash());
                }
                else {
                    %param_info := $role_type;
                }
            }

            # Handle twigil.
            my $twigil := $ ?? ~$ !! '';
            %param_info := $twigil;
            if $twigil eq '' || $twigil eq '*' {
                # Need to add the name.
                if $ {
                    my $name := ~$;
                    if $ {
                        $name := nqp::substr($name, 0, nqp::chars($name) - nqp::chars(~$));
                        %param_info := $name;
                    }
                    self.declare_param($/, $name);
                }
            }
            elsif $twigil eq '!' {
                if !$*HAS_SELF && $*SURROUNDING_DECL ne 'variable' {
                    $world.throw($/, ['X', 'Syntax', 'NoSelf'], variable => ~$/);
                }
                %param_info := 1;
                my int $succ := 1;
                try {
                    %param_info := $world.find_single_symbol('$?CLASS');
                    CATCH {
                        $succ := 0;
                    }
                }
                unless $succ {
                    $/.panic('cannot use a $! parameter in a signature where no $?CLASS is available');
                }
            }
            elsif $twigil eq '.' {
                if $*SURROUNDING_DECL ne 'variable' {
                    if !$*HAS_SELF {
                        $world.throw($/, ['X', 'Syntax', 'NoSelf'], variable => ~$/);
                    }
                    elsif $*HAS_SELF eq 'partial' {
                        $world.throw($/, ['X', 'Syntax', 'VirtualCall'], call => ~$/);
                    }
                }
                %param_info := 1;
                unless $ {
                    $/.panic("Cannot declare $. parameter in signature without an accessor name");
                }
            }
            else {
                if $twigil eq ':' {
                    $/.typed_sorry('X::Parameter::Placeholder',
                        type      => "named",
                        parameter => ~$/,
                        right     => ':' ~ $ ~ ~$,
                    );
                }
                elsif $twigil eq '^' {
                    $/.typed_sorry('X::Parameter::Placeholder',
                        type      => "positional",
                        parameter => ~$/,
                        right     => $ ~ $,
                    );
                }
                else {
                    $/.typed_sorry('X::Parameter::Twigil',
                        parameter => ~$/,
                        twigil    => $twigil,
                    );
                }
            }
        }
        # Handle leading declarative docs
        if $*DECLARATOR_DOCS ne '' {
            %param_info := $*POD_BLOCK;
            $*DECLARATOR_DOCS := '';
        }

        # Attach the dummy param we set up in Grammar::param_var to PARAM_INFO,
        # so we can access it later on.  The dummy param may have goodies like
        # trailing docs!
        my $par_type := $world.find_single_symbol_in_setting('Parameter');
        if nqp::istype($*PRECEDING_DECL, $par_type) {
            %param_info := $*PRECEDING_DECL;
        }

        if $ || $ -> $sig {
            %param_info := $sig.ast.value;
        }

        if $ {
            unless %param_info {
                %param_info := [];
            }
            my $where := make_where_block($,
                QAST::Op.new(
                    :op('callmethod'), :name('list'),
                    $.ast),
                QAST::Op.new(
                    :op('if'),
                    QAST::Op.new(
                        :op('can'),
                        WANTED(QAST::Var.new( :name('$_'), :scope('lexical') ),'param_var'),
                        QAST::SVal.new( :value('shape') )
                    ),
                    QAST::Op.new(
                        :op('callmethod'), :name('shape'),
                        WANTED(QAST::Var.new( :name('$_'), :scope('lexical') ),'param_var')
                    )));
            %param_info.push($where);
        }
    }

    method param_term($/) {
        if $ {
            my $name := $.ast;
            my %param_info := %*PARAM_INFO;
            %param_info := $name;
            %param_info   := $name;
            %param_info         := '';
            self.declare_param($/, $name);
        }
    }

    method declare_param($/, $name) {
        my $cur_pad := $*W.cur_lexpad();
        if $cur_pad.symbol($name) {
            $*W.throw($/, ['X', 'Redeclaration'], symbol => $name);
        }
        if nqp::existskey(%*PARAM_INFO, 'type') {
            $cur_pad[0].push(QAST::Var.new( :$name, :scope('lexical'),
                :decl('var'), :returns(%*PARAM_INFO) ));
            $cur_pad.symbol(%*PARAM_INFO, :type(%*PARAM_INFO));
        } else {
            $cur_pad[0].push(QAST::Var.new( :name($name), :scope('lexical'), :decl('var') ));
        }
        $cur_pad.symbol($name, :scope('lexical'));
    }

    method named_param($/) {
        my %param_info := %*PARAM_INFO;
        unless %param_info {
            %param_info := nqp::list_s();
        }
        if $ {
            nqp::push_s(%param_info, ~$);
        }
        elsif $ {
            my $name := $;
            nqp::push_s(%param_info, ~($name // $name));
        }
        else {
            nqp::push_s(%param_info, '');
        }
    }

    method default_value($/) {
        make WANTED($.ast, 'default_value');
    }

    method type_constraint($/) {
        my $world := $*W;
        my %param_info := %*PARAM_INFO;
        if $ {
            my str $typename := ~$;
            if nqp::eqat($typename, '::', 0) && !nqp::eqat($typename, '?', 2) {
                # Set up signature so it will find the typename.
                my str $desigilname := nqp::substr($typename, 2);
                unless %param_info {
                    %param_info := nqp::list_s()
                }
                nqp::push_s(%param_info, $desigilname);

                # Install type variable in the static lexpad. Of course,
                # we'll find the real thing at runtime, but in the static
                # view it's a type variable to be reified.
                $world.install_lexical_symbol($world.cur_lexpad(), $desigilname,
                    $.ast);
            }
            else {
                if nqp::existskey(%param_info,'type') {
                    $world.throw($/, ['X', 'Parameter', 'MultipleTypeConstraints'],
                        parameter => (%param_info // ''),
                    );
                }
                dissect_type_into_parameter($/, $.ast);
            }
        }
        elsif $ {
            if nqp::existskey(%param_info, 'type') {
                $world.throw($/, ['X', 'Parameter', 'MultipleTypeConstraints'],
                        parameter => (%param_info // ''),
                );
            }

            my $val := wanted(
                $.ast, 'type_constraint'
            ).compile_time_value;

            if $*NEGATE_VALUE {
                my $neg-op := $world.find_single_symbol_in_setting('&prefix:<->');
                $val := $neg-op($val);
                $world.add_object_if_no_sc($val);
            }

            %param_info := $val.WHAT;
            unless %param_info {
                %param_info := [];
            }
            %param_info.push($val);
        }
        else {
            $/.panic('Cannot do non-typename cases of type_constraint yet');
        }
    }

    sub dissect_type_into_parameter($/, $type) {
        my %param_info := %*PARAM_INFO;
        my $archetypes := $type.HOW.archetypes($type);
        if nqp::isconcrete($type) {
            if nqp::istype($type, $*W.find_single_symbol_in_setting('Bool')) {
                my $val := $type.gist;
                $/.worry(
                    "Literal values in signatures are smartmatched against and "
                    ~ "smartmatch with `$val` will always "
                    ~ ($val eq 'True' ?? 'succeed' !! 'fail')
                    ~ ". Use the `where` clause instead."
                );
            }

            # Actual a value that parses type-ish.
            %param_info := $type.WHAT;
            unless %param_info {
                %param_info := [];
            }
            %param_info.push($type);
        }
        elsif $archetypes.nominal || $archetypes.coercive {
            %param_info := $type;
        }
        elsif $archetypes.definite && nqp::eqaddr($type.HOW.wrappee($type, :definite), $type) {
            dissect_type_into_parameter($/, $type.HOW.base_type($type));
        }
        elsif $type.HOW.archetypes($type).generic {
            %param_info := $type;
        }
        elsif $archetypes.nominalizable {
            # XXX The actual nominalization is likely to be done by Parameter class itself.
            my $nom := $type.HOW.nominalize($type);
            %param_info := $nom;
            unless %param_info {
                %param_info := [];
            }
            %param_info.push($type);
        }
        else {
            $.typed_sorry('X::Parameter::BadType', :$type);
        }
        %param_info   := 1 if $archetypes.generic;
        %param_info        := %param_info;
        %param_info  := $;

        if $ {
            my $ast := $.ast;
            %param_info   := 1 if $ast;
            %param_info := 1 if $ast;
        }
    }

    method post_constraint($/) {
        if $*CONSTRAINT_USAGE eq 'param' {
            if $ {
                if nqp::existskey(%*PARAM_INFO, 'sub_signature_params') {
                    $/.panic('Cannot have more than one sub-signature for a parameter');
                }
                %*PARAM_INFO := $.ast;
                if nqp::eqat(~$/, '[', 0) {
                    %*PARAM_INFO := '@' unless %*PARAM_INFO;
                }
            }
            else {
                unless %*PARAM_INFO {
                    %*PARAM_INFO := [];
                }
                %*PARAM_INFO.push(make_where_block($, $.ast));
            }
        }
        else {
            if $ {
                $/.NYI('Signatures as constraints on variables');
            }
            else {
                make make_where_block($, $.ast);
            }
        }
    }

    method trait($/) {
        make $ ?? $.ast !! $.ast;
    }

    my class Trait {
        has $!match;
        has str $!trait_mod;
        has @!pos_args;
        has %!named_args;

        method new($match, str $trait_mod, *@pos_args, *%named_args) {
            my $self := nqp::create(self);
            nqp::bindattr($self, Trait, '$!match', $match);
            nqp::bindattr_s($self, Trait, '$!trait_mod', $trait_mod);
            nqp::bindattr($self, Trait, '@!pos_args', @pos_args);
            nqp::bindattr($self, Trait, '%!named_args', %named_args);
            $self
        }

        method apply($declarand, *%additional) {
            $*W.apply_trait($!match, $!trait_mod, $declarand, |@!pos_args,
                |%!named_args, |%additional);
        }

        method mod() { $!trait_mod }
        method args() { @!pos_args }
    }

    method trait_mod:sym($/) {
        # Handle is repr specially.
        if ~$ eq 'repr' {
            if $ {
                if nqp::istype($[0].ast, QAST::WVal) {
                    $*REPR := compile_time_value_str($[0].ast, "is repr(...) trait", $/);
                } else {
                    $*REPR := compile_time_value_str($[0].ast[0], "is repr(...) trait", $/);
                }
            }
            else {
                $/.panic("is repr(...) trait needs a parameter");
            }
        }
        else {
            my $world := $*W;
            # If we have an argument, get its compile time value or
            # evaluate it to get that.
            my @trait_arg;
            if $ {
                my $arg := WANTED($[0].ast, 'is');
                if nqp::istype($arg, QAST::Want) {
                    $arg := $arg[0];
                }

                @trait_arg[0] := $arg.has_compile_time_value ??
                    $arg.compile_time_value !!
                    $world.create_thunk($/, $[0].ast)();
            }

            # If we have a type name then we need to dispatch with that type; otherwise
            # we need to dispatch with it as a named argument.
            my @name := $world.dissect_longname($).components();
            if $world.is_name(@name) {
                my $trait := $world.find_symbol(@name);
                make Trait.new($/, '&trait_mod:', $trait, |@trait_arg);
            }
            else {
                my %arg;
                %arg{~$} := @trait_arg ?? @trait_arg[0] !!
                    $world.find_symbol(['Bool', 'True']);
                make Trait.new($/, '&trait_mod:', |%arg);
            }
        }
    }

    method trait_mod:sym($/) {
        make Trait.new($/, '&trait_mod:', $.ast);
    }

    method trait_mod:sym($/) {
        make Trait.new($/, '&trait_mod:', $.ast);
    }

    method trait_mod:sym($/) {
        my %arg;
        %arg{~$} := ($*W.add_constant('Int', 'int', 1)).compile_time_value;
        make Trait.new($/, '&trait_mod:', ($.ast).ann('code_object'), |%arg);
    }

    method trait_mod:sym($/) {
        make Trait.new($/, '&trait_mod:', $.ast);
    }

    method trait_mod:sym($/) {
        make Trait.new($/, '&trait_mod:', $.ast);
    }

    method trait_mod:sym($/) {
        # The term may be fairly complex. Thus we make it into a thunk
        # which the trait handler can use to get the term and work with
        # it.
        my $thunk := $*W.create_thunk($/, WANTED($.ast, 'handles'));
        make Trait.new($/, '&trait_mod:', $thunk);
    }

    method postop($/) {
        if $ {
            make $.ast
                 || QAST::Op.new( :name('&postfix' ~ $*W.canonicalize_pair('', $.Str)), :op )
        } else {
            make $.ast
                 || QAST::Op.new( :name('&postcircumfix' ~ $*W.canonicalize_pair('', $.Str)), :op );
        }
    }

    method revO($/) {
        my $O := nqp::clone($*FROM);
        if    $O eq 'right' { $O := 'left' }
        elsif $O eq 'left'  { $O := 'right' }
        make $O;
    }

    method dotty:sym<.>($/) { make $.ast; }

    method dotty:sym<.*>($/) {
        my $past := $.ast;
        unless nqp::istype($past, QAST::Op) && $past.op() eq 'callmethod' {
            $/.panic("Cannot use " ~ $.Str ~ " on a non-identifier method call");
        }
        if $ eq '.^' {
            $past.op('p6callmethodhow');
        }
        else {
            $past.unshift($*W.add_string_constant($past.name))
                if $past.name ne '';
            $past.name('dispatch' ~ $*W.canonicalize_pair('', ~$));
            $past.nosink(1);
        }
        make $past;
    }

    method dottyop($/) {
        if $ {
            make $.ast;
        }
        elsif $ {
            make $.ast;
        }
        else {
            if $ eq "" && $ -> $cf {
                if $cf -> $op_name {
                    make QAST::Op.new( :op, :node($/),
                    :name('&prefix' ~
                    $*W.canonicalize_pair('', $*W.colonpair_nibble_to_str(
                        $/, $op_name // $op_name // $op_name
                    ))));
                }
            } else {
                make $.ast;
            }
        }
    }

    method privop($/) {
        # Compiling private method calls is somewhat interesting. If it's
        # in any way qualified, we need to ensure that the current package
        # is trusted by the target class. Otherwise we assume that the call
        # is to a private method in the current (non-virtual) package.
        # XXX Attribute accesses? Again, maybe for the optimizer, since it
        # runs after CHECK time.
        my $past := $.ast;
        my $package := $/.package;
        if $ {
            my $world := $*W;
            my @parts   := $world.dissect_longname($).components();
            my $name    := @parts.pop;
            if @parts {
                my $methpkg := $world.find_symbol(@parts);
                unless nqp::can($methpkg.HOW, 'is_trusted') && $methpkg.HOW.is_trusted($methpkg, $package) {
                    $world.throw($/, ['X', 'Method', 'Private', 'Permission'],
                        :method(         $name),
                        :source-package( $methpkg.HOW.name($methpkg)),
                        :calling-package( $package.HOW.name($package)),
                    );
                }
                $past[1].returns($methpkg);
            }
            else {
                unless nqp::can($package.HOW, 'find_private_method') {
                    $world.throw($/, ['X', 'Method', 'Private', 'Unqualified'],
                        :method($name),
                    );
                }
                if $package.HOW.archetypes.parametric {
                    $past.unshift(typevar_or_lexical_lookup('::?CLASS'));
                }
                else {
                    $past.unshift(QAST::WVal.new( :value($package) ));
                    $past[0].returns($package);
                }
                $past.unshift($world.add_string_constant($name));
            }
            $past.name('dispatch:');
        }
        elsif $ {
            my $name := $past.shift;
            if $package.HOW.archetypes.parametric {
                $past.unshift(typevar_or_lexical_lookup('::?CLASS'));
            }
            else {
                $past.unshift(QAST::WVal.new( :value($package) ));
            }
            $past.unshift($name);
            $past.name('dispatch:');
        }
        else {
            $/.panic("Cannot use this form of method call with a private method");
        }
        make $past;
    }

    # We can generate typevar scope when we're in a method and the enclosing
    # role declares the symbol we're looking for.
    sub typevar_or_lexical_lookup($name) {
        if $*HAS_SELF {
            my $outer := $*W.cur_lexpad().ann('outer');
            if $outer && $outer.symbol($name) {
                return QAST::Var.new( :$name, :scope('typevar') );
            }
        }
        return QAST::Var.new( :$name, :scope('lexical') );
    }

    method methodop($/) {
        my $past := $ ?? $.ast !! QAST::Op.new( :node($/) );
        $past.op('callmethod');
        my $name;
        if $ {
            my $world := $*W;
            # May just be .foo, but could also be .Foo::bar. Also handle the
            # macro-ish cases.
            my @parts := $world.dissect_longname($).components();
            $name := @parts.pop;
            wantall($past, 'methodop/longname');
            if +@parts {
                my int $found_wval := 0;
                try {
                    my $sym := $world.find_symbol(@parts);
                    unless $sym.HOW.archetypes($sym).generic {
                        $past.unshift(QAST::WVal.new( :value($sym) ));
                        $found_wval := 1;
                    }
                }
                unless $found_wval {
                    $past.unshift($world.symbol_lookup(@parts, $/));
                }
                $past.unshift($world.add_string_constant($name));
                $past.name('dispatch:<::>');
                make $past;
                return;
            }
        }
        elsif $ {
            $name := ~$;
        }
        else {
            $name := '';
        }

        if $name ne '' {
            if $name eq 'WHAT' {
                whine_if_args($/, $past, $name);
                $past.op('what');
            }
            elsif $name eq 'HOW' {
                whine_if_args($/, $past, $name);
                $past.op('how');
            }
            elsif $name eq 'WHO' {
                whine_if_args($/, $past, $name);
                $past.op('who');
            }
            elsif $name eq 'VAR' {
                whine_if_args($/, $past, $name);
                $past.op('p6var');
            }
            elsif $name eq 'REPR' {
                whine_if_args($/, $past, $name);
                $past.op('p6reprname');
            }
            elsif $name eq 'DEFINITE' {
                whine_if_args($/, $past, $name);
                $past.op('p6definite');
            }
            else {
                $past.name( $name );
                $*W.cur_lexpad().no_inline(1) if $name eq 'EVAL';
            }
        }
        elsif $ {
            $past.unshift(
                QAST::Op.new(
                    :op,
                    $.ast
                )
            );
        }
        elsif $ {
            $past.unshift(WANTED($.ast, 'methodop/var'));
            $past.name('dispatch:');
        }
        unless $name eq 'sink' {
            wantall($past, 'methodop');
        }

        make $past;
    }

    sub whine_if_args($/, $past, $name) {
        if nqp::elems($past) > 0 {
           $*W.throw($/, ['X', 'Syntax', 'Argument', 'MOPMacro'], macro => $name);
        }
    }

    method term:sym<::?IDENT>($/) {
        make instantiated_type([~$/], $/);
    }

    method term:sym($/) {
        make QAST::Var.new( :name('self'), :scope('lexical'), :returns($/.package), :node($/) );
    }

    method term:sym($/) {
        make QAST::Op.new( :op('call'), :name('&term:'), :node($/) );
    }

    method term:sym