package php8_test import ( "testing" "github.com/VKCOM/php-parser/internal/tester" "github.com/VKCOM/php-parser/pkg/ast" "github.com/VKCOM/php-parser/pkg/conf" "github.com/VKCOM/php-parser/pkg/errors" "github.com/VKCOM/php-parser/pkg/parser" "github.com/VKCOM/php-parser/pkg/position" "github.com/VKCOM/php-parser/pkg/token" "github.com/VKCOM/php-parser/pkg/version" "gotest.tools/assert" ) func TestNullsafeExprMethodCall(t *testing.T) { suite := tester.NewParserTestSuite(t) suite.UsePHP8() suite.Code = `foo();` suite.Expected = &ast.Root{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 3, EndPos: 14, }, Stmts: []ast.Vertex{ &ast.StmtExpression{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 3, EndPos: 14, }, Expr: &ast.ExprNullsafeMethodCall{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 3, EndPos: 13, }, Var: &ast.ExprVariable{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 3, EndPos: 5, }, Name: &ast.Identifier{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 3, EndPos: 5, }, IdentifierTkn: &token.Token{ ID: token.T_VARIABLE, Value: []byte("$a"), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 3, EndPos: 5, }, FreeFloating: []*token.Token{ { ID: token.T_OPEN_TAG, Value: []byte(""), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 5, EndPos: 8, }, }, Method: &ast.Identifier{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 8, EndPos: 11, }, IdentifierTkn: &token.Token{ ID: token.T_STRING, Value: []byte("foo"), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 8, EndPos: 11, }, }, Value: []byte("foo"), }, OpenParenthesisTkn: &token.Token{ ID: token.ID(40), Value: []byte("("), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 11, EndPos: 12, }, }, CloseParenthesisTkn: &token.Token{ ID: token.ID(41), Value: []byte(")"), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 12, EndPos: 13, }, }, }, SemiColonTkn: &token.Token{ ID: token.ID(59), Value: []byte(";"), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 13, EndPos: 14, }, }, }, }, EndTkn: &token.Token{}, } suite.Run() } func TestNullsafePropertyFetch(t *testing.T) { suite := tester.NewParserTestSuite(t) suite.UsePHP8() suite.Code = `foo;` suite.Expected = &ast.Root{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 15, }, Stmts: []ast.Vertex{ &ast.StmtExpression{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 15, }, Expr: &ast.ExprNullsafePropertyFetch{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 14, }, Var: &ast.ExprVariable{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 8, }, Name: &ast.Identifier{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 8, }, IdentifierTkn: &token.Token{ ID: token.T_VARIABLE, Value: []byte("$a"), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 8, }, FreeFloating: []*token.Token{ { ID: token.T_OPEN_TAG, Value: []byte(""), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 8, EndPos: 11, }, }, Prop: &ast.Identifier{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 11, EndPos: 14, }, IdentifierTkn: &token.Token{ ID: token.T_STRING, Value: []byte("foo"), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 11, EndPos: 14, }, }, Value: []byte("foo"), }, }, SemiColonTkn: &token.Token{ ID: token.ID(59), Value: []byte(";"), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 14, EndPos: 15, }, }, }, }, EndTkn: &token.Token{}, } suite.Run() } func TestNullsafePropertyFetchInEncapsed(t *testing.T) { suite := tester.NewParserTestSuite(t) suite.UsePHP8() suite.Code = `foo";` suite.Expected = &ast.Root{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 17, }, Stmts: []ast.Vertex{ &ast.StmtExpression{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 17, }, Expr: &ast.ScalarEncapsed{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 16, }, OpenQuoteTkn: &token.Token{ ID: token.ID(34), Value: []byte("\""), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 7, }, FreeFloating: []*token.Token{ { ID: token.T_OPEN_TAG, Value: []byte(""), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 9, EndPos: 12, }, }, Prop: &ast.Identifier{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 12, EndPos: 15, }, IdentifierTkn: &token.Token{ ID: token.T_STRING, Value: []byte("foo"), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 12, EndPos: 15, }, }, Value: []byte("foo"), }, }, }, CloseQuoteTkn: &token.Token{ ID: token.ID(34), Value: []byte("\""), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 15, EndPos: 16, }, }, }, SemiColonTkn: &token.Token{ ID: token.ID(59), Value: []byte(";"), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 16, EndPos: 17, }, }, }, }, EndTkn: &token.Token{}, } suite.Run() } func TestNullsafePropertyFetchForDereferencable(t *testing.T) { suite := tester.NewParserTestSuite(t) suite.UsePHP8() suite.Code = `foo;` suite.Expected = &ast.Root{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 18, }, Stmts: []ast.Vertex{ &ast.StmtExpression{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 18, }, Expr: &ast.ExprNullsafePropertyFetch{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 17, }, Var: &ast.ExprBrackets{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 11, }, OpenParenthesisTkn: &token.Token{ ID: token.ID(40), Value: []byte("("), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 6, EndPos: 7, }, FreeFloating: []*token.Token{ { ID: token.T_OPEN_TAG, Value: []byte(""), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 11, EndPos: 14, }, }, Prop: &ast.Identifier{ Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 14, EndPos: 17, }, IdentifierTkn: &token.Token{ ID: token.T_STRING, Value: []byte("foo"), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 14, EndPos: 17, }, }, Value: []byte("foo"), }, }, SemiColonTkn: &token.Token{ ID: token.ID(59), Value: []byte(";"), Position: &position.Position{ StartLine: 1, EndLine: 1, StartPos: 17, EndPos: 18, }, }, }, }, EndTkn: &token.Token{}, } suite.Run() } func TestExprCast(t *testing.T) { suite := tester.NewParserTestSuite(t) suite.UsePHP8() suite.Code = `bar(some: $a, $b, ...$c); foo::bar($a, b: $b, ...$c); $foo::bar($b, c: $a, ...$b); new foo(a: $a, $c, ...$b); /** anonymous class */ new class (name: $a, $b, ...$c) {}; ` config := conf.Config{ Version: &version.Version{Major: 8, Minor: 0}, } root, err := parser.Parse([]byte(suite.Code), config) if err != nil { t.Fatalf("Error parse: %v", err) } stmts := root.(*ast.Root).Stmts for _, stmt := range stmts { stmtExpr, ok := stmt.(*ast.StmtExpression) if !ok { continue } switch n := stmtExpr.Expr.(type) { case *ast.ExprFunctionCall: assert.Assert(t, len(n.Args) == 3) case *ast.ExprMethodCall: assert.Assert(t, len(n.Args) == 3) case *ast.ExprStaticCall: assert.Assert(t, len(n.Args) == 3) case *ast.ExprNew: if class, ok := n.Class.(*ast.StmtClass); ok { assert.Assert(t, len(class.Args) == 3) continue } assert.Assert(t, len(n.Args) == 3) } } } func TestCurlyBracesAccessParserError(t *testing.T) { suite := tester.NewParserErrorTestSuite(t) suite.UsePHP8() suite.Code = ` 100 }; ` suite.Expected = &ast.Root{ Position: &position.Position{ StartLine: 2, EndLine: 4, StartPos: 6, EndPos: 33, }, Stmts: []ast.Vertex{ &ast.StmtExpression{ Position: &position.Position{ StartLine: 2, EndLine: 4, StartPos: 6, EndPos: 33, }, Expr: &ast.ExprMatch{ Position: &position.Position{ StartLine: 2, EndLine: 4, StartPos: 6, EndPos: 32, }, MatchTkn: &token.Token{ ID: token.T_MATCH, Value: []byte("match"), Position: &position.Position{ StartLine: 2, EndLine: 2, StartPos: 6, EndPos: 11, }, FreeFloating: []*token.Token{ { ID: token.T_OPEN_TAG, Value: []byte(""), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 24, EndPos: 26, }, FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte(" "), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 23, EndPos: 24, }, }, }, }, ReturnExpr: &ast.ScalarLnumber{ Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 27, EndPos: 30, }, NumberTkn: &token.Token{ ID: token.T_LNUMBER, Value: []byte("100"), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 27, EndPos: 30, }, FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte(" "), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 26, EndPos: 27, }, }, }, }, Value: []byte("100"), }, }, }, CloseCurlyBracketTkn: &token.Token{ ID: token.ID(125), Value: []byte("}"), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 31, EndPos: 32, }, FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte("\n"), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 30, EndPos: 31, }, }, }, }, }, SemiColonTkn: &token.Token{ ID: token.ID(59), Value: []byte(";"), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 32, EndPos: 33, }, }, }, }, EndTkn: &token.Token{ FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte("\n"), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 33, EndPos: 34, }, }, }, }, } suite.Run() } func TestMatchExprWithList(t *testing.T) { suite := tester.NewParserTestSuite(t) suite.UsePHP8() suite.Code = ` 100 }; ` suite.Expected = &ast.Root{ Position: &position.Position{ StartLine: 2, EndLine: 4, StartPos: 6, EndPos: 37, }, Stmts: []ast.Vertex{ &ast.StmtExpression{ Position: &position.Position{ StartLine: 2, EndLine: 4, StartPos: 6, EndPos: 37, }, Expr: &ast.ExprMatch{ Position: &position.Position{ StartLine: 2, EndLine: 4, StartPos: 6, EndPos: 36, }, MatchTkn: &token.Token{ ID: token.T_MATCH, Value: []byte("match"), Position: &position.Position{ StartLine: 2, EndLine: 2, StartPos: 6, EndPos: 11, }, FreeFloating: []*token.Token{ { ID: token.T_OPEN_TAG, Value: []byte(""), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 28, EndPos: 30, }, FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte(" "), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 27, EndPos: 28, }, }, }, }, ReturnExpr: &ast.ScalarLnumber{ Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 31, EndPos: 34, }, NumberTkn: &token.Token{ ID: token.T_LNUMBER, Value: []byte("100"), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 31, EndPos: 34, }, FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte(" "), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 30, EndPos: 31, }, }, }, }, Value: []byte("100"), }, }, }, CloseCurlyBracketTkn: &token.Token{ ID: token.ID(125), Value: []byte("}"), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 35, EndPos: 36, }, FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte("\n"), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 34, EndPos: 35, }, }, }, }, }, SemiColonTkn: &token.Token{ ID: token.ID(59), Value: []byte(";"), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 36, EndPos: 37, }, }, }, }, EndTkn: &token.Token{ FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte("\n"), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 37, EndPos: 38, }, }, }, }, } suite.Run() } func TestMatchExprWithDefault(t *testing.T) { suite := tester.NewParserTestSuite(t) suite.UsePHP8() suite.Code = ` 100, default => 101, }; ` suite.Expected = &ast.Root{ Position: &position.Position{ StartLine: 2, EndLine: 5, StartPos: 6, EndPos: 52, }, Stmts: []ast.Vertex{ &ast.StmtExpression{ Position: &position.Position{ StartLine: 2, EndLine: 5, StartPos: 6, EndPos: 52, }, Expr: &ast.ExprMatch{ Position: &position.Position{ StartLine: 2, EndLine: 5, StartPos: 6, EndPos: 51, }, MatchTkn: &token.Token{ ID: token.T_MATCH, Value: []byte("match"), Position: &position.Position{ StartLine: 2, EndLine: 2, StartPos: 6, EndPos: 11, }, FreeFloating: []*token.Token{ { ID: token.T_OPEN_TAG, Value: []byte(""), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 24, EndPos: 26, }, FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte(" "), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 23, EndPos: 24, }, }, }, }, ReturnExpr: &ast.ScalarLnumber{ Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 27, EndPos: 30, }, NumberTkn: &token.Token{ ID: token.T_LNUMBER, Value: []byte("100"), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 27, EndPos: 30, }, FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte(" "), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 26, EndPos: 27, }, }, }, }, Value: []byte("100"), }, }, &ast.MatchArm{ Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 34, EndPos: 48, }, DefaultTkn: &token.Token{ ID: token.T_DEFAULT, Value: []byte("default"), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 34, EndPos: 41, }, FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte("\n "), Position: &position.Position{ StartLine: 3, EndLine: 4, StartPos: 31, EndPos: 34, }, }, }, }, DoubleArrowTkn: &token.Token{ ID: token.T_DOUBLE_ARROW, Value: []byte("=>"), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 42, EndPos: 44, }, FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte(" "), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 41, EndPos: 42, }, }, }, }, ReturnExpr: &ast.ScalarLnumber{ Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 45, EndPos: 48, }, NumberTkn: &token.Token{ ID: token.T_LNUMBER, Value: []byte("101"), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 45, EndPos: 48, }, FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte(" "), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 44, EndPos: 45, }, }, }, }, Value: []byte("101"), }, }, }, SeparatorTkns: []*token.Token{ { ID: token.ID(44), Value: []byte(","), Position: &position.Position{ StartLine: 3, EndLine: 3, StartPos: 30, EndPos: 31, }, }, { ID: token.ID(44), Value: []byte(","), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 48, EndPos: 49, }, }, }, CloseCurlyBracketTkn: &token.Token{ ID: token.ID(125), Value: []byte("}"), Position: &position.Position{ StartLine: 5, EndLine: 5, StartPos: 50, EndPos: 51, }, FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte("\n"), Position: &position.Position{ StartLine: 4, EndLine: 4, StartPos: 49, EndPos: 50, }, }, }, }, }, SemiColonTkn: &token.Token{ ID: token.ID(59), Value: []byte(";"), Position: &position.Position{ StartLine: 5, EndLine: 5, StartPos: 51, EndPos: 52, }, }, }, }, EndTkn: &token.Token{ FreeFloating: []*token.Token{ { ID: token.T_WHITESPACE, Value: []byte("\n"), Position: &position.Position{ StartLine: 5, EndLine: 5, StartPos: 52, EndPos: 53, }, }, }, }, } suite.Run() } func TestStaticTypeParserError(t *testing.T) { suite := tester.NewParserErrorTestSuite(t) suite.UsePHP8() suite.Code = `createNotFoundException();` suite.Expected = `&ast.Root{ Stmts: []ast.Vertex{ &ast.StmtExpression{ Expr: &ast.ExprAssignCoalesce{ Var: &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$a"), }, }, Expr: &ast.ExprThrow{ Expr: &ast.ExprMethodCall{ Var: &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$this"), }, }, Method: &ast.Identifier{ Val: []byte("createNotFoundException"), }, }, }, }, }, }, },` suite.Run() } func TestArrowFunctionPrecedenceWithOr(t *testing.T) { suite := tester.NewParserDumpTestSuite(t) suite.UsePHP8() suite.Code = ` $a and $b;` suite.Expected = `&ast.Root{ Stmts: []ast.Vertex{ &ast.StmtExpression{ Expr: &ast.ExprArrowFunction{ Params: []ast.Vertex{ &ast.Parameter{ Var: &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$a"), }, }, }, &ast.Parameter{ Var: &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$b"), }, }, }, }, Expr: &ast.ExprBinaryLogicalAnd{ Left: &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$a"), }, }, Right: &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$b"), }, }, }, }, }, }, },` suite.Run() } func TestArrowFunctionPrecedenceWithAnd(t *testing.T) { suite := tester.NewParserDumpTestSuite(t) suite.UsePHP8() suite.Code = ` $a && $b;` suite.Expected = `&ast.Root{ Stmts: []ast.Vertex{ &ast.StmtExpression{ Expr: &ast.ExprArrowFunction{ Params: []ast.Vertex{ &ast.Parameter{ Var: &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$a"), }, }, }, &ast.Parameter{ Var: &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$b"), }, }, }, }, Expr: &ast.ExprBinaryBooleanAnd{ Left: &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$a"), }, }, Right: &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$b"), }, }, }, }, }, }, },` suite.Run() } func TestConcatPrecedenceWithPlus(t *testing.T) { suite := tester.NewParserDumpTestSuite(t) suite.UsePHP8() suite.Code = `length(); "foo$bar"[0]; "foo$bar"->length(); ` suite.Expected = `&ast.Root{ Stmts: []ast.Vertex{ &ast.StmtExpression{ Expr: &ast.ExprMethodCall{ Var: &ast.ScalarString{ Val: []byte("\"string\""), }, Method: &ast.Identifier{ Val: []byte("length"), }, }, }, &ast.StmtExpression{ Expr: &ast.ExprArrayDimFetch{ Var: &ast.ScalarEncapsed{ Parts: []ast.Vertex{ &ast.ScalarEncapsedStringPart{ Val: []byte("foo"), }, &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$bar"), }, }, }, }, Dim: &ast.ScalarLnumber{ Val: []byte("0"), }, }, }, &ast.StmtExpression{ Expr: &ast.ExprMethodCall{ Var: &ast.ScalarEncapsed{ Parts: []ast.Vertex{ &ast.ScalarEncapsedStringPart{ Val: []byte("foo"), }, &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$bar"), }, }, }, }, Method: &ast.Identifier{ Val: []byte("length"), }, }, }, }, },` suite.Run() } func TestConstantDerefencable(t *testing.T) { suite := tester.NewParserDumpTestSuite(t) suite.UsePHP8() suite.Code = `length; A->length(); A[0]; A[0][1][2]; A::B[0]; A::B[0][1][2]; A::B->length; A::B->length(); A::B::C; A::B::$c; A::B::c(); ` suite.Expected = `&ast.Root{ Stmts: []ast.Vertex{ &ast.StmtExpression{ Expr: &ast.ExprPropertyFetch{ Var: &ast.ExprConstFetch{ Const: &ast.Name{ Parts: []ast.Vertex{ &ast.NamePart{ Val: []byte("A"), }, }, }, }, Prop: &ast.Identifier{ Val: []byte("length"), }, }, }, &ast.StmtExpression{ Expr: &ast.ExprMethodCall{ Var: &ast.ExprConstFetch{ Const: &ast.Name{ Parts: []ast.Vertex{ &ast.NamePart{ Val: []byte("A"), }, }, }, }, Method: &ast.Identifier{ Val: []byte("length"), }, }, }, &ast.StmtExpression{ Expr: &ast.ExprArrayDimFetch{ Var: &ast.ExprConstFetch{ Const: &ast.Name{ Parts: []ast.Vertex{ &ast.NamePart{ Val: []byte("A"), }, }, }, }, Dim: &ast.ScalarLnumber{ Val: []byte("0"), }, }, }, &ast.StmtExpression{ Expr: &ast.ExprArrayDimFetch{ Var: &ast.ExprArrayDimFetch{ Var: &ast.ExprArrayDimFetch{ Var: &ast.ExprConstFetch{ Const: &ast.Name{ Parts: []ast.Vertex{ &ast.NamePart{ Val: []byte("A"), }, }, }, }, Dim: &ast.ScalarLnumber{ Val: []byte("0"), }, }, Dim: &ast.ScalarLnumber{ Val: []byte("1"), }, }, Dim: &ast.ScalarLnumber{ Val: []byte("2"), }, }, }, &ast.StmtExpression{ Expr: &ast.ExprArrayDimFetch{ Var: &ast.ExprClassConstFetch{ Class: &ast.Name{ Parts: []ast.Vertex{ &ast.NamePart{ Val: []byte("A"), }, }, }, Const: &ast.Identifier{ Val: []byte("B"), }, }, Dim: &ast.ScalarLnumber{ Val: []byte("0"), }, }, }, &ast.StmtExpression{ Expr: &ast.ExprArrayDimFetch{ Var: &ast.ExprArrayDimFetch{ Var: &ast.ExprArrayDimFetch{ Var: &ast.ExprClassConstFetch{ Class: &ast.Name{ Parts: []ast.Vertex{ &ast.NamePart{ Val: []byte("A"), }, }, }, Const: &ast.Identifier{ Val: []byte("B"), }, }, Dim: &ast.ScalarLnumber{ Val: []byte("0"), }, }, Dim: &ast.ScalarLnumber{ Val: []byte("1"), }, }, Dim: &ast.ScalarLnumber{ Val: []byte("2"), }, }, }, &ast.StmtExpression{ Expr: &ast.ExprPropertyFetch{ Var: &ast.ExprClassConstFetch{ Class: &ast.Name{ Parts: []ast.Vertex{ &ast.NamePart{ Val: []byte("A"), }, }, }, Const: &ast.Identifier{ Val: []byte("B"), }, }, Prop: &ast.Identifier{ Val: []byte("length"), }, }, }, &ast.StmtExpression{ Expr: &ast.ExprMethodCall{ Var: &ast.ExprClassConstFetch{ Class: &ast.Name{ Parts: []ast.Vertex{ &ast.NamePart{ Val: []byte("A"), }, }, }, Const: &ast.Identifier{ Val: []byte("B"), }, }, Method: &ast.Identifier{ Val: []byte("length"), }, }, }, &ast.StmtExpression{ Expr: &ast.ExprClassConstFetch{ Class: &ast.ExprClassConstFetch{ Class: &ast.Name{ Parts: []ast.Vertex{ &ast.NamePart{ Val: []byte("A"), }, }, }, Const: &ast.Identifier{ Val: []byte("B"), }, }, Const: &ast.Identifier{ Val: []byte("C"), }, }, }, &ast.StmtExpression{ Expr: &ast.ExprStaticPropertyFetch{ Class: &ast.ExprClassConstFetch{ Class: &ast.Name{ Parts: []ast.Vertex{ &ast.NamePart{ Val: []byte("A"), }, }, }, Const: &ast.Identifier{ Val: []byte("B"), }, }, Prop: &ast.ExprVariable{ Name: &ast.Identifier{ Val: []byte("$c"), }, }, }, }, &ast.StmtExpression{ Expr: &ast.ExprStaticCall{ Class: &ast.ExprClassConstFetch{ Class: &ast.Name{ Parts: []ast.Vertex{ &ast.NamePart{ Val: []byte("A"), }, }, }, Const: &ast.Identifier{ Val: []byte("B"), }, }, Call: &ast.Identifier{ Val: []byte("c"), }, }, }, }, },` suite.Run() }