diff --git a/MANIFEST b/MANIFEST index b9f4d35..9f21b54 100644 --- a/MANIFEST +++ b/MANIFEST @@ -39,6 +39,7 @@ lib/perl5i/2/HASH.pm lib/perl5i/2/Meta.pm lib/perl5i/2/Meta/Class.pm lib/perl5i/2/Meta/Instance.pm +lib/perl5i/2/MethodInfo.pm lib/perl5i/2/RequireMessage.pm lib/perl5i/2/SCALAR.pm lib/perl5i/2/Signature.pm diff --git a/lib/perl5i/2/CODE.pm b/lib/perl5i/2/CODE.pm index b32184d..8740678 100644 --- a/lib/perl5i/2/CODE.pm +++ b/lib/perl5i/2/CODE.pm @@ -17,4 +17,16 @@ sub signature { return $Signatures{$_[0]}; } +sub is_constant { + require B; + # Use eval to take advantage of compile-time constants. + eval ' + no warnings "redefine"; + sub is_constant { + not not B::svref_2object($_[0])->CvFLAGS & B::CVf_CONST + } + '; + goto &is_constant; +} + 1; diff --git a/lib/perl5i/2/Meta.pm b/lib/perl5i/2/Meta.pm index f0d34cf..589ecaa 100644 --- a/lib/perl5i/2/Meta.pm +++ b/lib/perl5i/2/Meta.pm @@ -47,6 +47,8 @@ sub linear_isa { } sub methods { + require perl5i::2::MethodInfo; + my $self = shift; my $opts = shift // {}; my $top = $self->class; @@ -70,15 +72,32 @@ sub methods { my $sym_table = $class->mc->symbol_table; for my $name (keys %$sym_table) { my $glob = $sym_table->{$name}; - next unless ref \$glob eq "GLOB"; - next unless my $code = *{$glob}{CODE}; + my $code; + if (ref $glob) { + if (ref $glob ne 'CODE') { # constant + $all_methods{$name} = + perl5i::2::MethodInfo->new($class, $name, + \$sym_table->{$name}); + next; + } + $code = $glob; + } + else { + next unless ref \$glob eq "GLOB"; + next unless $code = *{$glob}{CODE}; + } my $sig = $code->signature; next if $sig and !$sig->is_method; - $all_methods{$name} = $class; + $all_methods{$name} = + perl5i::2::MethodInfo->new($class, $name, + \$sym_table->{$name}); } } - return wantarray ? keys %all_methods : [keys %all_methods]; + my @ret = map perl5i::2::MethodInfo->new($_, $all_methods{$_}), + keys %all_methods; + + return wantarray ? values %all_methods : [values %all_methods]; } sub symbol_table { diff --git a/lib/perl5i/2/MethodInfo.pm b/lib/perl5i/2/MethodInfo.pm new file mode 100644 index 0000000..2fc8e17 --- /dev/null +++ b/lib/perl5i/2/MethodInfo.pm @@ -0,0 +1,28 @@ +package perl5i::2::MethodInfo; + +use strict; +use warnings; +use 5.010_000; +use perl5i::2::autobox; +use overload '""' => sub { shift->{name} }, fallback => 1; + +sub new { + my ($this_class, $that_class, $meth, $stashelem) = @_; + return bless { name => $meth, + package => $that_class, + stashelem => $stashelem }, $this_class; +} + +# Delegate these methods to the CODE autobox class. +for my $method (qw( is_constant )) { + no strict 'refs'; + *$method = sub { + my $self = shift; + my $sub = ref $self->{stashelem} eq 'GLOB' + ? *{$self->{stashelem}}{CODE} + : *{"$self->{package}:\:$self->{name}"}{CODE}; # reify + $sub->$method + } +} + +1; diff --git a/t/Meta/methods.t b/t/Meta/methods.t index f515409..b48d8fd 100644 --- a/t/Meta/methods.t +++ b/t/Meta/methods.t @@ -172,4 +172,17 @@ note "func gets filtered out of methods list"; { can_ok( $class, 'as_func'); # sanity check } +note "constants count as methods, regardless of how they came to be"; { + { + package Foo; + use constant a => 1; + sub b () { 2 } + sub c () { 3 } () = \&c; + } + is_deeply + scalar "Foo"->mc->methods->sort, + scalar [qw[ a b c ]]->sort, + "constants count as methods"; +} + done_testing;