perlstring-interpolationperl-critic

Why does PerlCritic think my prototype is string interpolation?


I have code in a package (class) that has a sorting function with a prototype:

#!perl
package Foo;

use strict;
use warnings;

sub new {
    my ($class, $value) = @_;
    my $self = bless{}, $class;
    $self->{value} = $value;
    return $self;
}

sub foo_value {
   my ($self) = @_;
   return $self->{value};
}

## no critic(Subroutines::ProhibitSubroutinePrototypes)
sub by_prototype($$) {
## use critic
    my ($a, $b) = @_;
    return $a->foo_value() cmp $b->foo_value();
}

sub by_plain {
    return $a->foo_value() cmp $b->foo_value();   # Can't call method "foo_value" on an undefined value at mcve.pl line 25.
}

package Bar;

my @values = (Foo->new('foo'), Foo->new('foobarbaz'), Foo->new('bar'));
print join(', ', sort Foo::by_prototype @values), "\n";
print join(', ', sort Foo::by_plain @values), "\n";

I am using the prototype because the documentation for sort says this is required to use my comparison routine from another package:

# using a prototype allows you to use any comparison subroutine  
# as a sort subroutine (including other package's subroutines)  
package other;  
sub backwards ($$) { $_[1] cmp $_[0]; }   # $a and $b are not set here

When I run this, it prints:

Foo=HASH(0x5e38b3a42538), Foo=HASH(0x5e38b3a6f4f0), Foo=HASH(0x5e38b3a6f478)
Can't call method "foo_value" on an undefined value at mcve.pl line 27.

Looks like the documentation is correct about requiring the prototype for my sort function, and because of that I've silenced PerlCritic's warning about the prototype.

Since my last OS update I have Perl 5.38.2 and PerlCritic 1.152 (I don't remember what it was before), and now PerlCritic complains about the prototype saying

perlcritic --brutal | grep interpolation
String *may* require interpolation at line 20, column 17.  See page 51 of PBP.  (Severity: 1)

I'm tempted to just add ## no critic(ValuesAndExpressions::RequireInterpolationOfMetachars) to silence this warning, but since this doesn't look like (string) interpolation to me (and that's what the documentation for this warning is about), I'm confused what PerlCritic wants here.

Why does PerlCritic think there is interpolation on the prototype and is this safe to ignore?


Solution

  • PerlCritic uses PPI to parse perl code; it generates an abstract syntax tree of objects of various classes. The ValuesAndExpressions::RequireInterpolationOfMetachars policy applies to objects of the PPI::Token::Quote::Literal class (among others). Function prototypes are represented by PPI::Token::Prototype objects, and that class turns out to be a subclass of PPI::Token::Quote::Literal, as this demonstrates:

    #!/usr/bin/env perl
    use warnings;
    use strict;
    use feature qw/say isa/;
    use PPI;
    
    my $doc = PPI::Document->new(\'sub foo($$) { return $a cmp $b; }');
    my $proto = $doc->find_first('PPI::Token::Prototype');
    say "Prototypes are a quotelike thing!"
      if $proto isa 'PPI::Token::Quote::Literal';
    

    Therefore, that policy looks at the contents of the prototype to see if it should generate a warning about interpolation, and $$ is enough to do so. It's a spurious warning you can safely ignore. Might even be considered a bug; maybe the policy should consider the context of the token, not just its contents?