Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First round of feature 'class' #20647

Closed
wants to merge 17 commits into from
Closed

Conversation

leonerd
Copy link
Contributor

@leonerd leonerd commented Dec 24, 2022

Overall Notes To Reviewers

This is an epic-sized PR. I am not expecting that any one person can review everything all at once. It is perfectly fine to decide to just review some part of it - maybe just the docs, or the tests, or some small part of the implementation. Whatever you feel most confident in. When you leave a review comment please explain which part(s) of the PR you read and are commenting on, so it's clear.

Reminder: You do not have to be an internals expert, or even read any C code at all, to be able to review and comment on the tests, documentation, and overall shape of the idea.

Additionally, anyone who does wish to read and comment on the actual implementation is advised to start by reading the new documentation file pod/perlclassguts.pod which explains many details of how the internals work. Please start there to try to understand how the pieces all fit together. If there's something still unclear after reading it then that in itself is a valuable review comment. :) So please let me know.

PR Specifics

I have tried to squash down my original list of about 60 commits into larger pieces here, but I've still kept various stages split apart by empty-commit markers, as well as leaving several of them separate generally. I'm open to suggestion on whether to further squash them down before merging.

In particular the very first commit, titled "Define the concept of a suspended compcv", could stand alone as its own PR to be merged directly first as a prerequisite, although it would have no real utility in core other than to support the rest of this work here so it seems less useful to bother doing so in its own PR.

pad.c Outdated
=for apidoc_section $lexer
=for apidoc suspend_compcv

Implements part of the concept of a "suspended complication CV"; which can be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the concept of a "suspended complication CV" be explained in user-facing documentation in a subsequent commit (i.e., pod/perlsomething.pod)?

Also, what is the point of the semicolon in the line above. Shouldn't it either be a comma or rewritten as "suspended complication CV." This can be

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the concept of a "suspended complication CV" be explained in user-facing documentation in a subsequent commit (i.e., pod/perlsomething.pod)?

Not sure what you mean "user-facing"... it is explained in the rest of the sentence after the semicolon (to be a comma, as per below)

Also, what is the point of the semicolon in the line above. Shouldn't it either be a comma or rewritten as "suspended complication CV." This can be

Ahyes a comma probably reads better.

@jkeenan
Copy link
Contributor

jkeenan commented Dec 25, 2022

Some perl-level warnings are being emitted during make test_prep:

$ git describe
v5.37.7-49-g5d3f7324c3
$ zcat 5d3f7324c3.freebsd.threaded.clang.feature-class.maketp.output.txt.gz | tail -n 6
./miniperl -Ilib autodoc.pl
Function 'resume_compcv_and_save', documented in pad.c, not listed in embed.fnc at autodoc.pl line 1819.
Function 'resume_compcv_final', documented in pad.c, not listed in embed.fnc at autodoc.pl line 1819.
./miniperl -Ilib pod/perlmodlib.PL -q
./perl -Ilib -I. -f pod/buildtoc -q
cd t && (rm -f perl; /bin/ln -s ../perl perl)

pod/perldiag.pod Outdated Show resolved Hide resolved
pod/perlclass.pod Outdated Show resolved Hide resolved
Copy link

@cjac cjac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just the start of my review. I would like to check this out and build the interpreter to test the documentation. Do we have a docker with the image and deps installed yet?

pod/perlclassguts.pod Outdated Show resolved Hide resolved
This document intends to provide in-depth information about the way in which
the perl interpreter internals around the C<feature 'class'> syntax and overall
behaviour are provided. It is not intended as an end-user guide on how to use
the feature; for that see L<perlclass>.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for that, see L.


=head2 Classes

A class is still fundamentally a package, and exists in the symbol table as an
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the word "still" necessary?

=head2 Classes

A class is still fundamentally a package, and exists in the symbol table as an
HV with an aux structure in exactly the same way. It is distinguished from a
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in exactly the same way as what?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as a non-class package ?

@cjac
Copy link

cjac commented Dec 26, 2022

I'm building from source on my Debian WSL container with

perl -p -e 'print "deb-src $1$/" if /^deb (.*)$/' /etc/apt/sources.list  | sudo dd of=/etc/apt/sources.list
sudo apt-get update
sudo apt-get build-dep -y perl
cd ~/src/github
mkdir perl5
cd perl5
git clone [email protected]:leonerd/perl5.git
cd perl5
git checkout -t origin/feature-class
eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib | tee -a ~/.bashrc)"
$ ./Configure -des -Dprefix=$HOME/perl5
$ make
$ make install
$ which perl5.37.8
/home/cjac/perl5/bin/perl5.37.8
$ /home/cjac/perl5/bin/perl5.37.8 --version

This is perl 5, version 37, subversion 8 (v5.37.8 (v5.34.0-RC1-3603-g5d3f7324c3)) built for x86_64-linux

Copyright 1987-2022, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at https://www.perl.org/, the Perl Home Page.

@Ovid
Copy link
Contributor

Ovid commented Dec 26, 2022

For what it's worth, I just ported HTML::TokeParser::Simple to use feature 'class'. It was dead simple to do and everything behaved exactly as expected. The code is also a hell of a lot cleaner, though there are technical differences which make it an unfair comparison.

@Ovid
Copy link
Contributor

Ovid commented Dec 26, 2022

Getting some odd behavior with Data::Dumper.

#!/usr/bin/env perl

use 5.37.8;
use experimental 'class';
use Data::Dumper;

class Foo {}
my $foo = Foo->new;
say Dumper($foo);

Running that prints:

cannot handle ref type 16 at /Users/ovid/perl5/perlbrew/perls/feature-class/lib/5.37.8/darwin-2level/Data/Dumper.pm line 212.
$VAR1 = bless( , 'Foo' );

I assume this is expected, but it might be nice to have something a touch cleaner for this.

@Ovid
Copy link
Contributor

Ovid commented Dec 26, 2022

Update: there is now a branch of Perl::Tidy which handles the new syntax

Perl::Tidy is also struggling. Since it's not core, it probably is less important, but it's widely used. Leaving this here for others.

#!perl

use 5.37.8;
use experimental 'class';

class Point {
    field $x :param;
    field $y :param;

    method to_string () {
        return "($x, $y)";
    }
}

Running Perl::Tidy on that produces:

tidy.pl: Begin Error Output Stream
tidy.pl:
tidy.pl: There is no previous '?' to match a ':' on line 7
tidy.pl: 7: field $x :param;
tidy.pl:             ^
tidy.pl:
tidy.pl: There is no previous '?' to match a ':' on line 8
tidy.pl: 8: field $y :param;
tidy.pl:             ^
tidy.pl:14: To save a full .LOG file rerun with -g

I tried reading through Perl::Tidy::Tokenizer to see what's going on. On line 1860, we can change this:

    my %is_my_our_state;
    @q = qw(my our state);
    @is_my_our_state{@q} = (1) x scalar(@q);

To this:

    my %is_my_our_state;
    @q = qw(my our state field);
    @is_my_our_state{@q} = (1) x scalar(@q);

And that fixes the issue. I'm sure there will be others in Perl::Tidy. You can also add class to that list to let it properly handle attributes (:isa(...)) on class declarations, but that seems a nasty hack.

Perl::Tidy also messes up the indentation if you use a postfix block on a class. This can be fixed in Perl::Tidy::Tokenizer's code_block_type function starting at line 5993 by including class with sub and package.

    # or a sub or package BLOCK
    elsif ( ( $last_nonblank_type eq 'i' || $last_nonblank_type eq 't' )
        && $last_nonblank_token =~ /^(sub|package|class)\b/ )
    {
        return $last_nonblank_token;
    }

    # or a sub alias
    elsif (( $last_nonblank_type eq 'i' || $last_nonblank_type eq 't' )
        && ( $is_sub{$last_nonblank_token} ) )
    {
        return 'sub';
    }

    elsif ( $statement_type =~ /^(sub|package|class)\b/ ) {
        return $statement_type;
    }

(I really should make these diffs and stop hacking my local perltidy code)

I've filed a report for Perl::Tidy at https://rt.cpan.org/Ticket/Display.html?id=145706&results=44284c8317c4fa0392f65a81614b024c

@cjac
Copy link

cjac commented Dec 27, 2022

Getting some odd behavior with Data::Dumper.

#!/usr/bin/env perl

use 5.37.8;
use experimental 'class';
use Data::Dumper;

class Foo {}
my $foo = Foo->new;
say Dumper($foo);

Running that prints:

cannot handle ref type 16 at /Users/ovid/perl5/perlbrew/perls/feature-class/lib/5.37.8/darwin-2level/Data/Dumper.pm line 212.
$VAR1 = bless( , 'Foo' );

I assume this is expected, but it might be nice to have something a touch cleaner for this.

Should this be added to dist/Data-Dumper/t/ ?

cjac@build0:/usr/src/git/github/perl5/perl5/dist/Data-Dumper$ cat t/class.t
#!perl

use 5.37.8;
use experimental 'class';
use Test::Exception;
use Test::More;

use 'Data::Dumper';

BEGIN { plan tests => 1; }

class Foo {}
my $foo = Foo->new;

lives_ok { say Dumper( $foo ) } 'expecting to live';
cjac@build0:/usr/src/git/github/perl5/perl5/dist/Data-Dumper$ prove5.37.8 -b -v t/class.t
t/class.t .. 
1..1
$VAR1 = bless( , 'Foo' );
ok 1 - expecting to live
cannot handle ref type 16 at /usr/src/git/github/perl5/perl5/dist/Data-Dumper/blib/lib/Data/Dumper.pm line 212.
ok
All tests successful.
Files=1, Tests=1,  0 wallclock secs ( 0.07 usr  0.02 sys +  0.13 cusr  0.03 csys =  0.25 CPU)
Result: PASS

cjac@build0:/usr/src/git/github/perl5/perl5/dist/Data-Dumper$ cat -n /usr/src/git/github/perl5/perl5/dist/Data-Dumper/blib/lib/Data/Dumper.pm | head -215 | tail -9
   207
   208  sub Dump {
   209    return &Dumpxs
   210      unless $Data::Dumper::Useperl || (ref($_[0]) && $_[0]->{useperl})
   211              # Use pure perl version on earlier releases on EBCDIC platforms
   212          || (! $IS_ASCII && $] lt 5.021_010);
   213    return &Dumpperl;
   214  }
   215

@Ovid
Copy link
Contributor

Ovid commented Dec 27, 2022

#!perl

use 5.37.8;
use experimental 'class';
use Test::Exception;
use Test::More;

BEGIN { use_ok('Data::Dumper') };

BEGIN { plan tests => 1; }

class Foo {}
my $foo = Foo->new;

lives_ok { say Dumper( $foo ) } 'expecting to live';

A few comments on testing. First, you have use_ok and lives_ok, so those are two tests, even though you only specify one in the plan.

Second, unless there's a standard for it in the Perl core, I wouldn't use use_ok. It's fragile and you have to remember how to use it correctly. Instead, just use Data::Dumper; and if that dies, the test fails. Much simpler and easier.

Third, lives_ok kinda helps to verify that the code lives, but we also want to know that there are no warnings and that the output is correct. For my tests, I use Capture::Tiny. For core, I have no idea what best practice is.

class.c Show resolved Hide resolved
@demerphq
Copy link
Collaborator

demerphq commented Dec 27, 2022

This is missing the build changes for win32 to compile class.c, you have to do that manually in the makefiles in win32. See the changes i did recently to add regcomp_debug.c:

$ git grep regcomp_debug win32
win32/GNUmakefile:              ..\regcomp_debug.c      \
win32/GNUmakefile:..\regcomp_debug$(o) : ..\regcomp.h ..\regcomp_internal.h ..\regnodes.h ..\regcharclass.h
win32/Makefile:         ..\regcomp_debug.c      \
win32/Makefile:..\regcomp_debug$(o) : ..\regcomp.h ..\regcomp_internal.h ..\regnodes.h ..\regcharclass.h

@demerphq
Copy link
Collaborator

Third, lives_ok kinda helps to verify that the code lives, but we also want there's no warning and that the output is correct. For my tests, I use Capture::Tiny. For core, I have no idea what best practice is

I tend to use fresh_perl_is() and fresh_perl_like().

pod/perlclass.pod Outdated Show resolved Hide resolved
@cjac
Copy link

cjac commented Dec 28, 2022

There are no examples of calling object methods. From within a method of the class, can we just call method() or do we need to call $self->method()? Are they equivalent?

@Ovid
Copy link
Contributor

Ovid commented Dec 30, 2022

@leonerd I think we need an implicit require for :isa(...). I just tried this:

#!/usr/bin/env perl

use lib 'lib';
use v5.37.8;

use HTML::TokeParser::Corinna::Token::Text;

my $token = HTML::TokeParser::Corinna::Token::Text->new( token => [ 'T', 'This is my text' ] ),

That fails with:

Class :isa attribute requires a class but "HTML::TokeParser::Corinna::Token" is not one at lib/HTML/TokeParser/Corinna/Token/Text.pm line 3.

The class I'm trying to instantiate looks like this:

use experimental 'class';

class HTML::TokeParser::Corinna::Token::Text : isa(HTML::TokeParser::Corinna::Token) {
    no warnings 'experimental::builtin';
    use builtin 'true';
    method is_text { true }
}

1;

My expectation is that, this should be like using parent or base: if the class is not loaded, require $class will be called.

@demerphq
Copy link
Collaborator

demerphq commented Dec 30, 2022 via email

@Ovid
Copy link
Contributor

Ovid commented Dec 30, 2022

On Fri, 30 Dec 2022 at 10:44, Ovid @.***> wrote:

class HTML::TokeParser::Corinna::Token::Text : isa(HTML::TokeParser::Corinna::Token) {

Maybe I have been overinfluenced by Rust, but I sure wish that lone ':'
token was as '->' or '=>' instead. It would read much nicer. There are a lot of colons on that line. cheers, Yves

Agreed. It looks kinda ugly, but the KIM grammar we've adopted for the keywords is:

KEYWORD IDENTIFIER MODIFIERS? DEFINITION?

Modifiers are always attributes. So Corinna only offers four new keywords (at this time) and all provide a consistent syntax:

# KEYWORD IDENTIFIER         MODIFIERS?        DEFINITION?
class     Foo                :isa(Bar)         { ... }
field     $foo               :param;
method    foo                :private          { ... }
role      Role::Serializable :does(Role::JSON) { ... }

Once Damian suggested this syntax, it was a unanimous decision by the Corinna team to adopt it (I was pleased, but surprised, by the unanimity). Plus, it mostly leverages what we already have in Perl.

@demerphq
Copy link
Collaborator

demerphq commented Dec 30, 2022 via email

@Ovid
Copy link
Contributor

Ovid commented Dec 30, 2022

@leonerd I don't know if this is intended, but I thought we were also injecting a $class variable into methods. This is failing:

    method normalize_tag {
        my $new_tag = lc $tag;
        return $class->new( token => [ 'E', $new_tag, sprintf "</%s>" => $new_tag ] );
    }

Changing $class to (ref $self) works, but it's ugly.

Section 7.2 of the spec reads:

Instance methods are defined via method $identifier (@args) { ... }. They automatically have immutable $class and $self variables injected into them. $class contains the name of the class from which this method was called. $self is an instance of the current class.

@Ovid
Copy link
Contributor

Ovid commented Dec 30, 2022

Update: Overload works fine. It's the explain that was killing this, damn it :)

Also, is it known that we cannot yet handle overloading?

use experimental 'class';
class HTML::TokeParser::Corinna::Exception {
    use overload '""' => 'to_string', fallback => 1;
    use Devel::StackTrace;

    field $message : param;
    field $stack_trace = Devel::StackTrace->new->as_string;

    method message ()     {$message}
    method stack_trace () {$stack_trace}

    method to_string () {
        return join "\n" => $message, $stack_trace;
    }
}

1;

Later, I have this:

eval { die HTML::TokeParser::Corinna::Exception->new( message => 'foo' ) };
explain $@;

And it gives this warning:

cannot handle ref type 16 at /Users/ovid/perl5/perlbrew/perls/feature-class/lib/5.37.8/darwin-2level/Data/Dumper.pm line 212.

@demerphq
Copy link
Collaborator

demerphq commented Dec 30, 2022 via email

@Ovid
Copy link
Contributor

Ovid commented Dec 30, 2022

@demerphq Doing this:

use Devel::Peek;
eval { die HTML::TokeParser::Corinna::Exception->new( message => 'foo' ) };
Dump($@);

Resulted in this:

SV = PV(0x13200b6a0) at 0x13200adc8
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x1327dd228
  SV = PVOBJ(0x13303d000) at 0x1327dd228
    REFCNT = 1
    FLAGS = (OBJECT)
    STASH = 0x1327dd1f8	"HTML::TokeParser::Corinna::Exception"
    MAXFIELD = 1
    FIELDS = 0x600003216430
    Field No. 0 ($message)
    SV = PV(0x132852e20) at 0x133046030
      REFCNT = 1
      FLAGS = (POK,pPOK)
      PV = 0x600003216280 "foo"\0
      CUR = 3
      LEN = 16
    Field No. 1 ($stack_trace)
    SV = PV(0x132852700) at 0x1326b1090
      REFCNT = 1
      FLAGS = (POK,pPOK)
      PV = 0x131ebc390 "Trace begun at lib/HTML/TokeParser/Corinna/Exception.pm line 9\nHTML::TokeParser::Corinna::Exception::__ANON__('HTML::TokeParser::Corinna::Exception=OBJECT(0x1327dd228)', '** argument not available anymore **') called at t/exceptions.t line 16\neval {...} at t/exceptions.t line 16\n"\0
      CUR = 280
      LEN = 282
  PV = 0x1327dd228 ""
  CUR = 0
  LEN = 0```

@demerphq
Copy link
Collaborator

demerphq commented Dec 30, 2022 via email

@demerphq
Copy link
Collaborator

demerphq commented Dec 30, 2022 via email

{
class Test2 {
my $ok = "OK";
sub NotAMethod { return $ok }
Copy link
Contributor

@rwp0 rwp0 Dec 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it (having my and sub within classes) go away in the future?

What's the use of it? Is it the same as static methods as private methods will have method :private syntax I believe.

It's currently a bit confusing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rwp0 Not sure if I understand your question, but I'll try to answer what I think you're asking.

sub will still be allowed in classes. There's no problem with that, but eventually we'll need work to ensure that subroutines cannot be called as methods.

Also, when the MOP is online, we'll need debugger work to ensure that typing m only shows subroutines, not methods (might be possibly now, but I haven't looked into it).

my will also be allowed in a top-level scope in a class (no sense arbitrarily disallowing it). It will effectively be class data, but as a my variable would be an internal implementation detail rather than a structural part of the class, its use should be strongly discouraged if the anything outside the class is able to use that data.

Copy link
Contributor

@rwp0 rwp0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also maybe worthy to note is that the docs stand out in using British English spelling while the rest of Perl POD is using American English spelling.

cf. initialise -> initialize which is caught by spellcheckers, GitHub editors included.

But I think that's okay because TMTOWTDI (ie. variation in written English).

Spelling difference

=head2 C<ADJUST> Phasers

During compiletime, parsing of an C<ADJUST> phaser is handled in a
fundamentally different way to the the existing perl phasers (C<BEGIN>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fundamentally different way to the the existing perl phasers (C,

Repeated "the"

C<xhv_class_fields> will point to a C<PADNAMELIST> containing C<PADNAME>s,
each being one defined field of the class. They are stored in order of
declaration. Note however, that the index into this array will not necessarily
be equal to the fieldix of each field, because in the case of a subclass, the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better written as: C<fieldix>


#define SVt_PVOBJ

An SV type constant used for comparision with the C<SvTYPE()> macro.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comparision -> comparison

extra i


=item * Metaprogramming

An extension of the metaprogramming API (currently proposed by RFC0022) which
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe link RFC's in POD for better visibility esp. by newcomers (or those who are unaware of the RFC) since googling them is hard.

But, that's unfortunately not an RFC yet, only a PR.

Perl/PPCs#25

leonerd and others added 17 commits February 10, 2023 12:07
Adds a new experimental warning, feature, keywords and enough parsing to
implement basic classes with an empty `new` constructor method.

Inject a $self lexical into method bodies; populate it with the object instance, suitably shifted
Creates a new OP_METHSTART opcode to perform method setup
Define an aux flag to remark which stashes are classes

Basic implementation of fields.

Basic anonymous methods.
Allows non-constant expressions with side effects. Evaluated during the
constructor of each instance.
* perlclass.pod: Document field initialising expressions and :param attribute

* perlclass.pod: Add a TODO section to explain what more is to be added

* Added pod/perlclassguts.pod which contains internal implementation notes and
  details about how the class system works.
@leonerd
Copy link
Contributor Author

leonerd commented Feb 10, 2023

Thanks @rwp0 for the docs review. I believe I've fixed those all up now.

If we're all happy with this I'd like to start a merge sometime soon. My attack plan is probably going to be three rounds of commandline merge --no-ff on each of the three stages, so as to make three new merge commits into blead. Can omit the various "empty" commits while I'm doing that.

Edit: @demerphq's review is still currently in "changes requested" state because of the #ifdef guarding in embed.fnc but I asked on IRC and he's happy for me to ahead.

@leonerd
Copy link
Contributor Author

leonerd commented Feb 10, 2023

This has now been manually merged in stages, so I can close this PR now.

@leonerd leonerd closed this Feb 10, 2023
@demerphq
Copy link
Collaborator

Congrats @leonerd

@abraxxa
Copy link
Contributor

abraxxa commented Feb 20, 2023

@leonerd sorry for the late response but while reading 5.37.9s perlclass, I wondered why you wrote use v5.36 and not use v5.38?

@leonerd
Copy link
Contributor Author

leonerd commented Feb 28, 2023

@abraxxa
use v5.36 enables the signatures that my example uses, but there's nothing else to be added by use v5.38 that would make a difference anyway.

@abraxxa
Copy link
Contributor

abraxxa commented Feb 28, 2023

So Perl 5.38 adds the class and field keywords even without use v5.38?

@rwp0
Copy link
Contributor

rwp0 commented Feb 28, 2023 via email

@ilmari
Copy link
Member

ilmari commented Feb 28, 2023

So Perl 5.38 adds the class and field keywords even without use v5.38?

No, use feature 'class'; (which is experimental, so not in any version bundle) does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.