Skip to content

Perl: a bare pattern match scores zero outside a boolean slot #1467

Description

@dekobon

Summary

Perl's bare pattern match /^#/ — a match against the implicit $_
scores one ABC condition inside a boolean slot and zero outside one,
while the bound form $x =~ /^#/ scores one in both. Found while
landing #1461, which fixed the same asymmetry in five other languages
but could not use the same route here.

Measured

bca metrics --no-config, abc.conditions per sub, at 336ed709:

sub bare_in   { if (/^#/) { return 1; } return 0; }        # 1
sub bare_out  { my $r = /^#/; return $r; }                 # 0   <-- the gap
sub bound_out { my ($x) = @_; my $r = ($x =~ /^#/); $r }   # 1
sub ctrl_out  { my ($x) = @_; my $r = ($x == 1); $r }      # 1

bare_out is the only row that disagrees with its controls.

Why #1461's route does not work here

#1461 moved five languages' relational productions out of
<lang>_bool_terminal_kinds!() into an unconditional arm, so the
construct scores wherever it is written. PatternMatcher (337) and
PatternMatcherM (336) cannot take that route unchanged: the ABC walk
visits every node, so an unconditional arm would also fire on the
pattern inside $x =~ /^#/, whose EQTILDE token is already counted —
bound_out would go to 2. That is the §5 double count
(.claude/rules/grammar-dispatch.md), and it is the same collision that
made Kotlin's as go through the wrapper peel rather than the terminal
set in #1459.

The existing note on perl_bool_terminal_kinds! explains why listing
the patterns double-counts nothing today: every walker that consumes
the set either breaks on the enclosing binary_expression or requires
the chain's list node to be the parent, so the pattern node is never
reached beside its own =~. An unconditional arm has no such
protection.

The shape a fix would take

A gated arm: count PatternMatcher / PatternMatcherM unless the
node's parent is a binary_expression whose operator token is =~ or
!~. That is a different shape from the seven unconditional arms #1461
landed, which is why it was left out rather than folded in.

Two things to check before writing it:

  • Whether substitution_pattern_s (s///) and
    transliteration_tr_or_y (tr///) should move with it. Both are
    absent from the terminal set today on the grounds that they evaluate
    to a count rather than a bool — a judgement perl_bool_terminal_kinds!
    records and decide: four uncounted boolean operands, and whether terminal sets should be token arms #1461 did not revisit.
  • Whether the same gap exists in Ruby, whose =~ is likewise a token
    arm gated on a binary parent. Ruby has no bare-match-against-$_
    spelling, so it probably does not, but that is an assumption rather
    than a measurement.

Corpus cost

None expected: no corpus carries a .pl / .pm file.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions