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

How distinct method with and without public visibility ? #136

Closed
llaville opened this issue Sep 25, 2014 · 15 comments
Closed

How distinct method with and without public visibility ? #136

llaville opened this issue Sep 25, 2014 · 15 comments

Comments

@llaville
Copy link
Contributor

Hello,

I've a problem [1] on my project php-compatinfo that used php-reflect that itself used PHP-Parser 1.0 (stable)

My question is : how can you distinct parsing nodes from code 1 to code 2

code 1

<?php
class Foo
{
    function bar() {}
}

code 2

<?php
class Foo
{
    public function bar() {}
}

Both gave me the same nodes dump

==> Node dump:
array(
    0: Stmt_Class(
        type: 0
        name: Foo
        extends: null
        implements: array(
        )
        stmts: array(
            0: Stmt_ClassMethod(
                type: 1
                byRef: false
                name: bar
                params: array(
                )
                stmts: array(
                )
            )
        )
    )
)

The PhpParser\Node\Stmt\ClassMethod::isPublic return me always TRUE with code1 or code2 (public visibility declared or not)

Thanks in advance for answer.
Laurent

[1] llaville/php-compatinfo#129

@cmb69
Copy link

cmb69 commented Sep 25, 2014

The PhpParser\Node\Stmt\ClassMethod::isPublic return me always TRUE with code1 or code2 (public visibility declared or not)

That is caused by PhpParser\Parser, which defaults to MODIFIER_PUBLIC when no visibility modifier is given. phpparser/grammar/zend_language_parser.php line 481:

variable_modifiers:
      non_empty_member_modifiers                            { $$ = $1; }
    | T_VAR                                                 { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
;

method_modifiers:
      /* empty */                                           { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
    | non_empty_member_modifiers                            { $$ = $1; }
;

ISTM that it is not possible to distinguish an implicit from an explicit public modifier as it is now.

I'll try to come up with a patch; however, I won't be able to make a PR, due to no experience with Git (still stuck with SVN).

@nikic
Copy link
Owner

nikic commented Sep 27, 2014

Yes, the parser does not distinguish between explicit and implicit visibility, because both are equivalent as far as PHP is concerned. It's a formatting difference and the syntax tree does not retain formatting.

The only way to distinguish this currently is by inspecting the tokens. Using the token offset lexer:

<?php

function isImplicitlyPublicProperty(array $tokens, Stmt\Property $prop) {
    $i = $prop->getAttribute('startOffset');
    return isset($tokens[$i]) && $tokens[$i][0] === T_VAR;
}

function isImplicitlyPublicFunction(array $tokens, Stmt\ClassMethod $method) {
    $i = $method->getAttribute('startOffset');
    for ($c = count($tokens); $i < $c; ++$i) {
        $t = $tokens[$i];
        if ($t[0] === T_PUBLIC || $t[0] === T_PROTECTED || $t[0] === T_PRIVATE) {
            return false;
        }
        if ($t[0] === T_FUNCTION) {
            break;
        }
    }
    return true;
}

If you like, I can add support for getting this information directly to the parser, but that would be a temporary solution.

@llaville
Copy link
Contributor Author

@niki I beg your pardon, but I don't see how to use / implement it in real condition.

Could you explain me how you'll connect these two functions ?

@cmb69
Copy link

cmb69 commented Oct 2, 2014

I have set up a Gist to show how isImplicitlyPublicProperty() can be used to distinguish implicit and explicit public properties. However, this uses a terrible hack to get the token list. @nikic: Is there any sane way to get the token list from the lexer or parser? Maybe adding LexerWithTokenOffsets::getTokens()?

@nikic
Copy link
Owner

nikic commented Oct 2, 2014

@cmb69 Yep, that pretty much matches what I had in mind. And yes, getting the tokens using a getTokens() method on the lexer sounds like a fine approach (and is even necessary if you extend the emulative lexer - which has different output than token_get_all). So something like https://gist.github.com/nikic/04fce01e69ae5b7b44f8.

@llaville
Copy link
Contributor Author

llaville commented Oct 3, 2014

@cmb69 and @nikic Thanks to you. I understand much more now the approach, and finally I could do something on my php-reflect project that implement the php-parser.

@llaville
Copy link
Contributor Author

llaville commented Oct 3, 2014

As I'm currently implemented the solution, I'll noticed that there is an error in isImplicitlyPublicFunction function provided by Nikita (probably a copy/paste from isImplicitlyPublicProperty).

So replace line $i = $prop->getAttribute('startOffset'); by $i = $method->getAttribute('startOffset');

@nikic
Copy link
Owner

nikic commented Oct 3, 2014

@llaville Yeah, that was a copy&paste error. I've fixed the code in the comment.

@llaville
Copy link
Contributor Author

llaville commented Oct 3, 2014

@nikic I was pretty glad of the solution after first unit test. But, there is a but, I can't solve, and I need your point of view.

To use the LexerWithTokenOffsets, we need tokens, that are initialized line 55 after the parser line 54. This is what I called the standard process.

Now, the BUT. If you don't parsed again your code, and used a cached version of the AST, like I did in php-reflect ( See lines 115 to 126 ), it does not work anymore.

Any idea to solve a "cache" problem implementation ?
Hope my explain are enough.

@llaville
Copy link
Contributor Author

llaville commented Oct 3, 2014

If you want to see result of my implementation. Have a look on the TokenOffsets branch.

See changes at commit llaville/php-reflect@4daccd5

Be carefull, this implementation run fine only if you don't add the cache plugin. (In my previous comment lines 115 to 126).

@nikic
Copy link
Owner

nikic commented Oct 3, 2014

@llaville Doesn't that just mean that you need to cache both the tokens and the AST?

@llaville
Copy link
Contributor Author

llaville commented Oct 6, 2014

@niki Thanks for your answer. I was away this week-end, so I reply only now.

Finally I've adopted another solution. To avoid lot of impacts and problem, with cached AST results, I read online the tokens list.

And I got expected results.

Your solution works as expected, so I'll close this issue.

@llaville llaville closed this as completed Oct 6, 2014
@cmb69
Copy link

cmb69 commented Oct 6, 2014

@llaville

Finally I've adopted another solution.

As @nikic has explained, using token_get_all() will not necessarily work when the emulative lexer (\PhpParser\Lexer\Emulative) is used:

and is even necessary if you extend the emulative lexer - which has different output than token_get_all

@llaville
Copy link
Contributor Author

llaville commented Oct 6, 2014

@cmb69 Have you tested the previous solution without the commit ?

On first run it works, but on second run if you've the cachePlugin it fails !

NOTE: the TokenOffesets lexer used extends the Emulative lexer

@cmb69
Copy link

cmb69 commented Oct 6, 2014

@llaville

Have you tested the previous solution without the commit ?

No. Unfortunately, I was not able to install the dev version of PHP_Reflect due to a dependency mismatch with PHP_CompatInfo reported by Composer.

On first run it works, but on second run if you've the cachePlugin it fails !

That's most likely due to the incompatibility of token_get_all() and Lexer\Emulative.

Can't you add another field "tokens" to the event object (besides "ast") on the first run, and retrieve and use this information in subsequent runs?

Anyway, these details might better be discussed in the respective PHP_Reflect issue.

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

No branches or pull requests

3 participants