php8.1: added intersection types support (#29)

This commit is contained in:
Makhnev Petr 2022-06-26 03:31:29 +03:00 committed by GitHub
parent 7f6cd25376
commit e16671724e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 22940 additions and 22127 deletions

View File

@ -789,6 +789,24 @@ func (b *Builder) NewUnionType(
} }
} }
func (b *Builder) NewIntersectionType(
Types ast.Vertex,
) *ast.Intersection {
var types []ast.Vertex
var sepTkns []*token.Token
if Types != nil {
cases := Types.(*ParserSeparatedList)
types = cases.Items
sepTkns = cases.SeparatorTkns
}
return &ast.Intersection{
Position: b.Pos.NewNodeListPosition(types),
Types: types,
SeparatorTkns: sepTkns,
}
}
func (b *Builder) NewReturnType( func (b *Builder) NewReturnType(
ColonTkn *token.Token, ColonTkn *token.Token,
Type ast.Vertex, Type ast.Vertex,

View File

@ -227,6 +227,21 @@ func (lex *Lexer) ungetCnt(n int) {
lex.te = lex.te - n lex.te = lex.te - n
} }
func (lex *Lexer) ungetWhile(s byte) {
for i := 0; i < 100; i++ {
v := lex.data[lex.te]
if v == s {
break
}
lex.te--
lex.p--
}
lex.te++
lex.p++
}
func (lex *Lexer) error(msg string) { func (lex *Lexer) error(msg string) {
if lex.errHandlerFunc == nil { if lex.errHandlerFunc == nil {
return return

View File

@ -322,3 +322,114 @@ enum C: int implements Bar {}
suite.Run() suite.Run()
} }
func TestIntersectionTypes(t *testing.T) {
suite := tester.NewParserDumpTestSuite(t)
suite.UsePHP8()
suite.Code = `<?php
class Test {
public A&B $prop;
}
function test(A&B $a): A&B {}
`
suite.Expected = `&ast.Root{
Stmts: []ast.Vertex{
&ast.StmtClass{
Name: &ast.Identifier{
Val: []byte("Test"),
},
Stmts: []ast.Vertex{
&ast.StmtPropertyList{
Modifiers: []ast.Vertex{
&ast.Identifier{
Val: []byte("public"),
},
},
Type: &ast.Intersection{
Types: []ast.Vertex{
&ast.Name{
Parts: []ast.Vertex{
&ast.NamePart{
Val: []byte("A"),
},
},
},
&ast.Name{
Parts: []ast.Vertex{
&ast.NamePart{
Val: []byte("B"),
},
},
},
},
},
Props: []ast.Vertex{
&ast.StmtProperty{
Var: &ast.ExprVariable{
Name: &ast.Identifier{
Val: []byte("$prop"),
},
},
},
},
},
},
},
&ast.StmtFunction{
Name: &ast.Identifier{
Val: []byte("test"),
},
Params: []ast.Vertex{
&ast.Parameter{
Type: &ast.Intersection{
Types: []ast.Vertex{
&ast.Name{
Parts: []ast.Vertex{
&ast.NamePart{
Val: []byte("A"),
},
},
},
&ast.Name{
Parts: []ast.Vertex{
&ast.NamePart{
Val: []byte("B"),
},
},
},
},
},
Var: &ast.ExprVariable{
Name: &ast.Identifier{
Val: []byte("$a"),
},
},
},
},
ReturnType: &ast.Intersection{
Types: []ast.Vertex{
&ast.Name{
Parts: []ast.Vertex{
&ast.NamePart{
Val: []byte("A"),
},
},
},
&ast.Name{
Parts: []ast.Vertex{
&ast.NamePart{
Val: []byte("B"),
},
},
},
},
},
Stmts: []ast.Vertex{},
},
},
},`
suite.Run()
}

View File

@ -4177,7 +4177,7 @@ class Point {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.ID(57492),
Val: []byte("&"), Val: []byte("&"),
FreeFloating: []*token.Token{ FreeFloating: []*token.Token{
{ {
@ -4302,7 +4302,7 @@ class Point {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.ID(57492),
Val: []byte("&"), Val: []byte("&"),
FreeFloating: []*token.Token{ FreeFloating: []*token.Token{
{ {

View File

@ -1972,7 +1972,7 @@ func TestPhp8ParameterNode(t *testing.T) {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 2, StartLine: 2,
@ -2453,7 +2453,7 @@ func TestPhp8ParameterNode(t *testing.T) {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 3, StartLine: 3,
@ -2826,7 +2826,7 @@ func TestPhp8ParameterNode(t *testing.T) {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 4, StartLine: 4,
@ -3212,7 +3212,7 @@ func TestPhp8ParameterNode(t *testing.T) {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 5, StartLine: 5,
@ -10102,7 +10102,7 @@ func TestStmtClassMethod_Php8ClassMethod(t *testing.T) {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 1, StartLine: 1,
@ -16870,7 +16870,7 @@ func TestStmtForeach_WithRef(t *testing.T) {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 1, StartLine: 1,
@ -18087,7 +18087,7 @@ func TestStmtFunction_Ref(t *testing.T) {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 1, StartLine: 1,
@ -18309,7 +18309,7 @@ func TestStmtFunction_ReturnType(t *testing.T) {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 1, StartLine: 1,
@ -35439,7 +35439,7 @@ func TestExprArray_Items(t *testing.T) {
EndPos: 18, EndPos: 18,
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 1, StartLine: 1,
@ -35929,7 +35929,7 @@ func TestExprArrowFunction_ReturnType(t *testing.T) {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 1, StartLine: 1,
@ -37273,7 +37273,7 @@ func TestExprClosure_Use(t *testing.T) {
EndPos: 32, EndPos: 32,
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 1, StartLine: 1,
@ -37633,7 +37633,7 @@ func TestExprClosure_Use2(t *testing.T) {
EndPos: 28, EndPos: 28,
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 1, StartLine: 1,
@ -45056,7 +45056,7 @@ func TestExprShortArray_Items(t *testing.T) {
EndPos: 13, EndPos: 13,
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 1, StartLine: 1,
@ -49734,7 +49734,7 @@ func TestExprAssign_Assign(t *testing.T) {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 3, StartLine: 3,
@ -49870,7 +49870,7 @@ func TestExprAssign_Assign(t *testing.T) {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 4, StartLine: 4,
@ -50038,7 +50038,7 @@ func TestExprAssign_Assign(t *testing.T) {
}, },
}, },
AmpersandTkn: &token.Token{ AmpersandTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 5, StartLine: 5,
@ -51946,7 +51946,7 @@ func TestExprBinary_BitwiseAnd(t *testing.T) {
}, },
}, },
OpTkn: &token.Token{ OpTkn: &token.Token{
ID: token.ID(38), ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"), Value: []byte("&"),
Position: &position.Position{ Position: &position.Position{
StartLine: 2, StartLine: 2,

File diff suppressed because it is too large Load Diff

View File

@ -162,6 +162,8 @@ import (
%token <token> T_NAME_FULLY_QUALIFIED %token <token> T_NAME_FULLY_QUALIFIED
%token <token> T_READONLY %token <token> T_READONLY
%token <token> T_ENUM %token <token> T_ENUM
%token <token> T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
%token <token> T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG
%token <token> '"' %token <token> '"'
%token <token> '`' %token <token> '`'
%token <token> '{' %token <token> '{'
@ -208,7 +210,7 @@ import (
%left T_BOOLEAN_AND %left T_BOOLEAN_AND
%left '|' %left '|'
%left '^' %left '^'
%left '&' %left T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP %nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL %nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
%left '.' %left '.'
@ -227,7 +229,7 @@ import (
%left T_ENDIF %left T_ENDIF
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY %right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC T_READONLY
%type <token> optional_arg_ref optional_ellipsis returns_ref %type <token> optional_ref optional_arg_ref optional_ellipsis
%type <token> reserved_non_modifiers %type <token> reserved_non_modifiers
%type <token> semi_reserved %type <token> semi_reserved
@ -236,6 +238,7 @@ import (
%type <token> optional_comma %type <token> optional_comma
%type <token> case_separator %type <token> case_separator
%type <token> use_type %type <token> use_type
%type <token> ampersand
%type <node> top_statement name statement function_declaration_statement %type <node> top_statement name statement function_declaration_statement
%type <node> class_declaration_statement trait_declaration_statement %type <node> class_declaration_statement trait_declaration_statement
@ -267,6 +270,7 @@ import (
%type <node> type_expr type union_type optional_return_type %type <node> type_expr type union_type optional_return_type
%type <node> type_expr_without_static type_without_static union_type_without_static optional_type_without_static %type <node> type_expr_without_static type_without_static union_type_without_static optional_type_without_static
%type <node> intersection_type intersection_type_without_static
%type <node> class_modifier %type <node> class_modifier
%type <node> argument_list ctor_arguments %type <node> argument_list ctor_arguments
@ -336,6 +340,11 @@ semi_reserved:
| T_PROTECTED {$$=$1} | T_PUBLIC {$$=$1} | T_READONLY {$$=$1} | T_PROTECTED {$$=$1} | T_PUBLIC {$$=$1} | T_READONLY {$$=$1}
; ;
ampersand:
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = $1 }
| T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG { $$ = $1 }
;
identifier: identifier:
T_STRING { $$ = $1 } T_STRING { $$ = $1 }
; ;
@ -839,16 +848,21 @@ unset_variable:
; ;
function_declaration_statement: function_declaration_statement:
T_FUNCTION returns_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}' T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
{ $$ = yylex.(*Parser).builder.NewFunction(nil, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10) } { $$ = yylex.(*Parser).builder.NewFunction(nil, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10) }
| attributes | attributes
T_FUNCTION returns_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}' T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
{ $$ = yylex.(*Parser).builder.NewFunction($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) } { $$ = yylex.(*Parser).builder.NewFunction($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) }
; ;
optional_ref:
/* empty */ { $$ = nil }
| ampersand { $$ = $1 }
;
optional_arg_ref: optional_arg_ref:
/* empty */ { $$ = nil } /* empty */ { $$ = nil }
| '&' { $$ = $1 } | T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = $1 }
; ;
optional_ellipsis: optional_ellipsis:
@ -956,7 +970,7 @@ foreach_variable:
{ {
$$ = $1 $$ = $1
} }
| '&' variable | ampersand variable
{ {
$$ = &ast.StmtForeach{ $$ = &ast.StmtForeach{
Position: yylex.(*Parser).builder.Pos.NewTokenNodePosition($1, $2), Position: yylex.(*Parser).builder.Pos.NewTokenNodePosition($1, $2),
@ -1299,6 +1313,7 @@ type_expr:
type { $$ = $1 } type { $$ = $1 }
| '?' type { $$ = yylex.(*Parser).builder.NewNullableType($1, $2) } | '?' type { $$ = yylex.(*Parser).builder.NewNullableType($1, $2) }
| union_type { $$ = yylex.(*Parser).builder.NewUnionType($1) } | union_type { $$ = yylex.(*Parser).builder.NewUnionType($1) }
| intersection_type { $$ = yylex.(*Parser).builder.NewIntersectionType($1) }
; ;
type: type:
@ -1324,10 +1339,25 @@ union_type_without_static:
{ $$ = yylex.(*Parser).builder.AppendToSeparatedList($1, $2, $3) } { $$ = yylex.(*Parser).builder.AppendToSeparatedList($1, $2, $3) }
; ;
intersection_type:
type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type
{ $$ = yylex.(*Parser).builder.NewSeparatedListWithTwoElements($1, $2, $3) }
| intersection_type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type
{ $$ = yylex.(*Parser).builder.AppendToSeparatedList($1, $2, $3) }
;
intersection_type_without_static:
type_expr_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_expr_without_static
{ $$ = yylex.(*Parser).builder.NewSeparatedListWithTwoElements($1, $2, $3) }
| intersection_type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_expr_without_static
{ $$ = yylex.(*Parser).builder.AppendToSeparatedList($1, $2, $3) }
;
type_expr_without_static: type_expr_without_static:
type_without_static { $$ = $1 } type_without_static { $$ = $1 }
| '?' type_without_static { $$ = yylex.(*Parser).builder.NewNullableType($1, $2) } | '?' type_without_static { $$ = yylex.(*Parser).builder.NewNullableType($1, $2) }
| union_type_without_static { $$ = yylex.(*Parser).builder.NewUnionType($1) } | union_type_without_static { $$ = yylex.(*Parser).builder.NewUnionType($1) }
| intersection_type_without_static { $$ = yylex.(*Parser).builder.NewIntersectionType($1) }
; ;
optional_type_without_static: optional_type_without_static:
@ -1438,7 +1468,7 @@ class_statement:
{ $$ = yylex.(*Parser).builder.NewPropertyList($1, $2, $3, $4, $5) } { $$ = yylex.(*Parser).builder.NewPropertyList($1, $2, $3, $4, $5) }
| optional_attributes method_modifiers T_CONST class_const_list ';' | optional_attributes method_modifiers T_CONST class_const_list ';'
{ $$ = yylex.(*Parser).builder.NewClassConstList($1, $2, $3, $4, $5) } { $$ = yylex.(*Parser).builder.NewClassConstList($1, $2, $3, $4, $5) }
| optional_attributes method_modifiers T_FUNCTION returns_ref identifier_ex '(' parameter_list ')' optional_return_type method_body | optional_attributes method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
{ $$ = yylex.(*Parser).builder.NewClassMethod($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) } { $$ = yylex.(*Parser).builder.NewClassMethod($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) }
| T_USE name_list trait_adaptations | T_USE name_list trait_adaptations
{ {
@ -1889,7 +1919,7 @@ expr_without_variable:
Expr: $3, Expr: $3,
} }
} }
| variable '=' '&' expr | variable '=' ampersand expr
{ {
$$ = &ast.ExprAssignReference{ $$ = &ast.ExprAssignReference{
Position: yylex.(*Parser).builder.Pos.NewNodesPosition($1, $4), Position: yylex.(*Parser).builder.Pos.NewNodesPosition($1, $4),
@ -2110,7 +2140,16 @@ expr_without_variable:
Right: $3, Right: $3,
} }
} }
| expr '&' expr | expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr
{
$$ = &ast.ExprBinaryBitwiseAnd{
Position: yylex.(*Parser).builder.Pos.NewNodesPosition($1, $3),
Left: $1,
OpTkn: $2,
Right: $3,
}
}
| expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr
{ {
$$ = &ast.ExprBinaryBitwiseAnd{ $$ = &ast.ExprBinaryBitwiseAnd{
Position: yylex.(*Parser).builder.Pos.NewNodesPosition($1, $3), Position: yylex.(*Parser).builder.Pos.NewNodesPosition($1, $3),
@ -2558,7 +2597,7 @@ attributed_inline_function:
; ;
inline_function: inline_function:
T_FUNCTION returns_ref backup_doc_comment '(' parameter_list ')' lexical_vars optional_return_type '{' inner_statement_list '}' T_FUNCTION optional_ref backup_doc_comment '(' parameter_list ')' lexical_vars optional_return_type '{' inner_statement_list '}'
{ {
closure := $7.(*ast.ExprClosure) closure := $7.(*ast.ExprClosure)
@ -2577,7 +2616,7 @@ inline_function:
$$ = closure $$ = closure
} }
| T_FN returns_ref '(' parameter_list ')' optional_return_type backup_doc_comment T_DOUBLE_ARROW expr %prec T_THROW | T_FN optional_ref '(' parameter_list ')' optional_return_type backup_doc_comment T_DOUBLE_ARROW expr %prec T_THROW
{ {
$$ = &ast.ExprArrowFunction{ $$ = &ast.ExprArrowFunction{
Position: yylex.(*Parser).builder.Pos.NewTokenNodePosition($1, $9), Position: yylex.(*Parser).builder.Pos.NewTokenNodePosition($1, $9),
@ -2599,11 +2638,6 @@ backup_doc_comment:
/* empty */ /* empty */
; ;
returns_ref:
/* empty */ { $$ = nil }
| '&' { $$ = $1 }
;
lexical_vars: lexical_vars:
/* empty */ /* empty */
{ $$ = &ast.ExprClosure{} } { $$ = &ast.ExprClosure{} }
@ -2643,7 +2677,7 @@ lexical_var:
}, },
} }
} }
| '&' plain_variable | ampersand plain_variable
{ {
$$ = &ast.ExprClosureUse{ $$ = &ast.ExprClosureUse{
Position: yylex.(*Parser).builder.Pos.NewTokensPosition($1, $2), Position: yylex.(*Parser).builder.Pos.NewTokensPosition($1, $2),
@ -3105,7 +3139,7 @@ array_pair:
Val: $1, Val: $1,
} }
} }
| expr T_DOUBLE_ARROW '&' variable | expr T_DOUBLE_ARROW T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG variable
{ {
$$ = &ast.ExprArrayItem{ $$ = &ast.ExprArrayItem{
Position: yylex.(*Parser).builder.Pos.NewNodesPosition($1, $4), Position: yylex.(*Parser).builder.Pos.NewNodesPosition($1, $4),
@ -3115,7 +3149,7 @@ array_pair:
Val: $4, Val: $4,
} }
} }
| '&' variable | T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG variable
{ {
$$ = &ast.ExprArrayItem{ $$ = &ast.ExprArrayItem{
Position: yylex.(*Parser).builder.Pos.NewTokenNodePosition($1, $2), Position: yylex.(*Parser).builder.Pos.NewTokenNodePosition($1, $2),

File diff suppressed because it is too large Load Diff

View File

@ -67,7 +67,7 @@ func (lex *Lexer) Lex() *token.Token {
varname_second = varname_first | [0-9]; varname_second = varname_first | [0-9];
varname = varname_first (varname_second)*; varname = varname_first (varname_second)*;
heredoc_label = varname >heredoc_lbl_start %heredoc_lbl_end; heredoc_label = varname >heredoc_lbl_start %heredoc_lbl_end;
operators = ';'|':'|','|'.'|'['|']'|'('|')'|'|'|'/'|'^'|'&'|'+'|'-'|'*'|'='|'%'|'!'|'~'|'$'|'<'|'>'|'?'|'@'; operators = ';'|':'|','|'.'|'['|']'|'('|')'|'|'|'/'|'^'|'+'|'-'|'*'|'='|'%'|'!'|'~'|'$'|'<'|'>'|'?'|'@';
prepush { lex.growCallStack(); } prepush { lex.growCallStack(); }
@ -295,7 +295,6 @@ func (lex *Lexer) Lex() *token.Token {
'or'i => {lex.setTokenPosition(tkn); tok = token.T_LOGICAL_OR; fbreak;}; 'or'i => {lex.setTokenPosition(tkn); tok = token.T_LOGICAL_OR; fbreak;};
'xor'i => {lex.setTokenPosition(tkn); tok = token.T_LOGICAL_XOR; fbreak;}; 'xor'i => {lex.setTokenPosition(tkn); tok = token.T_LOGICAL_XOR; fbreak;};
'#[' => {lex.setTokenPosition(tkn); tok = token.T_ATTRIBUTE; fbreak;}; '#[' => {lex.setTokenPosition(tkn); tok = token.T_ATTRIBUTE; fbreak;};
'...' => {lex.setTokenPosition(tkn); tok = token.T_ELLIPSIS; fbreak;};
'::' => {lex.setTokenPosition(tkn); tok = token.T_PAAMAYIM_NEKUDOTAYIM; fbreak;}; '::' => {lex.setTokenPosition(tkn); tok = token.T_PAAMAYIM_NEKUDOTAYIM; fbreak;};
'&&' => {lex.setTokenPosition(tkn); tok = token.T_BOOLEAN_AND; fbreak;}; '&&' => {lex.setTokenPosition(tkn); tok = token.T_BOOLEAN_AND; fbreak;};
'||' => {lex.setTokenPosition(tkn); tok = token.T_BOOLEAN_OR; fbreak;}; '||' => {lex.setTokenPosition(tkn); tok = token.T_BOOLEAN_OR; fbreak;};
@ -370,6 +369,12 @@ func (lex *Lexer) Lex() *token.Token {
fbreak; fbreak;
}; };
"&" whitespace_line* '$' => { lex.ungetWhile('&'); lex.setTokenPosition(tkn); tok = token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG; fbreak; };
"&" whitespace_line* '...' => { lex.ungetWhile('&'); lex.setTokenPosition(tkn); tok = token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG; fbreak; };
"&" whitespace_line* ^'$' => { lex.ungetWhile('&'); lex.setTokenPosition(tkn); tok = token.T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG; fbreak; };
"&" whitespace_line* ^'...' => { lex.ungetWhile('&'); lex.setTokenPosition(tkn); tok = token.T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG; fbreak; };
'...' => { lex.setTokenPosition(tkn); tok = token.T_ELLIPSIS; fbreak; };
"{" => { lex.setTokenPosition(tkn); tok = token.ID(int('{')); lex.call(ftargs, fentry(php)); goto _out; }; "{" => { lex.setTokenPosition(tkn); tok = token.ID(int('{')); lex.call(ftargs, fentry(php)); goto _out; };
"}" => { lex.setTokenPosition(tkn); tok = token.ID(int('}')); lex.ret(1); goto _out;}; "}" => { lex.setTokenPosition(tkn); tok = token.ID(int('}')); lex.ret(1); goto _out;};
"$" varname => { lex.setTokenPosition(tkn); tok = token.T_VARIABLE; fbreak; }; "$" varname => { lex.setTokenPosition(tkn); tok = token.T_VARIABLE; fbreak; };

View File

@ -180,3 +180,192 @@ func TestEnumTokens(t *testing.T) {
} }
suite.Run() suite.Run()
} }
func TestAmpersandFollowedByEllipsisTokens(t *testing.T) {
suite := tester.NewLexerTokenStructTestSuite(t)
suite.UsePHP8()
suite.Code = "<?php &...;"
suite.Expected = []*token.Token{
{
ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"),
},
{
ID: token.T_ELLIPSIS,
Value: []byte("..."),
},
{
ID: ';',
Value: []byte(";"),
},
}
suite.Run()
}
func TestAmpersandFollowedByEllipsisTokens2(t *testing.T) {
suite := tester.NewLexerTokenStructTestSuite(t)
suite.UsePHP8()
suite.Code = "<?php & ...;"
suite.Expected = []*token.Token{
{
ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"),
},
{
ID: token.T_ELLIPSIS,
Value: []byte("..."),
},
{
ID: ';',
Value: []byte(";"),
},
}
suite.Run()
}
func TestAmpersandFollowedByEllipsisTokens3(t *testing.T) {
suite := tester.NewLexerTokenStructTestSuite(t)
suite.UsePHP8()
suite.Code = "<?php & \n\t ...;"
suite.Expected = []*token.Token{
{
ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"),
},
{
ID: token.T_ELLIPSIS,
Value: []byte("..."),
},
{
ID: ';',
Value: []byte(";"),
},
}
suite.Run()
}
func TestAmpersandFollowedByVarTokens(t *testing.T) {
suite := tester.NewLexerTokenStructTestSuite(t)
suite.UsePHP8()
suite.Code = "<?php &$a;"
suite.Expected = []*token.Token{
{
ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"),
},
{
ID: token.T_VARIABLE,
Value: []byte("$a"),
},
{
ID: ';',
Value: []byte(";"),
},
}
suite.Run()
}
func TestAmpersandFollowedByVarTokens2(t *testing.T) {
suite := tester.NewLexerTokenStructTestSuite(t)
suite.UsePHP8()
suite.Code = "<?php & $a;"
suite.Expected = []*token.Token{
{
ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"),
},
{
ID: token.T_VARIABLE,
Value: []byte("$a"),
},
{
ID: ';',
Value: []byte(";"),
},
}
suite.Run()
}
func TestAmpersandFollowedByVarTokens3(t *testing.T) {
suite := tester.NewLexerTokenStructTestSuite(t)
suite.UsePHP8()
suite.Code = "<?php & \n\t $a;"
suite.Expected = []*token.Token{
{
ID: token.T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"),
},
{
ID: token.T_VARIABLE,
Value: []byte("$a"),
},
{
ID: ';',
Value: []byte(";"),
},
}
suite.Run()
}
func TestAmpersandNotFollowedByVarOrEllipsisTokens(t *testing.T) {
suite := tester.NewLexerTokenStructTestSuite(t)
suite.UsePHP8()
suite.Code = "<?php &A;"
suite.Expected = []*token.Token{
{
ID: token.T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"),
},
{
ID: token.T_STRING,
Value: []byte("A"),
},
{
ID: ';',
Value: []byte(";"),
},
}
suite.Run()
}
func TestAmpersandNotFollowedByVarOrEllipsisTokens2(t *testing.T) {
suite := tester.NewLexerTokenStructTestSuite(t)
suite.UsePHP8()
suite.Code = "<?php & A;"
suite.Expected = []*token.Token{
{
ID: token.T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"),
},
{
ID: token.T_STRING,
Value: []byte("A"),
},
{
ID: ';',
Value: []byte(";"),
},
}
suite.Run()
}
func TestAmpersandNotFollowedByVarOrEllipsisTokens3(t *testing.T) {
suite := tester.NewLexerTokenStructTestSuite(t)
suite.UsePHP8()
suite.Code = "<?php & \n\t A;"
suite.Expected = []*token.Token{
{
ID: token.T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
Value: []byte("&"),
},
{
ID: token.T_STRING,
Value: []byte("A"),
},
{
ID: ';',
Value: []byte(";"),
},
}
suite.Run()
}

View File

@ -317,7 +317,7 @@ func TestTokens(t *testing.T) {
token.ID(int('|')).String(), token.ID(int('|')).String(),
token.ID(int('/')).String(), token.ID(int('/')).String(),
token.ID(int('^')).String(), token.ID(int('^')).String(),
token.ID(int('&')).String(), token.ID(T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG).String(),
token.ID(int('+')).String(), token.ID(int('+')).String(),
token.ID(int('-')).String(), token.ID(int('-')).String(),
token.ID(int('*')).String(), token.ID(int('*')).String(),

View File

@ -15,6 +15,7 @@ type Visitor interface {
Argument(n *Argument) Argument(n *Argument)
MatchArm(n *MatchArm) MatchArm(n *MatchArm)
Union(n *Union) Union(n *Union)
Intersection(n *Intersection)
Attribute(n *Attribute) Attribute(n *Attribute)
AttributeGroup(n *AttributeGroup) AttributeGroup(n *AttributeGroup)

View File

@ -50,6 +50,21 @@ func (n *Union) GetPosition() *position.Position {
return n.Position return n.Position
} }
// Intersection node is Expr&Expr1&...
type Intersection struct {
Position *position.Position
Types []Vertex
SeparatorTkns []*token.Token
}
func (n *Intersection) Accept(v Visitor) {
v.Intersection(n)
}
func (n *Intersection) GetPosition() *position.Position {
return n.Position
}
// Parameter node // Parameter node
type Parameter struct { type Parameter struct {
Position *position.Position Position *position.Position

View File

@ -152,6 +152,8 @@ const (
T_NAME_FULLY_QUALIFIED T_NAME_FULLY_QUALIFIED
T_READONLY T_READONLY
T_ENUM T_ENUM
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG
) )
type Token struct { type Token struct {

View File

@ -154,11 +154,13 @@ func _() {
_ = x[T_NAME_FULLY_QUALIFIED-57489] _ = x[T_NAME_FULLY_QUALIFIED-57489]
_ = x[T_READONLY-57490] _ = x[T_READONLY-57490]
_ = x[T_ENUM-57491] _ = x[T_ENUM-57491]
_ = x[T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG-57492]
_ = x[T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG-57493]
} }
const _ID_name = "T_INCLUDET_INCLUDE_ONCET_EXITT_IFT_LNUMBERT_DNUMBERT_STRINGT_STRING_VARNAMET_VARIABLET_NUM_STRINGT_INLINE_HTMLT_CHARACTERT_BAD_CHARACTERT_ENCAPSED_AND_WHITESPACET_CONSTANT_ENCAPSED_STRINGT_ECHOT_DOT_WHILET_ENDWHILET_FORT_ENDFORT_FOREACHT_ENDFOREACHT_DECLARET_ENDDECLARET_AST_SWITCHT_ENDSWITCHT_CASET_DEFAULTT_BREAKT_CONTINUET_GOTOT_FUNCTIONT_FNT_CONSTT_RETURNT_TRYT_CATCHT_FINALLYT_THROWT_USET_INSTEADOFT_GLOBALT_VART_UNSETT_ISSETT_EMPTYT_HALT_COMPILERT_CLASST_TRAITT_INTERFACET_EXTENDST_IMPLEMENTST_OBJECT_OPERATORT_DOUBLE_ARROWT_LISTT_ARRAYT_CALLABLET_CLASS_CT_TRAIT_CT_METHOD_CT_FUNC_CT_LINET_FILET_COMMENTT_DOC_COMMENTT_OPEN_TAGT_OPEN_TAG_WITH_ECHOT_CLOSE_TAGT_WHITESPACET_START_HEREDOCT_END_HEREDOCT_DOLLAR_OPEN_CURLY_BRACEST_CURLY_OPENT_PAAMAYIM_NEKUDOTAYIMT_NAMESPACET_NS_CT_DIRT_NS_SEPARATORT_ELLIPSIST_EVALT_REQUIRET_REQUIRE_ONCET_LOGICAL_ORT_LOGICAL_XORT_LOGICAL_ANDT_INSTANCEOFT_NEWT_CLONET_ELSEIFT_ELSET_ENDIFT_PRINTT_YIELDT_STATICT_ABSTRACTT_FINALT_PRIVATET_PROTECTEDT_PUBLICT_INCT_DECT_YIELD_FROMT_INT_CASTT_DOUBLE_CASTT_STRING_CASTT_ARRAY_CASTT_OBJECT_CASTT_BOOL_CASTT_UNSET_CASTT_COALESCET_SPACESHIPT_NOELSET_PLUS_EQUALT_MINUS_EQUALT_MUL_EQUALT_POW_EQUALT_DIV_EQUALT_CONCAT_EQUALT_MOD_EQUALT_AND_EQUALT_OR_EQUALT_XOR_EQUALT_SL_EQUALT_SR_EQUALT_COALESCE_EQUALT_BOOLEAN_ORT_BOOLEAN_ANDT_POWT_SLT_SRT_IS_IDENTICALT_IS_NOT_IDENTICALT_IS_EQUALT_IS_NOT_EQUALT_IS_SMALLER_OR_EQUALT_IS_GREATER_OR_EQUALT_NULLSAFE_OBJECT_OPERATORT_MATCHT_ATTRIBUTET_NAME_RELATIVET_NAME_QUALIFIEDT_NAME_FULLY_QUALIFIEDT_READONLYT_ENUM" const _ID_name = "T_INCLUDET_INCLUDE_ONCET_EXITT_IFT_LNUMBERT_DNUMBERT_STRINGT_STRING_VARNAMET_VARIABLET_NUM_STRINGT_INLINE_HTMLT_CHARACTERT_BAD_CHARACTERT_ENCAPSED_AND_WHITESPACET_CONSTANT_ENCAPSED_STRINGT_ECHOT_DOT_WHILET_ENDWHILET_FORT_ENDFORT_FOREACHT_ENDFOREACHT_DECLARET_ENDDECLARET_AST_SWITCHT_ENDSWITCHT_CASET_DEFAULTT_BREAKT_CONTINUET_GOTOT_FUNCTIONT_FNT_CONSTT_RETURNT_TRYT_CATCHT_FINALLYT_THROWT_USET_INSTEADOFT_GLOBALT_VART_UNSETT_ISSETT_EMPTYT_HALT_COMPILERT_CLASST_TRAITT_INTERFACET_EXTENDST_IMPLEMENTST_OBJECT_OPERATORT_DOUBLE_ARROWT_LISTT_ARRAYT_CALLABLET_CLASS_CT_TRAIT_CT_METHOD_CT_FUNC_CT_LINET_FILET_COMMENTT_DOC_COMMENTT_OPEN_TAGT_OPEN_TAG_WITH_ECHOT_CLOSE_TAGT_WHITESPACET_START_HEREDOCT_END_HEREDOCT_DOLLAR_OPEN_CURLY_BRACEST_CURLY_OPENT_PAAMAYIM_NEKUDOTAYIMT_NAMESPACET_NS_CT_DIRT_NS_SEPARATORT_ELLIPSIST_EVALT_REQUIRET_REQUIRE_ONCET_LOGICAL_ORT_LOGICAL_XORT_LOGICAL_ANDT_INSTANCEOFT_NEWT_CLONET_ELSEIFT_ELSET_ENDIFT_PRINTT_YIELDT_STATICT_ABSTRACTT_FINALT_PRIVATET_PROTECTEDT_PUBLICT_INCT_DECT_YIELD_FROMT_INT_CASTT_DOUBLE_CASTT_STRING_CASTT_ARRAY_CASTT_OBJECT_CASTT_BOOL_CASTT_UNSET_CASTT_COALESCET_SPACESHIPT_NOELSET_PLUS_EQUALT_MINUS_EQUALT_MUL_EQUALT_POW_EQUALT_DIV_EQUALT_CONCAT_EQUALT_MOD_EQUALT_AND_EQUALT_OR_EQUALT_XOR_EQUALT_SL_EQUALT_SR_EQUALT_COALESCE_EQUALT_BOOLEAN_ORT_BOOLEAN_ANDT_POWT_SLT_SRT_IS_IDENTICALT_IS_NOT_IDENTICALT_IS_EQUALT_IS_NOT_EQUALT_IS_SMALLER_OR_EQUALT_IS_GREATER_OR_EQUALT_NULLSAFE_OBJECT_OPERATORT_MATCHT_ATTRIBUTET_NAME_RELATIVET_NAME_QUALIFIEDT_NAME_FULLY_QUALIFIEDT_READONLYT_ENUMT_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARGT_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG"
var _ID_index = [...]uint16{0, 9, 23, 29, 33, 42, 51, 59, 75, 85, 97, 110, 121, 136, 161, 187, 193, 197, 204, 214, 219, 227, 236, 248, 257, 269, 273, 281, 292, 298, 307, 314, 324, 330, 340, 344, 351, 359, 364, 371, 380, 387, 392, 403, 411, 416, 423, 430, 437, 452, 459, 466, 477, 486, 498, 515, 529, 535, 542, 552, 561, 570, 580, 588, 594, 600, 609, 622, 632, 652, 663, 675, 690, 703, 729, 741, 763, 774, 780, 785, 799, 809, 815, 824, 838, 850, 863, 876, 888, 893, 900, 908, 914, 921, 928, 935, 943, 953, 960, 969, 980, 988, 993, 998, 1010, 1020, 1033, 1046, 1058, 1071, 1082, 1094, 1104, 1115, 1123, 1135, 1148, 1159, 1170, 1181, 1195, 1206, 1217, 1227, 1238, 1248, 1258, 1274, 1286, 1299, 1304, 1308, 1312, 1326, 1344, 1354, 1368, 1389, 1410, 1436, 1443, 1454, 1469, 1485, 1507, 1517, 1523} var _ID_index = [...]uint16{0, 9, 23, 29, 33, 42, 51, 59, 75, 85, 97, 110, 121, 136, 161, 187, 193, 197, 204, 214, 219, 227, 236, 248, 257, 269, 273, 281, 292, 298, 307, 314, 324, 330, 340, 344, 351, 359, 364, 371, 380, 387, 392, 403, 411, 416, 423, 430, 437, 452, 459, 466, 477, 486, 498, 515, 529, 535, 542, 552, 561, 570, 580, 588, 594, 600, 609, 622, 632, 652, 663, 675, 690, 703, 729, 741, 763, 774, 780, 785, 799, 809, 815, 824, 838, 850, 863, 876, 888, 893, 900, 908, 914, 921, 928, 935, 943, 953, 960, 969, 980, 988, 993, 998, 1010, 1020, 1033, 1046, 1058, 1071, 1082, 1094, 1104, 1115, 1123, 1135, 1148, 1159, 1170, 1181, 1195, 1206, 1217, 1227, 1238, 1248, 1258, 1274, 1286, 1299, 1304, 1308, 1312, 1326, 1344, 1354, 1368, 1389, 1410, 1436, 1443, 1454, 1469, 1485, 1507, 1517, 1523, 1560, 1601}
func (i ID) String() string { func (i ID) String() string {
i -= 57346 i -= 57346

View File

@ -261,6 +261,17 @@ func (v *Dumper) Union(n *ast.Union) {
v.print(v.indent, "},\n") v.print(v.indent, "},\n")
} }
func (v *Dumper) Intersection(n *ast.Intersection) {
v.print(0, "&ast.Intersection{\n")
v.indent++
v.dumpPosition(n.Position)
v.dumpVertexList("Types", n.Types)
v.dumpTokenList("SeparatorTkns", n.SeparatorTkns)
v.indent--
v.print(v.indent, "},\n")
}
func (v *Dumper) Attribute(n *ast.Attribute) { func (v *Dumper) Attribute(n *ast.Attribute) {
v.print(0, "&ast.Attribute{\n") v.print(0, "&ast.Attribute{\n")
v.indent++ v.indent++

View File

@ -237,6 +237,12 @@ func (f *formatter) Union(n *ast.Union) {
} }
} }
func (f *formatter) Intersection(n *ast.Intersection) {
if len(n.Types) > 0 {
n.SeparatorTkns = f.formatList(n.Types, '&')
}
}
func (f *formatter) Attribute(n *ast.Attribute) { func (f *formatter) Attribute(n *ast.Attribute) {
n.Name.Accept(f) n.Name.Accept(f)
n.OpenParenthesisTkn = f.newToken('(', []byte("(")) n.OpenParenthesisTkn = f.newToken('(', []byte("("))

View File

@ -50,6 +50,10 @@ func (v *Null) Union(_ *ast.Union) {
// do nothing // do nothing
} }
func (v *Null) Intersection(_ *ast.Intersection) {
// do nothing
}
func (v *Null) Attribute(_ *ast.Attribute) { func (v *Null) Attribute(_ *ast.Attribute) {
// do nothing // do nothing
} }

View File

@ -180,6 +180,10 @@ func (p *printer) Union(n *ast.Union) {
p.printSeparatedList(n.Types, n.SeparatorTkns, []byte("|")) p.printSeparatedList(n.Types, n.SeparatorTkns, []byte("|"))
} }
func (p *printer) Intersection(n *ast.Intersection) {
p.printSeparatedList(n.Types, n.SeparatorTkns, []byte("&"))
}
func (p *printer) Attribute(n *ast.Attribute) { func (p *printer) Attribute(n *ast.Attribute) {
p.printNode(n.Name) p.printNode(n.Name)
p.printToken(n.OpenParenthesisTkn, p.ifNodeList(n.Args, []byte("("))) p.printToken(n.OpenParenthesisTkn, p.ifNodeList(n.Args, []byte("(")))

View File

@ -104,3 +104,13 @@ $foo = $closure->__invoke(...);
// new Foo(...); // not working // new Foo(...); // not working
`) `)
} }
func TestIntersectionTypesSyntaxPHP81(t *testing.T) {
tester.NewParserPrintTestSuite(t).UsePHP8().Run(`<?php
class Test {
public A&B $prop;
}
function test(A&B $a): A&B {}
`)
}

View File

@ -78,6 +78,14 @@ func (t *Traverser) Union(n *ast.Union) {
} }
} }
func (t *Traverser) Intersection(n *ast.Intersection) {
n.Accept(t.v)
for _, nn := range n.Types {
nn.Accept(t)
}
}
func (t *Traverser) Attribute(n *ast.Attribute) { func (t *Traverser) Attribute(n *ast.Attribute) {
n.Accept(t.v) n.Accept(t.v)