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

Allow type inference on dynamic ClassConstFetch #6321

Merged
merged 6 commits into from
Aug 19, 2021

Conversation

orklah
Copy link
Collaborator

@orklah orklah commented Aug 16, 2021

fix #5096 (almost)

I had to make the class in the example final to keep consistency with the code above. The whole block of code is copy/pasted from above with a few changes that makes it hard to refactor, even more given the code must be placed after analyzing the expression.

@orklah orklah force-pushed the analyze-dynamic-classConstFetch branch from 38a84da to 7308c86 Compare August 16, 2021 20:24
@orklah
Copy link
Collaborator Author

orklah commented Aug 16, 2021

Note: I tried to refactor. I was pretty happy with the result but there was still a ComplexMethod issue... I don't get what it checks.
The CI was failing anyway, the refactor was broken.

@weirdan
Copy link
Collaborator

weirdan commented Aug 17, 2021

there was still a ComplexMethod issue... I don't get what it checks.

Intuitively, it's a number of paths through the method, given that extracting a method with a couple of conditionals from the top of the original method makes the most impact on the metrics it reports.

@weirdan
Copy link
Collaborator

weirdan commented Aug 17, 2021

(almost)

Is there's something missing?

@orklah
Copy link
Collaborator Author

orklah commented Aug 17, 2021

Yeah, I explained below that the given example was still giving a mixed. This is due to the class in the example not being final.

However, I just checked again and the final thing was only applied for static:: so I don't think we should apply it when we have the type. I got rid of it so the original example should be completely solved

@weirdan
Copy link
Collaborator

weirdan commented Aug 17, 2021

Hm, I don't think we should allow constant type inference on non-final classes when accessing them dynamically. Consider this code:

<?php

class A
{
    public const C = 1;
}

class B extends A
{
    public const C = "one";
}

function f(A $a): int
{
    return $a::C;
}

f(new B);

It passes with flying colors on your branch, but results in runtime errors: https://3v4l.org/F6nbs

Master produces more sensible mixed result: https://psalm.dev/r/97a2a87c7c

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/97a2a87c7c
<?php

class A
{
    public const C = 1;
}

class B extends A
{
    public const C = "one";
}

function f(A $a): int
{
    return $a::C;
}

f(new B);
Psalm output (using commit 9e1f7ad):

INFO: MixedReturnStatement - 15:12 - Could not infer a return type

INFO: MixedInferredReturnType - 13:19 - Could not verify return type 'int' for f

@weirdan
Copy link
Collaborator

weirdan commented Aug 17, 2021

Unless constant is inherited from an interface, as those cannot be overridden.

@orklah
Copy link
Collaborator Author

orklah commented Aug 18, 2021

I have to think more about that. The thing that bothers me is that in the two tests I added, the type of the class is known with certainty, it can't be a child of the object so I feel like Psalm should know that and not require the class to be final. I'm really not sure Psalm keeps tabs on that sort of thing though and I wonder how hard it could be to add that.

@orklah
Copy link
Collaborator Author

orklah commented Aug 18, 2021

I tried introducing a new flag to make Psalm aware of case where the type of the class is known not to be a child. It seems to be working well. I didn't try to cover every case where the type is known, I think it's something we can refine on the go.

I added the flag to TNamedObject and TLiteralClassString but I think it may be useful in TClassString too (in some edge case where we do ::class on something we don't know the type of). For now I declared two separate properties, but it may have been better with a Trait (I don't really like traits...)

tests/ConstantTest.php Show resolved Hide resolved
src/Psalm/Type/Atomic/TNamedObject.php Outdated Show resolved Hide resolved
tests/ConstantTest.php Outdated Show resolved Hide resolved
Copy link
Collaborator

@weirdan weirdan left a comment

Choose a reason for hiding this comment

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

I think I have one last nitpick - the default value for $definite_class. There's quite a few of direct instantiations of both types, and a couple of calls to getLiteralClassString() that omit the argument. Historically we mostly treated TNamedObject as something that allows descendants - so shouldn't we default that flag to false everywhere, for both types, and only enable it where we know we want it?

@orklah
Copy link
Collaborator Author

orklah commented Aug 19, 2021

Oh, my bad, the wording flipped the whole meaning and I didn't update values. Before, it was true when it could be a child, now it should be true when it can't

@orklah orklah force-pushed the analyze-dynamic-classConstFetch branch from ce6df92 to dbf3512 Compare August 19, 2021 21:20
@weirdan
Copy link
Collaborator

weirdan commented Aug 19, 2021

This is safe (interface constants cannot be overridden) but is currently not inferred:

interface A
{
    public const C = 1;
}

function f(A $a): int
{
    return $a::C;
}

It can be fixed separately though.

@orklah
Copy link
Collaborator Author

orklah commented Aug 19, 2021

Yeah, I saw the issue you made. I think this PR is big enough, let's merge that and we'll check that later

@weirdan weirdan merged commit 9222b24 into vimeo:master Aug 19, 2021
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.

Object instance's string constants cannot be used as array keys with other hard-coded strings
2 participants