Skip to content

Add Attributes based on attributes_v2 RFC. #5394

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

Closed
wants to merge 11 commits into from

Conversation

beberlei
Copy link
Contributor

@beberlei beberlei commented Apr 15, 2020

@beberlei beberlei force-pushed the attributes_v2_rfc branch 5 times, most recently from 7bbc1db to 6bf867a Compare April 15, 2020 16:54
@beberlei beberlei mentioned this pull request Apr 16, 2020
9 tasks
Copy link
Contributor

@Ocramius Ocramius left a comment

Choose a reason for hiding this comment

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

This looks very good to me! It's flexible, a bit error prone (when forgetting imports), but extremely powerful.

It has potentially to finally kill docblocks, so that's a big 👍 from me :)

I'm missing nested attributes, but I think those could be added at a later point in time.

@kocsismate kocsismate added the RFC label Apr 16, 2020
@garygreen

This comment has been minimized.

@cebe

This comment has been minimized.

@garygreen

This comment has been minimized.

@kooldev

This comment has been minimized.

@nikic
Copy link
Member

nikic commented Apr 17, 2020

Please keep pull request discussions focused on implementation concerns. If you have general comments on an RFC, please make them on the PHP internals mailing list instead.

@AnrDaemon

This comment has been minimized.

@vanillajonathan

This comment has been minimized.

@SerafimArts
Copy link
Contributor

SerafimArts commented Apr 25, 2020

I apologize for offtopic, but is there a build in CI for Windows (https://windows.php.net/downloads/snaps/) with this PR or somewhere else? Or it makes sense to just wait for the merge?

// I can’t compile it at the moment

@Ocramius
Copy link
Contributor

@SerafimArts latest CI build targeting windows is at https://ci.appveyor.com/project/php/php-src/builds/32298449

@SerafimArts
Copy link
Contributor

@Ocramius I meant binaries (artifacts)

@moliata
Copy link
Contributor

moliata commented Apr 27, 2020

Description

It seems that the validation of <<PhpAttribute>> doesn't work.

Test script

<?php
declare(strict_types=1);

class Test {
        public string $name;

        public function __construct(string $name) {
                $this->name = $name;
        }
}

<<Test('test')>>
function test(): void {}

$attrs = (new ReflectionFunction('test'))->getAttributes();

echo $attrs[0]->newInstance()->name . PHP_EOL;

Expected behavior

Fatal error: Attempting to use class 'Test' as attribute that does not have <<PhpAttribute>>.

Actual behavior

test

@beberlei
Copy link
Contributor Author

@moliata validation only happens when you call ->newInstance() on an attribute. This is specified in the RFC.

@moliata
Copy link
Contributor

moliata commented Apr 27, 2020

@moliata validation only happens when you call ->newInstance() on an attribute. This is specified in the RFC.

I'm not sure if I correctly understand you but I am using newInstance() in the test script.

@beberlei
Copy link
Contributor Author

@moliata ah sorry, weird that i didn't see that, i only saw the getAttributes line.

@moliata
Copy link
Contributor

moliata commented Apr 27, 2020

@beberlei after doing some tests, it seems I can trigger the check only if the class has some kind of an attribute. For example, let's reuse the same test script I used in my bug report and add something like this:

<?php
declare(strict_types=1);

// Adding any other than the <<PhpAttribute>> attribute to
// the class makes the check work and raises a fatal error
<<TestAttribute>>
class Test {
        public string $name;

        public function __construct(string $name) {
                $this->name = $name;
        }
}

<<Test('test')>>
function test(): void {}

$attrs = (new ReflectionFunction('test'))->getAttributes();

echo $attrs[0]->newInstance()->name . PHP_EOL;

EDIT: fixed a TYPO


echo "\n";

<<A1(self::FOO, C1::BAR)>>
Copy link
Member

Choose a reason for hiding this comment

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

There is a bit of ambiguity here when it comes to cases like this:

class Test {
    public function method() {
        return new <<A(self::class)>> class {};
    }
}

Here self could intuitively both refer to Test and to the anonymous class.

More ambiguous cases are:

<<A(self::class)>>
trait T {}

In traits, self generally refers to the using class, not the trait. It's not clear how this is supposed to work.

$fn = <<A(self::class)>> function() {};
$fn->bindTo(null, X::class);

In closures, self refers to the bound scope of the closure. Again, it's not entirely obvious what the result is.

The behavior for these cases doesn't seem to be specified in the RFC.

Copy link
Member

Choose a reason for hiding this comment

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

FWIW I expect that just adding tests with current behavior will yield something that is pretty reasonable. Making the scope the scope of the entity on which the attribute is declared is necessary to make things like accessing private class constants work (which it probably should?) so there might not be much real choice here.

zend_class_entry *zend_ce_php_compiler_attribute;

typedef void (*zend_attributes_internal_validator)(zval *attribute, int target);
HashTable zend_attributes_internal_validators;
Copy link
Member

Choose a reason for hiding this comment

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

This should be part of compiler_globals or so.

@@ -227,16 +227,16 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
/* Token used to force a parse error from the lexer */
%token T_ERROR

%type <ast> top_statement namespace_name name statement function_declaration_statement
%type <ast> top_statement namespace_name name statement annotated_statement function_declaration_statement
Copy link
Member

Choose a reason for hiding this comment

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

A bit of terminology mix here with annotations vs attributes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Naming things is hard... would attributed_statement be a better fit? In addition there are annotated_class_statement and annotated_parameter that should also be renamed. Any suggestions as far as new names are concerned?

@beberlei
Copy link
Contributor Author

@kooldev done.

@kooldev
Copy link
Contributor

kooldev commented May 23, 2020

@beberlei You need to adjust zend_execute.c and add another NULL, after arg_info (line 140) in the struct initializer. This is needed because you extended the zend_internal_function structure.

@beberlei beberlei force-pushed the attributes_v2_rfc branch 3 times, most recently from 3bf85a2 to 4cc905c Compare May 24, 2020 20:33
@beberlei
Copy link
Contributor Author

@nikic We are ready for final review (hopefully :))

The only thing that is not adressed from your previous comments is moving internal_validators global HashTable to compiler_globals.

<<Entity("imported class")>>
<<ORM\Entity("imported namespace")>>
<<\Doctrine\ORM\Mapping\Entity("absolute from namespace")>>
<<\Entity("import absolute from global")>>
Copy link
Member

Choose a reason for hiding this comment

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

If you want to be complete, there's also <<namespace\Entity()>>.

Copy link
Member

Choose a reason for hiding this comment

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

I meant this one a bit more literally: namespace\Entity, with the namespace keyword is a namespace-relative name.

RETURN_THROWS();
}

if (!zend_get_attribute_str(ce->attributes, ZEND_STRL("phpattribute"))) {
Copy link
Member

Choose a reason for hiding this comment

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

We might want to add a ZEND_ACC_ATTRIBUTE flag instead of this check.

@beberlei
Copy link
Contributor Author

beberlei commented May 25, 2020

Open todos are:

  • Check scope resolution test, some funky things happening there with bindings, missing bindTo tests.
  • ZVAL_COPY suggestion in php_reflection.c memory leaks
  • attributes part of child array in zend_ast_decl
  • IS_SERIALIZED / IS_UNSERIALIZED in file opcache
  • Add a test for an attribute on an internal class/function

@kooldev
Copy link
Contributor

kooldev commented May 29, 2020

@beberlei All builds failed due to use of typed properties in zend_exceptions.c in lines 742 and 747. This has been committed yesterday. For some reason CI builds seem to use code from latest master..? You need to rebase attributes_v2_rfc on latest master and add a NULL, argument before use of (zend_type) in calls to zend_declare_typed_property() to fix compiler errors.

@nikic
Copy link
Member

nikic commented May 29, 2020

@kooldev You might want to not touch the zend_declare_property_ex and zend_declare_typed_property APIs at all, and instead add an additional lower-level zend_declare_attributed_property.

Copy link
Member

@nikic nikic left a comment

Choose a reason for hiding this comment

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

Looks good to me. Should be fine to land once CI green.

@kooldev
Copy link
Contributor

kooldev commented May 31, 2020

@nikic How about providing additional functions to do attribute registration and automatic HashTable initialization in a unified way? I implemented it that way in my attributes PR to avoid making changes to existing function signatures. I would like to add these function in zend_attributes.h:

ZEND_API zend_attribute *zend_add_class_attribute(zend_class_entry *ce, zend_string *name, uint32_t argc);
ZEND_API zend_attribute *zend_add_function_attribute(zend_function *func, zend_string *name, uint32_t argc);
ZEND_API zend_attribute *zend_add_parameter_attribute(zend_function *func, uint32_t offset, zend_string *name, uint32_t argc);
ZEND_API zend_attribute *zend_add_property_attribute(zend_class_entry *ce, zend_property_info *info, zend_string *name, uint32_t argc);
ZEND_API zend_attribute *zend_add_class_constant_attribute(zend_class_entry *ce, zend_class_constant *c, zend_string *name, uint32_t argc);

And provide implementations like that in zend_attributes.c:

static void attribute_ptr_dtor(zval *v)
{
	zend_attribute_free((zend_attribute *) Z_PTR_P(v), 1);
}

ZEND_API zend_attribute *zend_add_class_attribute(zend_class_entry *ce, zend_string *name, uint32_t argc)
{
    zend_bool persistent = (ce->type == ZEND_INTERNAL_CLASS);

    if (ce->attributes == NULL) {
        ce->attributes = pemalloc(sizeof(HashTable), persistent);
        zend_hash_init(ce->attributes, 8, NULL, attribute_ptr_dtor, persistent);
    }

    zend_attribute *attr = pemalloc(ZEND_ATTRIBUTE_SIZE(argc), persistent);
    attr->name = zend_string_dup(name, persistent);
    attr->lcname = zend_string_tolower_ex(attr->name, persistent);
    attr->offset = 0;
    attr->argc = argc;

    zend_hash_next_index_insert_ptr(ce->attributes, attr);

    return attr;
}

@nikic
Copy link
Member

nikic commented Jun 3, 2020

@kooldev That looks reasonable to me. Will probably make the compiler code a bit uglier...

attr->name = zend_string_dup(name, persistent);

I'd instead assert that the input has the right persistence type.

@beberlei beberlei force-pushed the attributes_v2_rfc branch from 20aceb5 to 8697ccf Compare June 4, 2020 09:57
Copy link
Member

@nikic nikic left a comment

Choose a reason for hiding this comment

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

LG assuming CI green.

@nikic
Copy link
Member

nikic commented Jun 4, 2020

CI is green, the Azure failure is unrelated.

@beberlei
Copy link
Contributor Author

beberlei commented Jun 4, 2020

Merged in a7908c2

@beberlei beberlei closed this Jun 4, 2020
@carusogabriel carusogabriel added this to the PHP 8.0 milestone Jun 7, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.