Repository: VKCOM/noverify
Pull request #661 (2020-08-12 09:28:20)

ir: simplify argument lists (and remove ArgumentList node)
This is done to simplify arguments handling.
We can't store arguments as []*Argument since it will
require us to duplicate code that accepts []Node
(and we don't have generics yet).

As a workaround, all nodes with arguments get Arg(i) method
that returns ith argument type-asserted to *Argument.

So we can do:

	call.Arg(i)

Instead of:

	call.ArgumentList.Arguments[i].(*ir.Argument)

This does look like an improvement.

To avoid information loss, freefloating from the ArgumentList
is stored inside the call-like node itself. So if we ever
need it, it's there.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>


Patch:
diff --git a/example/custom/main.go b/example/custom/main.go
index 6cd58cb..ce93056 100644
--- a/example/custom/main.go
+++ b/example/custom/main.go
@@ -81,11 +81,11 @@ func (b *block) handleFunctionCall(e *ir.FunctionCallExpr) {
 }
 
 func (b *block) handleInArrayCall(e *ir.FunctionCallExpr) {
-	if len(e.ArgumentList.Arguments) != 2 {
+	if len(e.Args) != 2 {
 		return
 	}
 
-	arg := e.ArgumentList.Arguments[0].(*ir.Argument)
+	arg := e.Arg(0)
 	if !isString(b.ctx, arg.Expr) {
 		return
 	}
diff --git a/src/ir/freefloating.go b/src/ir/freefloating.go
index 0a488ce..309e668 100644
--- a/src/ir/freefloating.go
+++ b/src/ir/freefloating.go
@@ -172,8 +172,6 @@ func (n *Name) GetFreeFloating() *freefloating.Collection { return &n.FreeFloati
 
 func (n *Argument) GetFreeFloating() *freefloating.Collection { return &n.FreeFloating }
 
-func (n *ArgumentList) GetFreeFloating() *freefloating.Collection { return &n.FreeFloating }
-
 func (n *Identifier) GetFreeFloating() *freefloating.Collection { return &n.FreeFloating }
 
 func (n *Nullable) GetFreeFloating() *freefloating.Collection { return &n.FreeFloating }
diff --git a/src/ir/get_node_kind.go b/src/ir/get_node_kind.go
index 9ab8a1d..84b85ad 100644
--- a/src/ir/get_node_kind.go
+++ b/src/ir/get_node_kind.go
@@ -91,7 +91,6 @@ const (
 	KindYieldFromExpr
 	KindName
 	KindArgument
-	KindArgumentList
 	KindIdentifier
 	KindNullable
 	KindParameter
@@ -332,8 +331,6 @@ func GetNodeKind(n Node) NodeKind {
 		return KindName
 	case *Argument:
 		return KindArgument
-	case *ArgumentList:
-		return KindArgumentList
 	case *Identifier:
 		return KindIdentifier
 	case *Nullable:
diff --git a/src/ir/get_position.go b/src/ir/get_position.go
index 3dcf1e6..e7c0166 100644
--- a/src/ir/get_position.go
+++ b/src/ir/get_position.go
@@ -176,8 +176,6 @@ func GetPosition(n Node) *position.Position {
 		return n.Position
 	case *Argument:
 		return n.Position
-	case *ArgumentList:
-		return n.Position
 	case *Identifier:
 		return n.Position
 	case *Nullable:
diff --git a/src/ir/irfmt/printer.go b/src/ir/irfmt/printer.go
index f773c80..8cdb52e 100644
--- a/src/ir/irfmt/printer.go
+++ b/src/ir/irfmt/printer.go
@@ -907,7 +907,7 @@ func (p *PrettyPrinter) printExprExit(n *ir.ExitExpr) {
 func (p *PrettyPrinter) printExprFunctionCall(n *ir.FunctionCallExpr) {
 	p.Print(n.Function)
 	io.WriteString(p.w, "(")
-	p.joinPrint(", ", n.ArgumentList.Arguments)
+	p.joinPrint(", ", n.Args)
 	io.WriteString(p.w, ")")
 }
 
@@ -940,7 +940,7 @@ func (p *PrettyPrinter) printExprMethodCall(n *ir.MethodCallExpr) {
 	io.WriteString(p.w, "->")
 	p.Print(n.Method)
 	io.WriteString(p.w, "(")
-	p.joinPrint(", ", n.ArgumentList.Arguments)
+	p.joinPrint(", ", n.Args)
 	io.WriteString(p.w, ")")
 }
 
@@ -948,9 +948,9 @@ func (p *PrettyPrinter) printExprNew(n *ir.NewExpr) {
 	io.WriteString(p.w, "new ")
 	p.Print(n.Class)
 
-	if n.ArgumentList != nil {
+	if len(n.Args) != 0 {
 		io.WriteString(p.w, "(")
-		p.joinPrint(", ", n.ArgumentList.Arguments)
+		p.joinPrint(", ", n.Args)
 		io.WriteString(p.w, ")")
 	}
 }
@@ -1017,7 +1017,7 @@ func (p *PrettyPrinter) printExprStaticCall(n *ir.StaticCallExpr) {
 	io.WriteString(p.w, "::")
 	p.Print(n.Call)
 	io.WriteString(p.w, "(")
-	p.joinPrint(", ", n.ArgumentList.Arguments)
+	p.joinPrint(", ", n.Args)
 	io.WriteString(p.w, ")")
 }
 
@@ -1158,9 +1158,9 @@ func (p *PrettyPrinter) printStmtClass(n *ir.ClassStmt) {
 		p.Print(n.ClassName)
 	}
 
-	if n.ArgumentList != nil {
+	if len(n.Args) != 0 {
 		io.WriteString(p.w, "(")
-		p.joinPrint(", ", n.ArgumentList.Arguments)
+		p.joinPrint(", ", n.Args)
 		io.WriteString(p.w, ")")
 	}
 
diff --git a/src/ir/irutil/clone.go b/src/ir/irutil/clone.go
index 2f79d68..f7d3fa8 100644
--- a/src/ir/irutil/clone.go
+++ b/src/ir/irutil/clone.go
@@ -17,16 +17,6 @@ func NodeClone(x ir.Node) ir.Node {
 			clone.Expr = NodeClone(x.Expr).(ir.Node)
 		}
 		return &clone
-	case *ir.ArgumentList:
-		clone := *x
-		{
-			sliceClone := make([]ir.Node, len(x.Arguments))
-			for i := range x.Arguments {
-				sliceClone[i] = NodeClone(x.Arguments[i]).(ir.Node)
-			}
-			clone.Arguments = sliceClone
-		}
-		return &clone
 	case *ir.ArrayDimFetchExpr:
 		clone := *x
 		if x.Variable != nil {
@@ -392,8 +382,12 @@ func NodeClone(x ir.Node) ir.Node {
 			}
 			clone.Modifiers = sliceClone
 		}
-		if x.ArgumentList != nil {
-			clone.ArgumentList = NodeClone(x.ArgumentList).(*ir.ArgumentList)
+		{
+			sliceClone := make([]ir.Node, len(x.Args))
+			for i := range x.Args {
+				sliceClone[i] = NodeClone(x.Args[i]).(ir.Node)
+			}
+			clone.Args = sliceClone
 		}
 		if x.Extends != nil {
 			clone.Extends = NodeClone(x.Extends).(*ir.ClassExtendsStmt)
@@ -675,8 +669,12 @@ func NodeClone(x ir.Node) ir.Node {
 		if x.Function != nil {
 			clone.Function = NodeClone(x.Function).(ir.Node)
 		}
-		if x.ArgumentList != nil {
-			clone.ArgumentList = NodeClone(x.ArgumentList).(*ir.ArgumentList)
+		{
+			sliceClone := make([]ir.Node, len(x.Args))
+			for i := range x.Args {
+				sliceClone[i] = NodeClone(x.Args[i]).(ir.Node)
+			}
+			clone.Args = sliceClone
 		}
 		return &clone
 	case *ir.FunctionStmt:
@@ -907,8 +905,12 @@ func NodeClone(x ir.Node) ir.Node {
 		if x.Method != nil {
 			clone.Method = NodeClone(x.Method).(ir.Node)
 		}
-		if x.ArgumentList != nil {
-			clone.ArgumentList = NodeClone(x.ArgumentList).(*ir.ArgumentList)
+		{
+			sliceClone := make([]ir.Node, len(x.Args))
+			for i := range x.Args {
+				sliceClone[i] = NodeClone(x.Args[i]).(ir.Node)
+			}
+			clone.Args = sliceClone
 		}
 		return &clone
 	case *ir.MinusExpr:
@@ -959,8 +961,12 @@ func NodeClone(x ir.Node) ir.Node {
 		if x.Class != nil {
 			clone.Class = NodeClone(x.Class).(ir.Node)
 		}
-		if x.ArgumentList != nil {
-			clone.ArgumentList = NodeClone(x.ArgumentList).(*ir.ArgumentList)
+		{
+			sliceClone := make([]ir.Node, len(x.Args))
+			for i := range x.Args {
+				sliceClone[i] = NodeClone(x.Args[i]).(ir.Node)
+			}
+			clone.Args = sliceClone
 		}
 		return &clone
 	case *ir.NopStmt:
@@ -1182,8 +1188,12 @@ func NodeClone(x ir.Node) ir.Node {
 		if x.Call != nil {
 			clone.Call = NodeClone(x.Call).(ir.Node)
 		}
-		if x.ArgumentList != nil {
-			clone.ArgumentList = NodeClone(x.ArgumentList).(*ir.ArgumentList)
+		{
+			sliceClone := make([]ir.Node, len(x.Args))
+			for i := range x.Args {
+				sliceClone[i] = NodeClone(x.Args[i]).(ir.Node)
+			}
+			clone.Args = sliceClone
 		}
 		return &clone
 	case *ir.StaticPropertyFetchExpr:
diff --git a/src/ir/irutil/equal.go b/src/ir/irutil/equal.go
index aeace2a..bdbcc22 100644
--- a/src/ir/irutil/equal.go
+++ b/src/ir/irutil/equal.go
@@ -26,20 +26,6 @@ func NodeEqual(x, y ir.Node) bool {
 			return false
 		}
 		return true
-	case *ir.ArgumentList:
-		y, ok := y.(*ir.ArgumentList)
-		if !ok || x == nil || y == nil {
-			return x == y
-		}
-		if len(x.Arguments) != len(y.Arguments) {
-			return false
-		}
-		for i := range x.Arguments {
-			if !NodeEqual(x.Arguments[i], y.Arguments[i]) {
-				return false
-			}
-		}
-		return true
 	case *ir.ArrayDimFetchExpr:
 		y, ok := y.(*ir.ArrayDimFetchExpr)
 		if !ok || x == nil || y == nil {
@@ -549,9 +535,14 @@ func NodeEqual(x, y ir.Node) bool {
 				return false
 			}
 		}
-		if !NodeEqual(x.ArgumentList, y.ArgumentList) {
+		if len(x.Args) != len(y.Args) {
 			return false
 		}
+		for i := range x.Args {
+			if !NodeEqual(x.Args[i], y.Args[i]) {
+				return false
+			}
+		}
 		if !NodeEqual(x.Extends, y.Extends) {
 			return false
 		}
@@ -971,9 +962,14 @@ func NodeEqual(x, y ir.Node) bool {
 		if !NodeEqual(x.Function, y.Function) {
 			return false
 		}
-		if !NodeEqual(x.ArgumentList, y.ArgumentList) {
+		if len(x.Args) != len(y.Args) {
 			return false
 		}
+		for i := range x.Args {
+			if !NodeEqual(x.Args[i], y.Args[i]) {
+				return false
+			}
+		}
 		return true
 	case *ir.FunctionStmt:
 		y, ok := y.(*ir.FunctionStmt)
@@ -1321,9 +1317,14 @@ func NodeEqual(x, y ir.Node) bool {
 		if !NodeEqual(x.Method, y.Method) {
 			return false
 		}
-		if !NodeEqual(x.ArgumentList, y.ArgumentList) {
+		if len(x.Args) != len(y.Args) {
 			return false
 		}
+		for i := range x.Args {
+			if !NodeEqual(x.Args[i], y.Args[i]) {
+				return false
+			}
+		}
 		return true
 	case *ir.MinusExpr:
 		y, ok := y.(*ir.MinusExpr)
@@ -1395,9 +1396,14 @@ func NodeEqual(x, y ir.Node) bool {
 		if !NodeEqual(x.Class, y.Class) {
 			return false
 		}
-		if !NodeEqual(x.ArgumentList, y.ArgumentList) {
+		if len(x.Args) != len(y.Args) {
 			return false
 		}
+		for i := range x.Args {
+			if !NodeEqual(x.Args[i], y.Args[i]) {
+				return false
+			}
+		}
 		return true
 	case *ir.NopStmt:
 		y, ok := y.(*ir.NopStmt)
@@ -1715,9 +1721,14 @@ func NodeEqual(x, y ir.Node) bool {
 		if !NodeEqual(x.Call, y.Call) {
 			return false
 		}
-		if !NodeEqual(x.ArgumentList, y.ArgumentList) {
+		if len(x.Args) != len(y.Args) {
 			return false
 		}
+		for i := range x.Args {
+			if !NodeEqual(x.Args[i], y.Args[i]) {
+				return false
+			}
+		}
 		return true
 	case *ir.StaticPropertyFetchExpr:
 		y, ok := y.(*ir.StaticPropertyFetchExpr)
diff --git a/src/ir/methods.go b/src/ir/methods.go
index e043eed..853d739 100644
--- a/src/ir/methods.go
+++ b/src/ir/methods.go
@@ -73,3 +73,18 @@ func (n *Name) RestParts() string {
 	}
 	return s[len(`\`)+slash:]
 }
+
+// Arg returns the ith argument.
+func (n *FunctionCallExpr) Arg(i int) *Argument { return n.Args[i].(*Argument) }
+
+// Arg returns the ith argument.
+func (n *MethodCallExpr) Arg(i int) *Argument { return n.Args[i].(*Argument) }
+
+// Arg returns the ith argument.
+func (n *NewExpr) Arg(i int) *Argument { return n.Args[i].(*Argument) }
+
+// Arg returns the ith argument.
+func (n *StaticCallExpr) Arg(i int) *Argument { return n.Args[i].(*Argument) }
+
+// Arg returns the ith argument.
+func (n *ClassStmt) Arg(i int) *Argument { return n.Args[i].(*Argument) }
diff --git a/src/ir/types.go b/src/ir/types.go
index 4392952..96a0b02 100644
--- a/src/ir/types.go
+++ b/src/ir/types.go
@@ -433,7 +433,9 @@ type FunctionCallExpr struct {
 	FreeFloating freefloating.Collection
 	Position     *position.Position
 	Function     Node
-	ArgumentList *ArgumentList
+
+	ArgsFreeFloating freefloating.Collection
+	Args             []Node
 }
 
 type ImportExpr struct {
@@ -468,14 +470,18 @@ type MethodCallExpr struct {
 	Position     *position.Position
 	Variable     Node
 	Method       Node
-	ArgumentList *ArgumentList
+
+	ArgsFreeFloating freefloating.Collection
+	Args             []Node
 }
 
 type NewExpr struct {
 	FreeFloating freefloating.Collection
 	Position     *position.Position
 	Class        Node
-	ArgumentList *ArgumentList
+
+	ArgsFreeFloating freefloating.Collection
+	Args             []Node
 }
 
 type ParenExpr struct {
@@ -538,7 +544,9 @@ type StaticCallExpr struct {
 	Position     *position.Position
 	Class        Node
 	Call         Node
-	ArgumentList *ArgumentList
+
+	ArgsFreeFloating freefloating.Collection
+	Args             []Node
 }
 
 type StaticPropertyFetchExpr struct {
@@ -595,12 +603,6 @@ type Argument struct {
 	Expr         Node
 }
 
-type ArgumentList struct {
-	FreeFloating freefloating.Collection
-	Position     *position.Position
-	Arguments    []Node
-}
-
 type Identifier struct {
 	FreeFloating freefloating.Collection
 	Position     *position.Position
@@ -717,10 +719,12 @@ type ClassStmt struct {
 	PhpDocComment string
 	ClassName     *Identifier
 	Modifiers     []*Identifier
-	ArgumentList  *ArgumentList
 	Extends       *ClassExtendsStmt
 	Implements    *ClassImplementsStmt
 	Stmts         []Node
+
+	ArgsFreeFloating freefloating.Collection
+	Args             []Node
 }
 
 type ClassConstListStmt struct {
diff --git a/src/ir/walk.go b/src/ir/walk.go
index 3b2f452..88be77c 100644
--- a/src/ir/walk.go
+++ b/src/ir/walk.go
@@ -757,8 +757,8 @@ func (n *FunctionCallExpr) Walk(v Visitor) {
 	if n.Function != nil {
 		n.Function.Walk(v)
 	}
-	if n.ArgumentList != nil {
-		n.ArgumentList.Walk(v)
+	for _, arg := range n.Args {
+		arg.Walk(v)
 	}
 	v.LeaveNode(n)
 }
@@ -810,8 +810,8 @@ func (n *MethodCallExpr) Walk(v Visitor) {
 	if n.Method != nil {
 		n.Method.Walk(v)
 	}
-	if n.ArgumentList != nil {
-		n.ArgumentList.Walk(v)
+	for _, arg := range n.Args {
+		arg.Walk(v)
 	}
 	v.LeaveNode(n)
 }
@@ -823,8 +823,8 @@ func (n *NewExpr) Walk(v Visitor) {
 	if n.Class != nil {
 		n.Class.Walk(v)
 	}
-	if n.ArgumentList != nil {
-		n.ArgumentList.Walk(v)
+	for _, arg := range n.Args {
+		arg.Walk(v)
 	}
 	v.LeaveNode(n)
 }
@@ -944,8 +944,8 @@ func (n *StaticCallExpr) Walk(v Visitor) {
 	if n.Call != nil {
 		n.Call.Walk(v)
 	}
-	if n.ArgumentList != nil {
-		n.ArgumentList.Walk(v)
+	for _, arg := range n.Args {
+		arg.Walk(v)
 	}
 	v.LeaveNode(n)
 }
@@ -1039,18 +1039,6 @@ func (n *Argument) Walk(v Visitor) {
 	v.LeaveNode(n)
 }
 
-func (n *ArgumentList) Walk(v Visitor) {
-	if !v.EnterNode(n) {
-		return
-	}
-	for i := range n.Arguments {
-		if n.Arguments[i] != nil {
-			n.Arguments[i].Walk(v)
-		}
-	}
-	v.LeaveNode(n)
-}
-
 func (n *Identifier) Walk(v Visitor) {
 	if !v.EnterNode(n) {
 		return
@@ -1241,8 +1229,8 @@ func (n *ClassStmt) Walk(v Visitor) {
 			n.Modifiers[i].Walk(v)
 		}
 	}
-	if n.ArgumentList != nil {
-		n.ArgumentList.Walk(v)
+	for _, arg := range n.Args {
+		arg.Walk(v)
 	}
 	if n.Extends != nil {
 		n.Extends.Walk(v)
diff --git a/src/irgen/irgen.go b/src/irgen/irgen.go
index 8ab1325..bdd7d2b 100644
--- a/src/irgen/irgen.go
+++ b/src/irgen/irgen.go
@@ -708,7 +708,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
 		out.Function = convertNode(state, n.Function)
-		out.ArgumentList = convertNode(state, n.ArgumentList).(*ir.ArgumentList)
+		out.ArgsFreeFloating = n.ArgumentList.FreeFloating
+		out.Args = convertNodeSlice(state, n.ArgumentList.Arguments)
 		return out
 
 	case *expr.InstanceOf:
@@ -758,7 +759,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.Position = n.Position
 		out.Variable = convertNode(state, n.Variable)
 		out.Method = convertNode(state, n.Method)
-		out.ArgumentList = convertNode(state, n.ArgumentList).(*ir.ArgumentList)
+		out.ArgsFreeFloating = n.ArgumentList.FreeFloating
+		out.Args = convertNodeSlice(state, n.ArgumentList.Arguments)
 		return out
 
 	case *expr.New:
@@ -769,7 +771,10 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
 		out.Class = convertNode(state, n.Class)
-		out.ArgumentList = convertNode(state, n.ArgumentList).(*ir.ArgumentList)
+		if n.ArgumentList != nil {
+			out.ArgsFreeFloating = n.ArgumentList.FreeFloating
+			out.Args = convertNodeSlice(state, n.ArgumentList.Arguments)
+		}
 		return out
 
 	case *expr.Paren:
@@ -881,7 +886,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.Position = n.Position
 		out.Class = convertNode(state, n.Class)
 		out.Call = convertNode(state, n.Call)
-		out.ArgumentList = convertNode(state, n.ArgumentList).(*ir.ArgumentList)
+		out.ArgsFreeFloating = n.ArgumentList.FreeFloating
+		out.Args = convertNodeSlice(state, n.ArgumentList.Arguments)
 		return out
 
 	case *expr.StaticPropertyFetch:
@@ -975,22 +981,6 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.Expr = convertNode(state, n.Expr)
 		return out
 
-	case *node.ArgumentList:
-		if n == nil {
-			return (*ir.ArgumentList)(nil)
-		}
-		out := &ir.ArgumentList{}
-		out.FreeFloating = n.FreeFloating
-		out.Position = n.Position
-		{
-			slice := make([]ir.Node, len(n.Arguments))
-			for i := range n.Arguments {
-				slice[i] = convertNode(state, n.Arguments[i])
-			}
-			out.Arguments = slice
-		}
-		return out
-
 	case *node.Identifier:
 		if n == nil {
 			return (*ir.Identifier)(nil)
@@ -1191,7 +1181,10 @@ func convertNode(state *convState, n node.Node) ir.Node {
 			}
 			out.Modifiers = slice
 		}
-		out.ArgumentList = convertNode(state, n.ArgumentList).(*ir.ArgumentList)
+		if n.ArgumentList != nil {
+			out.ArgsFreeFloating = n.ArgumentList.FreeFloating
+			out.Args = convertNodeSlice(state, n.ArgumentList.Arguments)
+		}
 		out.Extends = convertNode(state, n.Extends).(*ir.ClassExtendsStmt)
 		out.Implements = convertNode(state, n.Implements).(*ir.ClassImplementsStmt)
 		out.Stmts = convertNodeSlice(state, n.Stmts)
diff --git a/src/linter/and_walker.go b/src/linter/and_walker.go
index 22188f5..21d8511 100644
--- a/src/linter/and_walker.go
+++ b/src/linter/and_walker.go
@@ -20,21 +20,20 @@ type andWalker struct {
 func (a *andWalker) EnterNode(w ir.Node) (res bool) {
 	switch n := w.(type) {
 	case *ir.FunctionCallExpr:
-		args := n.ArgumentList.Arguments
 		nm, ok := n.Function.(*ir.Name)
 		if !ok {
 			break
 		}
 		switch {
-		case len(args) == 2 && nm.Value == `method_exists`:
-			obj := args[0].(*ir.Argument).Expr
-			methodName := args[1].(*ir.Argument).Expr
+		case len(n.Args) == 2 && nm.Value == `method_exists`:
+			obj := n.Arg(0).Expr
+			methodName := n.Arg(1).Expr
 			lit, ok := methodName.(*ir.String)
 			if ok {
 				a.b.ctx.addCustomMethod(obj, unquote(lit.Value))
 			}
-		case len(args) == 1 && nm.Value == `function_exists`:
-			functionName := args[0].(*ir.Argument).Expr
+		case len(n.Args) == 1 && nm.Value == `function_exists`:
+			functionName := n.Arg(0).Expr
 			lit, ok := functionName.(*ir.String)
 			if ok {
 				a.b.ctx.addCustomFunction(unquote(lit.Value))
diff --git a/src/linter/block.go b/src/linter/block.go
index b160cb9..a134608 100644
--- a/src/linter/block.go
+++ b/src/linter/block.go
@@ -755,9 +755,9 @@ func (b *BlockWalker) handleFunctionCall(e *ir.FunctionCallExpr) bool {
 	e.Function.Walk(b)
 
 	if call.fqName == `\compact` {
-		b.handleCompactCallArgs(e.ArgumentList.Arguments)
+		b.handleCompactCallArgs(e.Args)
 	} else {
-		b.handleCallArgs(e.Function, e.ArgumentList.Arguments, call.info)
+		b.handleCallArgs(e.Function, e.Args, call.info)
 	}
 	b.ctx.exitFlags |= call.info.ExitFlags
 
@@ -866,7 +866,7 @@ func (b *BlockWalker) handleMethodCall(e *ir.MethodCallExpr) bool {
 	}
 
 	if !magic {
-		b.handleCallArgs(e.Method, e.ArgumentList.Arguments, fn)
+		b.handleCallArgs(e.Method, e.Args, fn)
 	}
 	b.ctx.exitFlags |= fn.ExitFlags
 
@@ -917,7 +917,7 @@ func (b *BlockWalker) handleStaticCall(e *ir.StaticCallExpr) bool {
 		b.r.Report(e.Call, LevelError, "accessLevel", "Cannot access %s method %s::%s()", fn.AccessLevel, m.ClassName, methodName)
 	}
 
-	b.handleCallArgs(e.Call, e.ArgumentList.Arguments, fn)
+	b.handleCallArgs(e.Call, e.Args, fn)
 	b.ctx.exitFlags |= fn.ExitFlags
 
 	return false
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index fca6c38..42ccb6f 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -363,11 +363,7 @@ func (b *blockLinter) checkNew(e *ir.NewExpr) {
 	ctor := m.Info
 	// If new expression is written without (), ArgumentList will be nil.
 	// It's equivalent of 0 arguments constructor call.
-	var args []ir.Node
-	if e.ArgumentList != nil {
-		args = e.ArgumentList.Arguments
-	}
-	if ok && !enoughArgs(args, ctor) {
+	if ok && !enoughArgs(e.Args, ctor) {
 		b.report(e, LevelError, "argCount", "Too few arguments for %s constructor", className)
 	}
 }
@@ -603,10 +599,10 @@ func (b *blockLinter) checkFunctionCall(e *ir.FunctionCallExpr) {
 
 	switch fqName {
 	case `\preg_match`, `\preg_match_all`, `\preg_replace`, `\preg_split`:
-		if len(e.ArgumentList.Arguments) < 1 {
+		if len(e.Args) < 1 {
 			break
 		}
-		b.checkRegexp(e, e.ArgumentList.Arguments[0].(*ir.Argument))
+		b.checkRegexp(e, e.Arg(0))
 	}
 }
 
diff --git a/src/linter/root.go b/src/linter/root.go
index 1da2bdf..7804c91 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -1578,19 +1578,19 @@ func (d *RootWalker) enterFunctionCall(s *ir.FunctionCallExpr) bool {
 		return d.handleOverride(s)
 	}
 
-	if nm.Value != `define` || len(s.ArgumentList.Arguments) < 2 {
+	if nm.Value != `define` || len(s.Args) < 2 {
 		// TODO: actually we could warn about bogus defines
 		return true
 	}
 
-	arg := s.ArgumentList.Arguments[0].(*ir.Argument)
+	arg := s.Arg(0)
 
 	str, ok := arg.Expr.(*ir.String)
 	if !ok {
 		return true
 	}
 
-	valueArg := s.ArgumentList.Arguments[1].(*ir.Argument)
+	valueArg := s.Arg(1)
 
 	if d.meta.Constants == nil {
 		d.meta.Constants = make(meta.ConstantsMap)
@@ -1606,12 +1606,12 @@ func (d *RootWalker) enterFunctionCall(s *ir.FunctionCallExpr) bool {
 // Handle e.g. "override(\array_shift(0), elementType(0));"
 // which means "return type of array_shift() is the type of element of first function parameter"
 func (d *RootWalker) handleOverride(s *ir.FunctionCallExpr) bool {
-	if len(s.ArgumentList.Arguments) != 2 {
+	if len(s.Args) != 2 {
 		return true
 	}
 
-	arg0 := s.ArgumentList.Arguments[0].(*ir.Argument)
-	arg1 := s.ArgumentList.Arguments[1].(*ir.Argument)
+	arg0 := s.Arg(0)
+	arg1 := s.Arg(1)
 
 	fc0, ok := arg0.Expr.(*ir.FunctionCallExpr)
 	if !ok {
@@ -1633,11 +1633,11 @@ func (d *RootWalker) handleOverride(s *ir.FunctionCallExpr) bool {
 		return true
 	}
 
-	if len(fc1.ArgumentList.Arguments) != 1 {
+	if len(fc1.Args) != 1 {
 		return true
 	}
 
-	fc1Arg0 := fc1.ArgumentList.Arguments[0].(*ir.Argument)
+	fc1Arg0 := fc1.Arg(0)
 
 	argNumNode, ok := fc1Arg0.Expr.(*ir.Lnumber)
 	if !ok {
diff --git a/src/phpgrep/matcher.go b/src/phpgrep/matcher.go
index 195b9cb..b4be994 100644
--- a/src/phpgrep/matcher.go
+++ b/src/phpgrep/matcher.go
@@ -307,10 +307,13 @@ func (m *matcher) eqNode(state *matcherState, x, y ir.Node) bool {
 		if !ok || !m.eqNode(state, x.Class, y.Class) {
 			return false
 		}
-		if x.ArgumentList == nil || y.ArgumentList == nil {
-			return x.ArgumentList == y.ArgumentList
+		if x.Args == nil {
+			return y.Args == nil
 		}
-		return m.eqNodeSlice(state, x.ArgumentList.Arguments, y.ArgumentList.Arguments)
+		if y.Args == nil {
+			return x.Args == nil
+		}
+		return m.eqNodeSlice(state, x.Args, y.Args)
 
 	case *ir.CaseStmt:
 		y, ok := y.(*ir.CaseStmt)
@@ -401,7 +404,7 @@ func (m *matcher) eqNode(state *matcherState, x, y ir.Node) bool {
 		if !ok || !m.eqNode(state, x.Function, y.Function) {
 			return false
 		}
-		return m.eqNodeSlice(state, x.ArgumentList.Arguments, y.ArgumentList.Arguments)
+		return m.eqNodeSlice(state, x.Args, y.Args)
 
 	case *ir.PostIncExpr:
 		y, ok := y.(*ir.PostIncExpr)
@@ -469,7 +472,7 @@ func (m *matcher) eqNode(state *matcherState, x, y ir.Node) bool {
 		return ok &&
 			m.eqNode(state, x.Class, y.Class) &&
 			m.eqNode(state, x.Call, y.Call) &&
-			m.eqNodeSlice(state, x.ArgumentList.Arguments, y.ArgumentList.Arguments)
+			m.eqNodeSlice(state, x.Args, y.Args)
 
 	case *ir.ShellExecExpr:
 		y, ok := y.(*ir.ShellExecExpr)
@@ -618,7 +621,7 @@ func (m *matcher) eqNode(state *matcherState, x, y ir.Node) bool {
 		y, ok := y.(*ir.MethodCallExpr)
 		return ok && m.eqNode(state, x.Variable, y.Variable) &&
 			m.eqNode(state, x.Method, y.Method) &&
-			m.eqNodeSlice(state, x.ArgumentList.Arguments, y.ArgumentList.Arguments)
+			m.eqNodeSlice(state, x.Args, y.Args)
 
 	case *ir.TypeCastExpr:
 		y, ok := y.(*ir.TypeCastExpr)
diff --git a/src/solver/exprtype.go b/src/solver/exprtype.go
index 1acd199..f2809fa 100644
--- a/src/solver/exprtype.go
+++ b/src/solver/exprtype.go
@@ -72,11 +72,11 @@ func internalFuncType(nm string, sc *meta.Scope, cs *meta.ClassParseState, c *ir
 	}
 
 	override, ok := meta.GetInternalFunctionOverrideInfo(nm)
-	if !ok || len(c.ArgumentList.Arguments) <= override.ArgNum {
+	if !ok || len(c.Args) <= override.ArgNum {
 		return fn.Typ, true
 	}
 
-	arg := c.ArgumentList.Arguments[override.ArgNum].(*ir.Argument)
+	arg := c.Arg(override.ArgNum)
 	typ = ExprTypeLocalCustom(sc, cs, arg.Expr, custom)
 	if override.OverrideType == meta.OverrideArgType {
 		return typ, true


Test patch:
diff --git a/src/ir/irfmt/printer_test.go b/src/ir/irfmt/printer_test.go
index dec167f..4f60f96 100644
--- a/src/ir/irfmt/printer_test.go
+++ b/src/ir/irfmt/printer_test.go
@@ -1432,19 +1432,17 @@ func TestPrintFunctionCall(t *testing.T) {
 	p := NewPrettyPrinter(o, "    ")
 	p.Print(&ir.FunctionCallExpr{
 		Function: &ir.SimpleVar{Name: "var"},
-		ArgumentList: &ir.ArgumentList{
-			Arguments: []ir.Node{
-				&ir.Argument{
-					IsReference: true,
-					Expr:        &ir.SimpleVar{Name: "a"},
-				},
-				&ir.Argument{
-					Variadic: true,
-					Expr:     &ir.SimpleVar{Name: "b"},
-				},
-				&ir.Argument{
-					Expr: &ir.SimpleVar{Name: "c"},
-				},
+		Args: []ir.Node{
+			&ir.Argument{
+				IsReference: true,
+				Expr:        &ir.SimpleVar{Name: "a"},
+			},
+			&ir.Argument{
+				Variadic: true,
+				Expr:     &ir.SimpleVar{Name: "b"},
+			},
+			&ir.Argument{
+				Expr: &ir.SimpleVar{Name: "c"},
 			},
 		},
 	})
@@ -1560,14 +1558,12 @@ func TestPrintMethodCall(t *testing.T) {
 	p.Print(&ir.MethodCallExpr{
 		Variable: &ir.SimpleVar{Name: "foo"},
 		Method:   &ir.Identifier{Value: "bar"},
-		ArgumentList: &ir.ArgumentList{
-			Arguments: []ir.Node{
-				&ir.Argument{
-					Expr: &ir.SimpleVar{Name: "a"},
-				},
-				&ir.Argument{
-					Expr: &ir.SimpleVar{Name: "b"},
-				},
+		Args: []ir.Node{
+			&ir.Argument{
+				Expr: &ir.SimpleVar{Name: "a"},
+			},
+			&ir.Argument{
+				Expr: &ir.SimpleVar{Name: "b"},
 			},
 		},
 	})
@@ -1586,14 +1582,12 @@ func TestPrintNew(t *testing.T) {
 	p := NewPrettyPrinter(o, "    ")
 	p.Print(&ir.NewExpr{
 		Class: &ir.Name{Value: "Foo"},
-		ArgumentList: &ir.ArgumentList{
-			Arguments: []ir.Node{
-				&ir.Argument{
-					Expr: &ir.SimpleVar{Name: "a"},
-				},
-				&ir.Argument{
-					Expr: &ir.SimpleVar{Name: "b"},
-				},
+		Args: []ir.Node{
+			&ir.Argument{
+				Expr: &ir.SimpleVar{Name: "a"},
+			},
+			&ir.Argument{
+				Expr: &ir.SimpleVar{Name: "b"},
 			},
 		},
 	})
@@ -1834,14 +1828,12 @@ func TestPrintStaticCall(t *testing.T) {
 	p.Print(&ir.StaticCallExpr{
 		Class: &ir.Identifier{Value: "Foo"},
 		Call:  &ir.Identifier{Value: "bar"},
-		ArgumentList: &ir.ArgumentList{
-			Arguments: []ir.Node{
-				&ir.Argument{
-					Expr: &ir.SimpleVar{Name: "a"},
-				},
-				&ir.Argument{
-					Expr: &ir.SimpleVar{Name: "b"},
-				},
+		Args: []ir.Node{
+			&ir.Argument{
+				Expr: &ir.SimpleVar{Name: "a"},
+			},
+			&ir.Argument{
+				Expr: &ir.SimpleVar{Name: "b"},
 			},
 		},
 	})
@@ -2494,14 +2486,12 @@ func TestPrintStmtAnonymousClass(t *testing.T) {
 		Stmts: []ir.Node{
 			&ir.ClassStmt{
 				Modifiers: []*ir.Identifier{{Value: "abstract"}},
-				ArgumentList: &ir.ArgumentList{
-					Arguments: []ir.Node{
-						&ir.Argument{
-							Expr: &ir.SimpleVar{Name: "a"},
-						},
-						&ir.Argument{
-							Expr: &ir.SimpleVar{Name: "b"},
-						},
+				Args: []ir.Node{
+					&ir.Argument{
+						Expr: &ir.SimpleVar{Name: "a"},
+					},
+					&ir.Argument{
+						Expr: &ir.SimpleVar{Name: "b"},
 					},
 				},
 				Extends: &ir.ClassExtendsStmt{
diff --git a/src/tests/exprtype/exprtype_test.go b/src/tests/exprtype/exprtype_test.go
index 6022c07..6e02789 100644
--- a/src/tests/exprtype/exprtype_test.go
+++ b/src/tests/exprtype/exprtype_test.go
@@ -2077,8 +2077,8 @@ func (w *exprTypeWalker) LeaveNode(n ir.Node) {}
 func (w *exprTypeWalker) EnterNode(n ir.Node) bool {
 	call, ok := n.(*ir.FunctionCallExpr)
 	if ok && meta.NameNodeEquals(call.Function, `exprtype`) {
-		checkedExpr := call.ArgumentList.Arguments[0].(*ir.Argument).Expr
-		expectedType := call.ArgumentList.Arguments[1].(*ir.Argument).Expr.(*ir.String).Value
+		checkedExpr := call.Arg(0).Expr
+		expectedType := call.Arg(1).Expr.(*ir.String).Value
 		actualType, ok := exprTypeResult[checkedExpr]
 		if !ok {
 			w.t.Fatalf("no type found for %s expression", irutil.FmtNode(checkedExpr))
@@ -2113,7 +2113,7 @@ func (c *exprTypeCollector) AfterEnterNode(n ir.Node) {
 	if !ok || !meta.NameNodeEquals(call.Function, `exprtype`) {
 		return
 	}
-	checkedExpr := call.ArgumentList.Arguments[0].(*ir.Argument).Expr
+	checkedExpr := call.Arg(0).Expr
 
 	// We need to clone a types map because if it belongs to a var
 	// or some other symbol those type can be volatile we'll get