develooper Front page | perl.perl5.porters | Postings from June 2020

Re: Dual-life perl 5-or-7 code and prototypes - impossible?

Thread Previous | Thread Next
From:
ilmari
Date:
June 27, 2020 18:36
Subject:
Re: Dual-life perl 5-or-7 code and prototypes - impossible?
Message ID:
87y2o8mkfd.fsf@wibble.ilmari.org
"Paul \"LeoNerd\" Evans" <leonerd@leonerd.org.uk> writes:

> TL;DR: I believe it impossible to write a dual-life module using
>   function prototypes that works on both perl 5 and perl 7.

Almost...

>
> So my first thought was to inject a MODIFY_CODE_ATTRIBUTES.
>
> Simply put, this doesn't work. A naive attempt which invokes
> Sub::Util::set_prototype silently has no side-effect - it doesn't warn
> or fail, it just doesn't set the prototype. I haven't investigated why
> yet, but I have a bug open:
>
>   https://rt.cpan.org/Ticket/Display.html?id=132889
>
> I think it's probably because MODIFY_CODE_ATTRIBUTES runs too early in 
> the parser. I have to defer it until UNITCHECK time. See more code
> above.

attributes.pm says:

    The call to [the MODIFY_type_ATTRIBUTES] method is currently made
    during the processing of the declaration.  In particular, this means
    that a subroutine reference will probably be for an undefined
    subroutine, even if this declaration is actually part of the
    definition.

> While that works, it produces lots of warnings:
>
>   CODE package attribute may clash with future reserved word: prototype at lib/List/UtilsBy.pm line 147.
>
> OK, well I can sortof fix that one too, by injecting a
> `no warnings 'reserved'".
>
> So the current code in List/UtilsBy.pm looks like:
>
> BEGIN {
>    if( $] < 5.020 ) {
>       require Sub::Util; Sub::Util->VERSION( '1.40' );
>       warnings->unimport( qw( reserved ) );
>
>       my @prototypes;
>
>       *MODIFY_CODE_ATTRIBUTES = sub {
>          my ( $pkg, $code, @attrs ) = @_;
>
>          my @ret;
>          foreach my $attr ( @attrs ) {
>             if( $attr =~ m/^prototype\((.*)\)$/ ) {
>                my $prototype = "$1";
>                push @prototypes, [ $code, $prototype ];
>                next;
>             }
>             push @ret, $attr;
>          }
>
>          return @ret;
>       };
>
>       UNITCHECK {
>          foreach ( @prototypes ) {
>             my ( $code, $prototype ) = @$_;
>             Sub::Util::set_prototype( $_->[1], $_->[0] );
>          }
>       }
>    }
> }
>
> And note that there are two large problems still unaddressed here:
>
>  1) This code has to suppress all "reserved" warnings, not just those
>     relating to the :prototype attribute
>
>  2) This code has to appear in List/UtilsBy.pm directly because of that
>     UNITCHECK phaser. It would not be possible to inject this entire
>     thing via a module.

The B::CompilerPhase::Hook module lets you programmatically enqueue
phasers: https://metacpan.org/pod/B::CompilerPhase::Hook.

Sticking the above code in a Sub::Attribute::Prototype::import() and
replacing UNITCHECK { ... } with enqueue_UNICHECK(sub { ...}) works.

Hoewver, another issue is that because the prototype isn't set until
UNITCHECK time, it will not apply to calls in the same unit as the
prototyped sub is defined in, so this is really only useful for modules
that export subs, not scripts.

- ilmari
-- 
- Twitter seems more influential [than blogs] in the 'gets reported in
  the mainstream press' sense at least.               - Matt McLeod
- That'd be because the content of a tweet is easier to condense down
  to a mainstream media article.                      - Calle Dybedahl

Thread Previous | Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About