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

Make VisitorArray shape explicit #1675

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open

Make VisitorArray shape explicit #1675

wants to merge 9 commits into from

Conversation

ruudk
Copy link
Contributor

@ruudk ruudk commented Feb 26, 2025

When creating a node visitor that uses more specific types, we get this error:

Parameter #2 $visitor of static method GraphQL\Language\Visitor::visit() expects array<string, array<string, callable(GraphQL\Language\AST\Node): (GraphQL\Language\VisitorOperation|void|false|null)>|(callable(GraphQL\Language\AST\Node): (GraphQL\Language\VisitorOperation|void|false|null))>, array{OperationDefinition: array{enter: Closure(GraphQL\Language\AST\OperationDefinitionNode): void, leave: Closure(): void}, Field: array{enter: Closure(GraphQL\Language\AST\FieldNode): void, leave: Closure(GraphQL\Language\AST\FieldNode): void}, InlineFragment: array{enter: Closure(GraphQL\Language\AST\InlineFragmentNode): void, leave: Closure(GraphQL\Language\AST\InlineFragmentNode): void}, FragmentDefinition: array{enter: Closure(GraphQL\Language\AST\FragmentDefinitionNode): void, leave: Closure(GraphQL\Language\AST\FragmentDefinitionNode): void}, SelectionSet: Closure(GraphQL\Language\AST\SelectionSetNode): void} given.

For the following code:

$ast = Visitor::visit($ast, [
                NodeKind::OPERATION_DEFINITION => [
                    'enter' => function (OperationDefinitionNode $node) : void {
                        $this->parents[] = $node->operation;

                        // Remove the operation name as we don't need this
                        $node->name = null;
                    },
                    'leave' => function () : void {
                        array_pop($this->parents);
                    },
                ],
                NodeKind::FIELD => [
                    'enter' => function (FieldNode $node) : void {
                        $this->parents[] = $node->name->value;
                    },
                    'leave' => function (FieldNode $node) : void {
                        array_pop($this->parents);
                    },
                ],
 // ...

When creating a node visitor that uses more specific types, we get this error:

```
Parameter webonyx#2 $visitor of static method GraphQL\Language\Visitor::visit() expects array<string, array<string, callable(GraphQL\Language\AST\Node): (GraphQL\Language\VisitorOperation|void|false|null)>|(callable(GraphQL\Language\AST\Node): (GraphQL\Language\VisitorOperation|void|false|null))>, array{OperationDefinition: array{enter: Closure(GraphQL\Language\AST\OperationDefinitionNode): void, leave: Closure(): void}, Field: array{enter: Closure(GraphQL\Language\AST\FieldNode): void, leave: Closure(GraphQL\Language\AST\FieldNode): void}, InlineFragment: array{enter: Closure(GraphQL\Language\AST\InlineFragmentNode): void, leave: Closure(GraphQL\Language\AST\InlineFragmentNode): void}, FragmentDefinition: array{enter: Closure(GraphQL\Language\AST\FragmentDefinitionNode): void, leave: Closure(GraphQL\Language\AST\FragmentDefinitionNode): void}, SelectionSet: Closure(GraphQL\Language\AST\SelectionSetNode): void} given.
```

For the following code:
```php
$ast = Visitor::visit($ast, [
                NodeKind::OPERATION_DEFINITION => [
                    'enter' => function (OperationDefinitionNode $node) : void {
                        $this->parents[] = $node->operation;

                        // Remove the operation name as we don't need this
                        $node->name = null;
                    },
                    'leave' => function () : void {
                        array_pop($this->parents);
                    },
                ],
                NodeKind::FIELD => [
                    'enter' => function (FieldNode $node) : void {
                        $this->parents[] = $node->name->value;
                    },
                    'leave' => function (FieldNode $node) : void {
                        array_pop($this->parents);
                    },
                ],
 // ...
```

By changing the signature to `covariant` the problem goes away.
@ruudk
Copy link
Contributor Author

ruudk commented Feb 27, 2025

I guess this doesn't work. When I tested it, it produced no error, but that's probably because PHPStan skipped the error completely.

@ruudk ruudk closed this Feb 27, 2025
@spawnia
Copy link
Collaborator

spawnia commented Feb 27, 2025

In order to make this work, we would have to define an array shape for the visitor. Something like this:

@phpstan-type VisitorArray array{
    OperationDefinition?: array{
        enter?: callable(OperationDefinitionNode): (VisitorOperation|null|false|void),
        leave?: callable(OperationDefinitionNode): (VisitorOperation|null|false|void),
    }|callable(OperationDefinitionNode): (VisitorOperation|null|false|void),
    Field?: ...

How badly do you want this? 😉

@ruudk
Copy link
Contributor Author

ruudk commented Feb 27, 2025

If you would accept that, I can do it 😊

@spawnia
Copy link
Collaborator

spawnia commented Feb 27, 2025

Yeah, I am fine with it. It should allow use to get rid of some @phpstan-ignore we have around Visitor::visit as well, and offer a better typesafe API for users. I just hope we don't run into some kind of limit PHPStan has around type complexity. Let's see!

@ruudk ruudk reopened this Feb 27, 2025
@ruudk ruudk changed the title Make NodeVisitor Node argument covariant Make VisitorArray shape explicit Feb 27, 2025
@ruudk
Copy link
Contributor Author

ruudk commented Feb 27, 2025

These docs / cs fixer pushes are handy and annoying at the same time... they don't trigger CI to run.. So I have to pull them , force push it.

@spawnia
Copy link
Collaborator

spawnia commented Feb 27, 2025

CI does run for the previously pushed commit, you can check the results there.

image

This makes it easier to work with NodeList, if you don't care about it's type.
@ruudk
Copy link
Contributor Author

ruudk commented Feb 27, 2025

After this change, PHPStan time shoots from 30 seconds to multiple minutes on CI 😭

@spawnia spawnia added the bug label Feb 27, 2025
@spawnia
Copy link
Collaborator

spawnia commented Feb 27, 2025

After this change, PHPStan time shoots from 30 seconds to multiple minutes on CI 😭

I am not sure if it is worth the added complexity and worse development experience then. I imagine it would also slow down PHPStan for consumers of the library significantly?

@ruudk
Copy link
Contributor Author

ruudk commented Feb 28, 2025

Maybe @ondrejmirtes has some insights on how to solve this type of problem. I tried searching for this, but I probably use the wrong terminology so couldn't find similar things.

@ondrejmirtes
Copy link

Please formulate the question by providing full context, I have no idea what's going on here.

@ruudk
Copy link
Contributor Author

ruudk commented Feb 28, 2025

You're right, I feel bad for not giving you the proper context when pinging you. My apologies.

This library has an AST visitor that works like this:

* $editedAST = Visitor::visit($ast, [
* 'enter' => function ($node, $key, $parent, $path, $ancestors) {
* // ...
* },
* 'leave' => function ($node, $key, $parent, $path, $ancestors) {
* // ...
* }
* ]);
*
* Alternatively to providing `enter` and `leave` functions, a visitor can
* instead provide functions named the same as the [kinds of AST nodes](class-reference.md#graphqllanguageastnodekind),
* or enter/leave visitors at a named key, leading to four permutations of
* visitor API:
*
* 1. Named visitors triggered when entering a node a specific kind.
*
* Visitor::visit($ast, [
* 'Kind' => function ($node) {
* // enter the "Kind" node
* }
* ]);
*
* 2. Named visitors that trigger upon entering and leaving a node of
* a specific kind.
*
* Visitor::visit($ast, [
* 'Kind' => [
* 'enter' => function ($node) {
* // enter the "Kind" node
* }
* 'leave' => function ($node) {
* // leave the "Kind" node
* }
* ]
* ]);
*
* 3. Generic visitors that trigger upon entering and leaving any node.
*
* Visitor::visit($ast, [
* 'enter' => function ($node) {
* // enter any node
* },
* 'leave' => function ($node) {
* // leave any node
* }
* ]);
*
* 4. Parallel visitors for entering and leaving nodes of a specific kind.
*
* Visitor::visit($ast, [
* 'enter' => [
* 'Kind' => function($node) {
* // enter the "Kind" node
* }
* },
* 'leave' => [
* 'Kind' => function ($node) {
* // leave the "Kind" node
* }
* ]
* ]);

For example:

$ast = Visitor::visit($ast, [
    NodeKind::OPERATION_DEFINITION => [
        'enter' => function ($node) : void {
            // $node is OperationDefinitionNode
        },
    ],
    NodeKind::FIELD => [
        'enter' => function ($node) : void {
            // $node is FieldNode
        },
    ],
]);

Depending on the node kind you add the visitor for, you get the corresponding Node type.

I want to write my visitor like this:

$ast = Visitor::visit($ast, [
    NodeKind:: OPERATION_DEFINITION => [
        'enter' => function (OperationDefinitionNode  $node) : void {},
    ],
    NodeKind::FIELD => [
        'enter' => function (FieldNode  $node) : void {},
    ],
]);

But that doesn't work because you get an error like this:

Parameter #2 $visitor of static method GraphQL\Language\Visitor::visit() expects array<string, array<string, callable(GraphQL\Language\AST\Node): (GraphQL\Language\VisitorOperation|void|false|null)>|(callable(GraphQL\Language\AST\Node): (GraphQL\Language\VisitorOperation|void|false|null))>, array{OperationDefinition: array{enter: Closure(GraphQL\Language\AST\OperationDefinitionNode): void, leave: Closure(): void}, Field: array{enter: Closure(GraphQL\Language\AST\FieldNode): void, leave: Closure(GraphQL\Language\AST\FieldNode): void}, InlineFragment: array{enter: Closure(GraphQL\Language\AST\InlineFragmentNode): void, leave: Closure(GraphQL\Language\AST\InlineFragmentNode): void}, FragmentDefinition: array{enter: Closure(GraphQL\Language\AST\FragmentDefinitionNode): void, leave: Closure(GraphQL\Language\AST\FragmentDefinitionNode): void}, SelectionSet: Closure(GraphQL\Language\AST\SelectionSetNode): void} given.

In this PR I changed the type of the VisitorArray (2nd param to Visitor::visit) and made it a fully defined array shape:

* @phpstan-type VisitorReturnType Node|VisitorOperation|null|false|void
* @phpstan-type VisitorArray array{
* enter?: callable(Node, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(Node, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* Name?: array{
* enter?: callable(NameNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(NameNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(NameNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* Document?: array{
* enter?: callable(DocumentNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(DocumentNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(DocumentNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* OperationDefinition?: array{
* enter?: callable(OperationDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(OperationDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(OperationDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* VariableDefinition?: array{
* enter?: callable(VariableDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(VariableDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(VariableDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* SchemaDefinition?: array{
* enter?: callable(SchemaDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(SchemaDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(SchemaDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* OperationTypeDefinition?: array{
* enter?: callable(OperationTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(OperationTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(OperationTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* ScalarTypeDefinition?: array{
* enter?: callable(ScalarTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(ScalarTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(ScalarTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* ObjectTypeDefinition?: array{
* enter?: callable(ObjectTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(ObjectTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(ObjectTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* FieldDefinition?: array{
* enter?: callable(FieldDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(FieldDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(FieldDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* InputValueDefinition?: array{
* enter?: callable(InputValueDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(InputValueDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(InputValueDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* InterfaceTypeDefinition?: array{
* enter?: callable(InterfaceTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(InterfaceTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(InterfaceTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* UnionTypeDefinition?: array{
* enter?: callable(UnionTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(UnionTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(UnionTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* EnumTypeDefinition?: array{
* enter?: callable(EnumTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(EnumTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(EnumTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* EnumValueDefinition?: array{
* enter?: callable(EnumValueDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(EnumValueDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(EnumValueDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* InputObjectTypeDefinition?: array{
* enter?: callable(InputObjectTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(InputObjectTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(InputObjectTypeDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* SchemaExtension?: array{
* enter?: callable(SchemaExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(SchemaExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(SchemaExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* DirectiveDefinition?: array{
* enter?: callable(DirectiveDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(DirectiveDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(DirectiveDefinitionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* ScalarTypeExtension?: array{
* enter?: callable(ScalarTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(ScalarTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(ScalarTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* ObjectTypeExtension?: array{
* enter?: callable(ObjectTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(ObjectTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(ObjectTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* InterfaceTypeExtension?: array{
* enter?: callable(InterfaceTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(InterfaceTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(InterfaceTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* UnionTypeExtension?: array{
* enter?: callable(UnionTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(UnionTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(UnionTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* EnumTypeExtension?: array{
* enter?: callable(EnumTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(EnumTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(EnumTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* InputObjectTypeExtension?: array{
* enter?: callable(InputObjectTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* leave?: callable(InputObjectTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,
* }|callable(InputObjectTypeExtensionNode, string, null|Node|NodeList, array<int, int|string>, array<int, Node|NodeList>): VisitorReturnType,

It's not nice, but at least the developer now has full type safety.

It works, except it now takes ±4 minutes to analyze. Before it would take 30 seconds.

So this is a performance issue, and also something that might be expected when creating such a monstrous array shape.

But that leads me to the question: can this be done simpler?

Maybe it could be done once we have support for class-string-map and then leverage the info defined here:

public const CLASS_MAP = [
self::NAME => NameNode::class,
// Document
self::DOCUMENT => DocumentNode::class,
self::OPERATION_DEFINITION => OperationDefinitionNode::class,
self::VARIABLE_DEFINITION => VariableDefinitionNode::class,
self::VARIABLE => VariableNode::class,
self::SELECTION_SET => SelectionSetNode::class,
self::FIELD => FieldNode::class,
self::ARGUMENT => ArgumentNode::class,
// Fragments
self::FRAGMENT_SPREAD => FragmentSpreadNode::class,
self::INLINE_FRAGMENT => InlineFragmentNode::class,
self::FRAGMENT_DEFINITION => FragmentDefinitionNode::class,
// Values
self::INT => IntValueNode::class,
self::FLOAT => FloatValueNode::class,
self::STRING => StringValueNode::class,
self::BOOLEAN => BooleanValueNode::class,
self::ENUM => EnumValueNode::class,
self::NULL => NullValueNode::class,
self::LST => ListValueNode::class,
self::OBJECT => ObjectValueNode::class,
self::OBJECT_FIELD => ObjectFieldNode::class,
// Directives
self::DIRECTIVE => DirectiveNode::class,
// Types
self::NAMED_TYPE => NamedTypeNode::class,
self::LIST_TYPE => ListTypeNode::class,
self::NON_NULL_TYPE => NonNullTypeNode::class,
// Type System Definitions
self::SCHEMA_DEFINITION => SchemaDefinitionNode::class,
self::OPERATION_TYPE_DEFINITION => OperationTypeDefinitionNode::class,
// Type Definitions
self::SCALAR_TYPE_DEFINITION => ScalarTypeDefinitionNode::class,
self::OBJECT_TYPE_DEFINITION => ObjectTypeDefinitionNode::class,
self::FIELD_DEFINITION => FieldDefinitionNode::class,
self::INPUT_VALUE_DEFINITION => InputValueDefinitionNode::class,
self::INTERFACE_TYPE_DEFINITION => InterfaceTypeDefinitionNode::class,
self::UNION_TYPE_DEFINITION => UnionTypeDefinitionNode::class,
self::ENUM_TYPE_DEFINITION => EnumTypeDefinitionNode::class,
self::ENUM_VALUE_DEFINITION => EnumValueDefinitionNode::class,
self::INPUT_OBJECT_TYPE_DEFINITION => InputObjectTypeDefinitionNode::class,
// Type Extensions
self::SCALAR_TYPE_EXTENSION => ScalarTypeExtensionNode::class,
self::OBJECT_TYPE_EXTENSION => ObjectTypeExtensionNode::class,
self::INTERFACE_TYPE_EXTENSION => InterfaceTypeExtensionNode::class,
self::UNION_TYPE_EXTENSION => UnionTypeExtensionNode::class,
self::ENUM_TYPE_EXTENSION => EnumTypeExtensionNode::class,
self::INPUT_OBJECT_TYPE_EXTENSION => InputObjectTypeExtensionNode::class,
// Directive Definitions
self::DIRECTIVE_DEFINITION => DirectiveDefinitionNode::class,
];

@ondrejmirtes
Copy link

Alright, so you want to report a performance PHPStan issue. Please create an isolated gist.github.com and open an issue on PHPStan's GitHub.

Also you can try creating an example on phpstan.org/try with two or three different NodeKinds (so it finishes in time) and I can try rewriting it using https://phpstan.org/writing-php-code/phpdoc-types#offset-access or something like that.

@ruudk
Copy link
Contributor Author

ruudk commented Feb 28, 2025

Alright, so you want to report a performance PHPStan issue. Please create an isolated gist.github.com and open an issue on PHPStan's GitHub.

Done here: phpstan/phpstan#12661

Also you can try creating an example on phpstan.org/try with two or three different NodeKinds (so it finishes in time) and I can try rewriting it using https://phpstan.org/writing-php-code/phpdoc-types#offset-access or something like that.

This is the small variant that runs on the playground: https://phpstan.org/r/a3fde113-c526-4b87-be65-ee8c2586217c

@ondrejmirtes
Copy link

I'm sorry, I wasn't able to figure out a different syntax with the "offset access type", it's too complicated.

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.

3 participants