Skip to content

Conversation

@Nek-
Copy link
Contributor

@Nek- Nek- commented Apr 17, 2025

Note about the design: I created a new sub-api for type management because the current one is not compatible with intersections but needs to exist to keep backward compatibility.

What still needs to be done:

  • Fix existing bugs/tests with this implementation (god it's hard to fix)
  • Add deprecation about the old API (some comments already existing in the code will help!)
  • Refactor to remove deprecated calls
  • Implement code generation with intersection
  • Add tests regarding intersection
  • Implement intersection support & dnf for methods args

Please have a look and tell me if this kind of implementation would be ok for you. Thanks!

Stands as replacement for #569 and should fix #535 and #558


Update: the PR is ready and waiting to be merged :)

{
if ($type instanceof ReflectionIntersectionType) {
foreach ($type->getTypes() as $innerReflectionType) {
$innerTypes[] = new SimpleType($innerReflectionType->getName());
Copy link
Member

Choose a reason for hiding this comment

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

isn't this missing the resolution of the type name ?

Copy link
Member

Choose a reason for hiding this comment

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

maybe this should use createTypeFromReflection for the inner type as well.

/**
* @param string|TypeInterface ...$types
*/
public function __construct(string|TypeInterface ...$types)
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't the new API allows null as well ?

Copy link
Contributor Author

@Nek- Nek- Apr 17, 2025

Choose a reason for hiding this comment

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

Yep the new API will be null|string|TypeInterface $type = null, you're absolutely right

Copy link
Member

Choose a reason for hiding this comment

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

Then this needs to be updated to support passing null

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad it should be new BuiltinType('null') in the new API.

Copy link
Member

Choose a reason for hiding this comment

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

I'm not talking about the case where of using null as return type, but of the case of not having a return type at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

new ReturnTypeNode() will work for this (just like it was before). Do you want the new API to support new ReturnTypeNode(null) ? (I though it was a good idea, not sure about it now)

Copy link
Member

Choose a reason for hiding this comment

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

yes, new ReturnTypeNode(null) should work. Otherwise, we won't be able to remove the BC layer in the next version anyway (where we don't want to have a variadic argument anymore)

}
}

if (\PHP_VERSION_ID >= 80000 && isset($this->types['mixed']) && count($this->types) !== 1) {
Copy link
Member

Choose a reason for hiding this comment

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

why removing this ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Question is more like why is this method guardIsValidType still here. I will remove it on cleanup. The UnionType checks this mixed-thing already.

}

/**
* @todo: put this in SimpleType
Copy link
Member

Choose a reason for hiding this comment

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

This cannot really go in SimpleType, as some of those are about the full type, not each simple type used in a union

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess each type will have its own exceptions, this still needs to be done.

* @todo: put this in SimpleType
* @return void
*/
protected function guardIsValidType()
Copy link
Member

Choose a reason for hiding this comment

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

Be careful. ReturnTypeNode extends this class to override this method, but you still use $this->types in it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After cleanup this method should not even exist anymore here.

@Nek- Nek- force-pushed the intersection-types branch from 5250a7c to ebb7746 Compare April 25, 2025 23:51
@Nek-
Copy link
Contributor Author

Nek- commented Apr 26, 2025

Thank you for your review @stof . And good news! I fixed the bugs in my previous implementation. (some you noticed in your review!) Keeping backward compatibility has been a real pain! But it WORKS. (test suite green 100%)

It's currently a fully working PR for intersection type.... But only for return types!

Some work needs to be done for cleaning... But it also needs php to be bumped to 8.1+ (which would probably be better in another PR), and a lot of cleaning.

But for now, since it's a lot of work and personal investment, I'd like you to tell me if the implementation is ok before working on it again.

@Nek- Nek- force-pushed the intersection-types branch from e0672c9 to facea20 Compare April 26, 2025 12:50
@Nek- Nek- changed the title Implement intersection types Intersection types Apr 26, 2025
{
/** @var array<string, string> */
protected $types = [];
protected TypeInterface|null $type;
Copy link
Member

Choose a reason for hiding this comment

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

I suggest making the new property private (if child classes need to read it, there is a public getter they can use)

/**
* @param string|TypeInterface ...$types
*/
public function __construct(string|TypeInterface ...$types)
Copy link
Member

Choose a reason for hiding this comment

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

Then this needs to be updated to support passing null


if ($type instanceof UnionType) {
return join('|', array_map(
fn (TypeInterface $type) => $this->generateSubType($type),
Copy link
Member

Choose a reason for hiding this comment

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

this logic does not support DNF types. Parenthesis are required around the intersection types used in a union type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, and fixed!

@Nek- Nek- force-pushed the intersection-types branch from facea20 to 55a06b0 Compare May 14, 2025 18:47
@Nek-
Copy link
Contributor Author

Nek- commented Jun 10, 2025

@stof I updated the PR following your insights, thank you for the detailed review. ⭐

I still need to track deprecated calls and add deprecation triggers, and I will do it promptly as soon as you validate that new implementation.

Copy link
Member

@stof stof left a comment

Choose a reason for hiding this comment

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

The general approach looks good. I still recommend changing the way we manage the source code representation of a given type.
Please start working on the deprecation layer as well.

$this->types[$type] = $type;
}
$deprecation = 'Only 1 type will be supported in the future, strings are no longer supported as type.';
if (count($types) === 1 && $types[0] instanceof TypeInterface) {
Copy link
Member

Choose a reason for hiding this comment

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

we should accept count($types) === 1 && $types[0] === null as well, to assign null in the property


private function prefixWithNsSeparator(string $type): string
{
// Avoid double-prefixing if already prefixed
Copy link
Member

Choose a reason for hiding this comment

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

I think the new API should not deal with that, expecting the unprefixed class name to be passed instead (which might be guarded to avoid issues). The BC layer in TypeNodeAbstract would have to handle the BC layer.

Copy link
Member

Choose a reason for hiding this comment

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

I'm unmarking this as resolved, because you kept this logic (without any answer about why my comment got ignored)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is done! This method is only for the __toString() method.

Copy link
Member

Choose a reason for hiding this comment

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

If the ObjectType is implemented as expecting class-string, we won't care about a type starting with \, as that won't happen. So __toString should just be return '\\'.$this->type;

And that's also solving the case of equals which currently treat new ObjectType('stdClass') and new ObjectType('\stdClass') as not equal.

@Nek-
Copy link
Contributor Author

Nek- commented Jun 10, 2025

I still recommend changing the way we manage the source code representation of a given type.

Please explain what you mean because I refactored precisely with your recommandation here (the "ObjectType" vs "BuiltinType" thing in particular)

@stof
Copy link
Member

stof commented Jun 10, 2025

@Nek- that's what I detailed more in #637 (comment) and #637 (comment)

public function it_can_not_double_intersection_return_types(): void
public function it_can_double_intersection_return_types(): void
{
if (PHP_VERSION_ID < 80100) {
Copy link
Member

Choose a reason for hiding this comment

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

looks like those version checks were not properly cleaned when bumping the min PHP version.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe to be done in a separated work?

Nek- added a commit to Nek-/prophecy that referenced this pull request Jul 14, 2025
@Nek-
Copy link
Contributor Author

Nek- commented Jul 16, 2025

@stof I'm still working on this!

Removing deprecated usages is a new mini challenge and made me discover some features I forgot on the way, I will add according tests as well.

But I have an issue with triggering deprecation notice. It does makes the phpspec test suite fail, I can't make it pass with the trigger working. (I guess I'm not the first one hitting this issue since other deprecated methods do not throw deprecation error)
Is it ok to you if I push those deprecations commented?

@stof
Copy link
Member

stof commented Jul 22, 2025

Indeed, phpspec does not have a good handling of deprecations. I opened phpspec/phpspec#1494 about it.

reading the code of phpspec, I think the pattern used in symfony/deprecation-contracts (triggering a silenced deprecation) would work in phpspec (phpspec itself won't report it, but it will benefit people using phpunit with a config reporting them)

@Nek-
Copy link
Contributor Author

Nek- commented Jul 22, 2025

reading the code of phpspec, I think the pattern used in symfony/deprecation-contracts (triggering a silenced deprecation) would work in phpspec (phpspec itself won't report it, but it will benefit people using phpunit with a config reporting them)

Awesome. I will use this. Thanks!

@Nek- Nek- force-pushed the intersection-types branch from 9e5062b to 3cef15d Compare July 23, 2025 23:18
@Nek-
Copy link
Contributor Author

Nek- commented Jul 23, 2025

@stof finally something mergeable 😅

Thank you so much for your critical reviews. (I secretly expect to get some more again haha)

@Nek- Nek- marked this pull request as ready for review August 11, 2025 13:17
@Nek-
Copy link
Contributor Author

Nek- commented Sep 8, 2025

@stof I updated the PR it's waiting for your review. 🙏


private function prefixWithNsSeparator(string $type): string
{
// Avoid double-prefixing if already prefixed
Copy link
Member

Choose a reason for hiding this comment

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

I'm unmarking this as resolved, because you kept this logic (without any answer about why my comment got ignored)

Nek- added a commit to Nek-/prophecy that referenced this pull request Sep 10, 2025
@Nek- Nek- force-pushed the intersection-types branch from 0d5c75e to 1a5f210 Compare September 10, 2025 10:30
@Nek- Nek- force-pushed the intersection-types branch 2 times, most recently from d7098f6 to 876f8ee Compare September 10, 2025 16:04
@Nek-
Copy link
Contributor Author

Nek- commented Sep 11, 2025

@stof I think I did all your review. Do you want me to squash and detail a bit inside the commit what happen here?

@stof
Copy link
Member

stof commented Sep 26, 2025

Do you want me to squash and detail a bit inside the commit what happen here?

yes, that would be great

@Nek- Nek- force-pushed the intersection-types branch from 876f8ee to d044dbb Compare September 29, 2025 12:32
@Nek-
Copy link
Contributor Author

Nek- commented Sep 29, 2025

@stof done!

- Adds a new layer for Types inside the generator (with keeping the old one for BC reasons!)
- Introduce a way of triggering deprecation without making phpspec failing by adding
  symfony/deprecation-contracts to the project
- Update all internal usages to make prophecy use its own new API
@stof stof force-pushed the intersection-types branch from d044dbb to d6d9103 Compare September 29, 2025 13:23
@stof stof merged commit 222e40d into phpspec:master Sep 29, 2025
8 checks passed
@stof
Copy link
Member

stof commented Sep 29, 2025

It took time, but this is finally merged. Thanks a lot @Nek- !

@Nek- Nek- deleted the intersection-types branch September 29, 2025 14:48
@julienbornstein
Copy link

Thanks @Nek- (nice to see you again)

This was referenced Oct 6, 2025
@Shivoham
Copy link

Thanks @Nek- !

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.

Intersection types in generated code

4 participants