perl5
perl5 copied to clipboard
Apply HINT_BLOCK_SCOPE to lexical variable declarations
Apply HINT_BLOCK_SCOPE to lexical variable declarations
At present, any else {...} block is wrapped in an ENTER/LEAVE pair, even
if no new scope is warranted, which causes runtime inefficiency. For
example, this block doesn't need a scope but gets one anyway:
} else {
return 1;
}
However, this behaviour means that an object like $kaboom will reliably
be destroyed when the else block is exited:
} else {
my $kaboom = TickTick->new;
}
In contrast, if () {...} or elsif () {...} blocks default to not
having an ENTER/LEAVE pair, which is more efficient but arguably
incorrect, as $marvin in this code won't be destroyed when the if
block is exited:
if ( $x ) {
my $marvin = TickTick->new;
}
Exactly when it is destroyed is dependent upon where the next scope exit happens to be, and this could be some ways away from its block.
This behaviour is also very brittle, as shown in this case where the
no-op 0; statement causes - via a quirk of parsing - the if
block to be assigned an ENTER/LEAVE pair and so $marvin
will be destroyed when the block exits:
if ( $x ) {
0; my $marvin = TickTick->new;
}
Whether a block gains a scope via ENTER/LEAVE pair or is just parented
by a SCOPE OP (that will be optimized away), is decided by
Perl_op_scope on the basis of the OPf_PARENS flag on a block's
LINESEQ OP. The parser always sets this flag for else blocks,
a variety of circumstances determines whether it is set otherwise.
This commit makes two changes:
-
Removes the enforced use of the
OPf_PARENSflag onelseblocks. The same rules now apply toif,elsif, andelse, hopefully making destruction behaviour more predictable. -
Changes the tokenizer such that occurrances of
my,our, andstateset theOPf_PARENSflag, and the containing block will be wrapped in anENTER/LEAVEpair.
After this commit, neither of the if or else blocks in this example
will have an unnecessaryENTER/LEAVE pair:
if ($x ) {
return 0;
} else {
return 1;
}
And in this example, both objects will be destroyed when the relevant block is exited:
if ( $x ) {
my $kaboom_true = TickTick->new;
} else {
my $kaboom_else = TickTick->new;
}
Sadly, both blocks in this example will still have unnecessary scopes, but fixing that is for a different PR:
if ($x ) {
0; return 0;
} else {
0; return 1;
}
There are two downsides to this commit:
-
It has the potential to change the destruction timing of objects created in an
ifblock and assigned to a lexical declared within the block, if the block hasn't ended up with anENTER/LEAVEpair. As noted above though. that behaviour is very brittle and already sensitive to even minor changes to the Perl code - and also to minor parser/optimization changes within the interpreter. -
The old
elsebehaviour meant that the first statement'sNEXTSTATEOP stays in place and consequently any error messages arising from the first statement mention the correct (or closer) line number. Theif/elsifbehaviour is more likely to cause theNEXTSTATEOP to be optimized away, causing any error messages to contain the line number for where theiforelsifkeyword keyword occurred, which could be many lines away.
Following this commit, first-line error messages will be just as bad
for an else block as they are for if/elsif blocks. However, that's
a separate issue and should not be a reason to avoid this commit.
- This set of changes requires a perldelta entry, and I need help writing it.