From b8428a8878e256d50f4e77cf57a1853fb214937e Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 5 Oct 2026 18:31:46 +0200 Subject: [PATCH] Add PHP 8.2 DNF type support ((A&B)|C) Parenthesized intersection types are now accepted as union members, e.g. (Countable&Traversable)|array, in parameter, return and property positions. The parenthesis tokens are kept on the Intersection node so the type prints back unchanged. Also fixes the stale github.com/TomasVotruba/reco imports left in php8.y after the module extraction, so regeneration produces the correct module path. --- internal/php8/dnf_types_test.go | 86 + internal/php8/php8.go | 3968 ++++++++++++++++--------------- internal/php8/php8.y | 48 +- pkg/ast/node.go | 11 +- pkg/visitor/printer/printer.go | 2 + 5 files changed, 2155 insertions(+), 1960 deletions(-) create mode 100644 internal/php8/dnf_types_test.go diff --git a/internal/php8/dnf_types_test.go b/internal/php8/dnf_types_test.go new file mode 100644 index 0000000..9150da3 --- /dev/null +++ b/internal/php8/dnf_types_test.go @@ -0,0 +1,86 @@ +package php8_test + +import ( + "bytes" + "testing" + + "github.com/rectorphp/php-parser-in-go/pkg/ast" + "github.com/rectorphp/php-parser-in-go/pkg/visitor/printer" +) + +func TestDNFParameterType(test *testing.T) { + root, parserErrors := parseIntersectionTypes(` alt_if_stmt_without_else %type array_pair possible_array_pair %type isset_variable type return_type type_expr union_type intersection_type class_constant +%type dnf_parenthesized_intersection %type class_modifier %type argument_list ctor_arguments %type trait_adaptations @@ -2150,6 +2151,18 @@ intersection_type: } ; +dnf_parenthesized_intersection: + '(' intersection_type ')' + { + intersection := $2.(*ast.Intersection) + intersection.Position = yylex.(*Parser).builder.NewTokensPosition($1, $3) + intersection.OpenParenthesisTkn = $1 + intersection.CloseParenthesisTkn = $3 + + $$ = intersection + } +; + union_type: type '|' type { @@ -2159,6 +2172,30 @@ union_type: SeparatorTkns: []*token.Token{$2}, } } + | type '|' dnf_parenthesized_intersection + { + $$ = &ast.Union{ + Position: yylex.(*Parser).builder.NewNodesPosition($1, $3), + Types: []ast.Vertex{$1, $3}, + SeparatorTkns: []*token.Token{$2}, + } + } + | dnf_parenthesized_intersection '|' type + { + $$ = &ast.Union{ + Position: yylex.(*Parser).builder.NewNodesPosition($1, $3), + Types: []ast.Vertex{$1, $3}, + SeparatorTkns: []*token.Token{$2}, + } + } + | dnf_parenthesized_intersection '|' dnf_parenthesized_intersection + { + $$ = &ast.Union{ + Position: yylex.(*Parser).builder.NewNodesPosition($1, $3), + Types: []ast.Vertex{$1, $3}, + SeparatorTkns: []*token.Token{$2}, + } + } | union_type '|' type { $1.(*ast.Union).Types = append($1.(*ast.Union).Types, $3) @@ -2166,6 +2203,13 @@ union_type: $$ = $1 } + | union_type '|' dnf_parenthesized_intersection + { + $1.(*ast.Union).Types = append($1.(*ast.Union).Types, $3) + $1.(*ast.Union).SeparatorTkns = append($1.(*ast.Union).SeparatorTkns, $2) + + $$ = $1 + } ; type: diff --git a/pkg/ast/node.go b/pkg/ast/node.go index e049058..036f08b 100644 --- a/pkg/ast/node.go +++ b/pkg/ast/node.go @@ -52,11 +52,14 @@ func (node *Union) GetPosition() *position.Position { } // Intersection node holds an intersection type such as `Countable&Traversable`, -// its member types separated by `&`. +// its member types separated by `&`. In a DNF type the intersection is wrapped +// in parentheses, e.g. `(A&B)|C`; the parenthesis tokens are then set. type Intersection struct { - Position *position.Position - Types []Vertex - SeparatorTkns []*token.Token + Position *position.Position + OpenParenthesisTkn *token.Token + Types []Vertex + SeparatorTkns []*token.Token + CloseParenthesisTkn *token.Token } func (node *Intersection) Accept(visitor Visitor) { diff --git a/pkg/visitor/printer/printer.go b/pkg/visitor/printer/printer.go index f536e9f..2df692a 100644 --- a/pkg/visitor/printer/printer.go +++ b/pkg/visitor/printer/printer.go @@ -159,7 +159,9 @@ func (printer *printer) Union(node *ast.Union) { } func (printer *printer) Intersection(node *ast.Intersection) { + printer.printToken(node.OpenParenthesisTkn, nil) printer.printSeparatedList(node.Types, node.SeparatorTkns, []byte("&")) + printer.printToken(node.CloseParenthesisTkn, nil) } func (printer *printer) Parameter(node *ast.Parameter) {