Repository: VKCOM/noverify
Pull request #1187 (2022-08-02 20:09:01)

ir: added parent field for all nodes
The ability to walk up in AST is a useful feature that will allow us to 
remove some of the crutches that we have been doing before to get around 
this limitation.

Also added `FindParent()` and `FindChild()` functions.

This is part of a redesign of the entire linter architecture for greater flexibility.


Patch:
diff --git a/src/ir/codegen/gen_clone.go b/src/ir/codegen/gen_clone.go
index 3d0a3a1..5626445 100644
--- a/src/ir/codegen/gen_clone.go
+++ b/src/ir/codegen/gen_clone.go
@@ -52,6 +52,10 @@ func (g *genClone) writeCloneCase(w *bytes.Buffer, pkg *packageData, typ *typeDa
 
 	for i := 0; i < typ.info.NumFields(); i++ {
 		field := typ.info.Field(i)
+		if field.Name() == "ParentNode" {
+			continue
+		}
+
 		switch typeString := field.Type().String(); typeString {
 		case "[]ir.Node":
 			fmt.Fprintf(w, "    clone.%[1]s = NodeSliceClone(x.%[1]s)\n", field.Name())
diff --git a/src/ir/codegen/gen_equal.go b/src/ir/codegen/gen_equal.go
index 2003e65..a86f234 100644
--- a/src/ir/codegen/gen_equal.go
+++ b/src/ir/codegen/gen_equal.go
@@ -43,6 +43,10 @@ func (g *genEqual) writeCompare(w *bytes.Buffer, pkg *packageData, typ *typeData
 	w.WriteString("    if !ok || x == nil || y == nil { return x == y }\n")
 	for i := 0; i < typ.info.NumFields(); i++ {
 		field := typ.info.Field(i)
+		if field.Name() == "ParentNode" {
+			continue
+		}
+
 		switch typeString := field.Type().String(); typeString {
 		case "string", "bool":
 			fmt.Fprintf(w, "    if x.%[1]s != y.%[1]s { return false }\n", field.Name())
diff --git a/src/ir/codegen/gen_get_first_token.go b/src/ir/codegen/gen_get_first_token.go
index a32d89e..5bb6235 100644
--- a/src/ir/codegen/gen_get_first_token.go
+++ b/src/ir/codegen/gen_get_first_token.go
@@ -40,6 +40,10 @@ func (g *genGetFirstToken) Run() error {
 func (g *genGetFirstToken) writeGet(w *bytes.Buffer, pkg *packageData, typ *typeData) {
 	for i := 0; i < typ.info.NumFields(); i++ {
 		field := typ.info.Field(i)
+		if field.Name() == "ParentNode" {
+			continue
+		}
+
 		switch typeString := field.Type().String(); typeString {
 		case "*github.com/VKCOM/php-parser/pkg/token.Token":
 			fmt.Fprintf(w, "    if n.%s != nil {\n", field.Name())
diff --git a/src/ir/codegen/gen_iterate_tokens.go b/src/ir/codegen/gen_iterate_tokens.go
index 487aae6..241ac05 100644
--- a/src/ir/codegen/gen_iterate_tokens.go
+++ b/src/ir/codegen/gen_iterate_tokens.go
@@ -32,6 +32,10 @@ func (g *genIterate) Run() error {
 func (g *genIterate) writeIterate(w *bytes.Buffer, pkg *packageData, typ *typeData) {
 	for i := 0; i < typ.info.NumFields(); i++ {
 		field := typ.info.Field(i)
+		if field.Name() == "ParentNode" {
+			continue
+		}
+
 		switch typeString := field.Type().String(); typeString {
 		case "*github.com/VKCOM/php-parser/pkg/token.Token":
 			fmt.Fprintf(w, "    if !traverseToken(n.%s, cb) {\n", field.Name())
diff --git a/src/ir/codegen/gen_parent.go b/src/ir/codegen/gen_parent.go
new file mode 100644
index 0000000..ab6d65e
--- /dev/null
+++ b/src/ir/codegen/gen_parent.go
@@ -0,0 +1,33 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+)
+
+type genParent struct {
+	ctx *context
+}
+
+func (g *genParent) Run() error {
+	ctx := g.ctx
+
+	var buf bytes.Buffer
+	for _, typ := range ctx.irPkg.types {
+		g.writeParent(&buf, typ)
+		buf.WriteString("\n")
+	}
+
+	return ctx.WriteGoFile(codegenFile{
+		filename: "get_parent.go",
+		pkgPath:  "ir",
+		deps:     []string{},
+		contents: buf.Bytes(),
+	})
+}
+
+func (g *genParent) writeParent(w *bytes.Buffer, typ *typeData) {
+	fmt.Fprintf(w, "func (n *%s) Parent() Node {\n", typ.name)
+	w.WriteString("   return n.ParentNode\n")
+	w.WriteString("}\n")
+}
diff --git a/src/ir/codegen/gen_walk.go b/src/ir/codegen/gen_walk.go
index 251e220..a6cd5a7 100644
--- a/src/ir/codegen/gen_walk.go
+++ b/src/ir/codegen/gen_walk.go
@@ -42,6 +42,10 @@ func (g *genWalk) writeFieldsWalk(w *bytes.Buffer, typ *types.Struct) {
 }
 
 func (g *genWalk) writeFieldWalk(w *bytes.Buffer, field *types.Var) {
+	if field.Name() == "ParentNode" {
+		return
+	}
+
 	// Embedded structs are handles as a normal members of the struct.
 	if field.Embedded() {
 		g.writeFieldsWalk(w, field.Type().Underlying().(*types.Struct))
diff --git a/src/ir/codegen/main.go b/src/ir/codegen/main.go
index e917916..797a0d5 100644
--- a/src/ir/codegen/main.go
+++ b/src/ir/codegen/main.go
@@ -41,6 +41,7 @@ func main() {
 		{"generate get position", doGenerateGetPosition},
 		{"generate iterate tokens", doGenerateIterateTokens},
 		{"generate get first token", doGenerateGetFirstToken},
+		{"generate parent", doGenerateParent},
 	}
 
 	for _, step := range steps {
@@ -122,3 +123,8 @@ func doGenerateGetFirstToken(ctx *context) error {
 	g := &genGetFirstToken{ctx: ctx}
 	return g.Run()
 }
+
+func doGenerateParent(ctx *context) error {
+	g := &genParent{ctx: ctx}
+	return g.Run()
+}
diff --git a/src/ir/get_first_token.go b/src/ir/get_first_token.go
index 670f2ee..8c4a10d 100644
--- a/src/ir/get_first_token.go
+++ b/src/ir/get_first_token.go
@@ -1721,8 +1721,8 @@ func GetFirstToken(n Node) *token.Token {
 				return GetFirstToken(n.Modifiers[0])
 			}
 		}
-		if n.Type != nil {
-			return GetFirstToken(n.Type)
+		if n.PropertyType != nil {
+			return GetFirstToken(n.PropertyType)
 		}
 		if len(n.Properties) != 0 {
 			if n.Properties[0] != nil {
diff --git a/src/ir/get_parent.go b/src/ir/get_parent.go
new file mode 100644
index 0000000..89a90e2
--- /dev/null
+++ b/src/ir/get_parent.go
@@ -0,0 +1,642 @@
+// Code generated by the `ir/codegen` package. DO NOT EDIT.
+package ir
+
+func (n *AnonClassExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Argument) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ArrayDimFetchExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ArrayExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ArrayItemExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ArrowFunctionExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Assign) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignBitwiseAnd) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignBitwiseOr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignBitwiseXor) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignCoalesce) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignConcat) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignDiv) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignMinus) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignMod) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignMul) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignPlus) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignPow) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignReference) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignShiftLeft) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AssignShiftRight) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Attribute) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *AttributeGroup) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *BadString) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *BitwiseAndExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *BitwiseNotExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *BitwiseOrExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *BitwiseXorExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *BooleanAndExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *BooleanNotExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *BooleanOrExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *BreakStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *CaseStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *CatchStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ClassConstFetchExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ClassConstListStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ClassExtendsStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ClassImplementsStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ClassMethodStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ClassStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *CloneExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *CloseTagStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ClosureExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ClosureUsesExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *CoalesceExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ConcatExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ConstFetchExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ConstListStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ConstantStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ContinueStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *DeclareStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *DefaultStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *DivExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Dnumber) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *DoStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *EchoStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ElseIfStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ElseStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *EmptyExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Encapsed) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *EncapsedStringPart) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *EqualExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ErrorSuppressExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *EvalExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ExitExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ExpressionStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *FinallyStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ForStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ForeachStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *FunctionCallExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *FunctionStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *GlobalStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *GotoStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *GreaterExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *GreaterOrEqualExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *GroupUseStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *HaltCompilerStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Heredoc) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *IdenticalExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Identifier) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *IfStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ImportExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *InlineHTMLStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *InstanceOfExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *InterfaceExtendsStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *InterfaceStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *IssetExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *LabelStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ListExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Lnumber) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *LogicalAndExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *LogicalOrExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *LogicalXorExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *MagicConstant) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *MatchArm) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *MatchExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *MethodCallExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *MinusExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ModExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *MulExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Name) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *NamespaceStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *NewExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *NopStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *NotEqualExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *NotIdenticalExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Nullable) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *NullsafeMethodCallExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *NullsafePropertyFetchExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Parameter) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ParenExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *PlusExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *PostDecExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *PostIncExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *PowExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *PreDecExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *PreIncExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *PrintExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *PropertyFetchExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *PropertyListStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *PropertyStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ReferenceExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ReturnStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Root) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ShellExecExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ShiftLeftExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ShiftRightExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *SimpleVar) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *SmallerExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *SmallerOrEqualExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *SpaceshipExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *StaticCallExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *StaticPropertyFetchExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *StaticStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *StaticVarStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *StmtList) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *String) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *SwitchStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *TernaryExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ThrowExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *ThrowStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *TraitAdaptationListStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *TraitMethodRefStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *TraitStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *TraitUseAliasStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *TraitUsePrecedenceStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *TraitUseStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *TryStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *TypeCastExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *UnaryMinusExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *UnaryPlusExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Union) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *UnsetCastExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *UnsetStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *UseListStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *UseStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *Var) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *WhileStmt) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *YieldExpr) Parent() Node {
+	return n.ParentNode
+}
+
+func (n *YieldFromExpr) Parent() Node {
+	return n.ParentNode
+}
diff --git a/src/ir/ir.go b/src/ir/ir.go
index 6f8d68e..3116908 100644
--- a/src/ir/ir.go
+++ b/src/ir/ir.go
@@ -12,6 +12,7 @@ import (
 // Node is a type that is implemented by all IR types.
 // node_types.go contains all implementations.
 type Node interface {
+	Parent() Node
 	Walk(Visitor)
 	IterateTokens(func(*token.Token) bool)
 }
diff --git a/src/ir/irconv/irconv.go b/src/ir/irconv/irconv.go
index 9c07220..e1771bf 100644
--- a/src/ir/irconv/irconv.go
+++ b/src/ir/irconv/irconv.go
@@ -20,6 +20,7 @@ func ConvertNode(n ast.Vertex) ir.Node {
 
 type Converter struct {
 	namespace string
+	nodeStack []ir.Node
 
 	phpdocTypeParser *phpdoc.TypeParser
 }
@@ -33,9 +34,25 @@ type Converter struct {
 func NewConverter(typeParser *phpdoc.TypeParser) *Converter {
 	return &Converter{
 		phpdocTypeParser: typeParser,
+		nodeStack:        make([]ir.Node, 50),
 	}
 }
 
+func (c *Converter) push(n ir.Node) {
+	c.nodeStack = append(c.nodeStack, n)
+}
+
+func (c *Converter) pop() {
+	c.nodeStack = c.nodeStack[:len(c.nodeStack)-1]
+}
+
+func (c *Converter) back() ir.Node {
+	if len(c.nodeStack) == 0 {
+		return nil
+	}
+	return c.nodeStack[len(c.nodeStack)-1]
+}
+
 func (c *Converter) ConvertRoot(n *ast.Root) *ir.Root {
 	return c.ConvertNode(n).(*ir.Root)
 }
@@ -67,6 +84,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.Assign)(nil)
 		}
 		out := &ir.Assign{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
@@ -96,6 +115,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			}
 		}
 
+		c.pop()
 		return out
 
 	case *ast.ExprAssignBitwiseAnd:
@@ -103,10 +123,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignBitwiseAnd)(nil)
 		}
 		out := &ir.AssignBitwiseAnd{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignBitwiseOr:
@@ -114,10 +137,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignBitwiseOr)(nil)
 		}
 		out := &ir.AssignBitwiseOr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignBitwiseXor:
@@ -125,10 +151,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignBitwiseXor)(nil)
 		}
 		out := &ir.AssignBitwiseXor{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignCoalesce:
@@ -136,10 +165,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignCoalesce)(nil)
 		}
 		out := &ir.AssignCoalesce{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignConcat:
@@ -147,10 +179,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignConcat)(nil)
 		}
 		out := &ir.AssignConcat{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignDiv:
@@ -158,10 +193,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignDiv)(nil)
 		}
 		out := &ir.AssignDiv{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignMinus:
@@ -169,10 +207,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignMinus)(nil)
 		}
 		out := &ir.AssignMinus{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignMod:
@@ -180,10 +221,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignMod)(nil)
 		}
 		out := &ir.AssignMod{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignMul:
@@ -191,10 +235,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignMul)(nil)
 		}
 		out := &ir.AssignMul{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignPlus:
@@ -202,10 +249,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignPlus)(nil)
 		}
 		out := &ir.AssignPlus{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignPow:
@@ -213,10 +263,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignPow)(nil)
 		}
 		out := &ir.AssignPow{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignReference:
@@ -224,10 +277,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignReference)(nil)
 		}
 		out := &ir.AssignReference{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignShiftLeft:
@@ -235,10 +291,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignShiftLeft)(nil)
 		}
 		out := &ir.AssignShiftLeft{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprAssignShiftRight:
@@ -246,10 +305,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AssignShiftRight)(nil)
 		}
 		out := &ir.AssignShiftRight{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryBitwiseAnd:
@@ -257,10 +319,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.BitwiseAndExpr)(nil)
 		}
 		out := &ir.BitwiseAndExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryBitwiseOr:
@@ -268,10 +333,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.BitwiseOrExpr)(nil)
 		}
 		out := &ir.BitwiseOrExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryBitwiseXor:
@@ -279,10 +347,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.BitwiseXorExpr)(nil)
 		}
 		out := &ir.BitwiseXorExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryBooleanAnd:
@@ -290,10 +361,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.BooleanAndExpr)(nil)
 		}
 		out := &ir.BooleanAndExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryBooleanOr:
@@ -301,10 +375,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.BooleanOrExpr)(nil)
 		}
 		out := &ir.BooleanOrExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryCoalesce:
@@ -312,10 +389,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.CoalesceExpr)(nil)
 		}
 		out := &ir.CoalesceExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryConcat:
@@ -323,10 +403,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ConcatExpr)(nil)
 		}
 		out := &ir.ConcatExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryDiv:
@@ -334,10 +417,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.DivExpr)(nil)
 		}
 		out := &ir.DivExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryEqual:
@@ -345,10 +431,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.EqualExpr)(nil)
 		}
 		out := &ir.EqualExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryGreater:
@@ -356,10 +445,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.GreaterExpr)(nil)
 		}
 		out := &ir.GreaterExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryGreaterOrEqual:
@@ -367,10 +459,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.GreaterOrEqualExpr)(nil)
 		}
 		out := &ir.GreaterOrEqualExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryIdentical:
@@ -378,10 +473,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.IdenticalExpr)(nil)
 		}
 		out := &ir.IdenticalExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryLogicalAnd:
@@ -389,10 +487,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.LogicalAndExpr)(nil)
 		}
 		out := &ir.LogicalAndExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryLogicalOr:
@@ -400,10 +501,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.LogicalOrExpr)(nil)
 		}
 		out := &ir.LogicalOrExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryLogicalXor:
@@ -411,10 +515,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.LogicalXorExpr)(nil)
 		}
 		out := &ir.LogicalXorExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryMinus:
@@ -422,10 +529,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.MinusExpr)(nil)
 		}
 		out := &ir.MinusExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryMod:
@@ -433,10 +543,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ModExpr)(nil)
 		}
 		out := &ir.ModExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryMul:
@@ -444,10 +557,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.MulExpr)(nil)
 		}
 		out := &ir.MulExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryNotEqual:
@@ -455,10 +571,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.NotEqualExpr)(nil)
 		}
 		out := &ir.NotEqualExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryNotIdentical:
@@ -466,10 +585,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.NotIdenticalExpr)(nil)
 		}
 		out := &ir.NotIdenticalExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryPlus:
@@ -477,10 +599,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.PlusExpr)(nil)
 		}
 		out := &ir.PlusExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryPow:
@@ -488,10 +613,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.PowExpr)(nil)
 		}
 		out := &ir.PowExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryShiftLeft:
@@ -499,10 +627,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ShiftLeftExpr)(nil)
 		}
 		out := &ir.ShiftLeftExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinaryShiftRight:
@@ -510,10 +641,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ShiftRightExpr)(nil)
 		}
 		out := &ir.ShiftRightExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinarySmaller:
@@ -521,10 +655,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.SmallerExpr)(nil)
 		}
 		out := &ir.SmallerExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinarySmallerOrEqual:
@@ -532,10 +669,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.SmallerOrEqualExpr)(nil)
 		}
 		out := &ir.SmallerOrEqualExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprBinarySpaceship:
@@ -543,10 +683,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.SpaceshipExpr)(nil)
 		}
 		out := &ir.SpaceshipExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpTkn = n.OpTkn
 		out.Left = c.convNode(n.Left)
 		out.Right = c.convNode(n.Right)
+		c.pop()
 		return out
 
 	case *ast.ExprCastArray:
@@ -568,9 +711,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.UnsetCastExpr)(nil)
 		}
 		out := &ir.UnsetCastExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.CastTkn = n.CastTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprArray:
@@ -578,6 +724,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ArrayExpr)(nil)
 		}
 		out := &ir.ArrayExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ArrayTkn = n.ArrayTkn
 		out.OpenBracketTkn = n.OpenBracketTkn
@@ -593,6 +741,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		}
 
 		out.ShortSyntax = !hasValue(n.ArrayTkn)
+		c.pop()
 		return out
 
 	case *ast.ExprArrayDimFetch:
@@ -600,6 +749,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ArrayDimFetchExpr)(nil)
 		}
 		out := &ir.ArrayDimFetchExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpenBracketTkn = n.OpenBracketTkn
 		out.CloseBracketTkn = n.CloseBracketTkn
@@ -608,6 +759,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 
 		out.CurlyBrace = hasValue(out.OpenBracketTkn) && out.OpenBracketTkn.Value[0] == '{'
 
+		c.pop()
 		return out
 
 	case *ast.ExprArrayItem:
@@ -615,6 +767,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ArrayItemExpr)(nil)
 		}
 		out := &ir.ArrayItemExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EllipsisTkn = n.EllipsisTkn
 		out.DoubleArrowTkn = n.DoubleArrowTkn
@@ -633,6 +787,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		}
 
 		out.Unpack = hasValue(n.EllipsisTkn)
+		c.pop()
 		return out
 
 	case *ast.ExprArrowFunction:
@@ -640,6 +795,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ArrowFunctionExpr)(nil)
 		}
 		out := &ir.ArrowFunctionExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		if n.AttrGroups != nil {
@@ -670,6 +827,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Expr = c.convNode(n.Expr)
 
 		out.Doc = c.getPHPDoc(ir.GetFirstToken(out))
+		c.pop()
 		return out
 
 	case *ast.ExprBitwiseNot:
@@ -677,9 +835,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.BitwiseNotExpr)(nil)
 		}
 		out := &ir.BitwiseNotExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.TildaTkn = n.TildaTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprBooleanNot:
@@ -687,9 +848,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.BooleanNotExpr)(nil)
 		}
 		out := &ir.BooleanNotExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ExclamationTkn = n.ExclamationTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprClassConstFetch:
@@ -697,10 +861,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ClassConstFetchExpr)(nil)
 		}
 		out := &ir.ClassConstFetchExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.DoubleColonTkn = n.DoubleColonTkn
 		out.Class = c.convNode(n.Class)
 		out.ConstantName = c.convNode(n.Const).(*ir.Identifier)
+		c.pop()
 		return out
 
 	case *ast.ExprClone:
@@ -708,9 +875,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.CloneExpr)(nil)
 		}
 		out := &ir.CloneExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.CloneTkn = n.CloneTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprClosure:
@@ -718,6 +888,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ClosureExpr)(nil)
 		}
 		out := &ir.ClosureExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		if n.AttrGroups != nil {
@@ -757,6 +929,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Static = hasValue(n.StaticTkn)
 
 		out.Doc = c.getPHPDoc(ir.GetFirstToken(out))
+		c.pop()
 		return out
 
 	case *ast.ExprClosureUse:
@@ -790,8 +963,11 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ConstFetchExpr)(nil)
 		}
 		out := &ir.ConstFetchExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.Constant = c.convNode(n.Const).(*ir.Name)
+		c.pop()
 		return out
 
 	case *ast.ExprEmpty:
@@ -799,11 +975,14 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.EmptyExpr)(nil)
 		}
 		out := &ir.EmptyExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EmptyTkn = n.EmptyTkn
 		out.OpenParenthesisTkn = n.OpenParenthesisTkn
 		out.CloseParenthesisTkn = n.CloseParenthesisTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprErrorSuppress:
@@ -811,9 +990,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ErrorSuppressExpr)(nil)
 		}
 		out := &ir.ErrorSuppressExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.AtTkn = n.AtTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprEval:
@@ -821,11 +1003,14 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.EvalExpr)(nil)
 		}
 		out := &ir.EvalExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EvalTkn = n.EvalTkn
 		out.OpenParenthesisTkn = n.OpenParenthesisTkn
 		out.CloseParenthesisTkn = n.CloseParenthesisTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprExit:
@@ -833,6 +1018,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ExitExpr)(nil)
 		}
 		out := &ir.ExitExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ExitTkn = n.ExitTkn
 		out.OpenParenthesisTkn = n.OpenParenthesisTkn
@@ -840,6 +1027,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Expr = c.convNode(n.Expr)
 
 		out.Die = hasValue(n.ExitTkn) && bytes.EqualFold(n.ExitTkn.Value, []byte("die"))
+		c.pop()
 		return out
 
 	case *ast.ExprFunctionCall:
@@ -847,12 +1035,15 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.FunctionCallExpr)(nil)
 		}
 		out := &ir.FunctionCallExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpenParenthesisTkn = n.OpenParenthesisTkn
 		out.SeparatorTkns = n.SeparatorTkns
 		out.CloseParenthesisTkn = n.CloseParenthesisTkn
 		out.Function = c.convNode(n.Function)
 		out.Args = c.convNodeSlice(n.Args)
+		c.pop()
 		return out
 
 	case *ast.ExprInstanceOf:
@@ -860,10 +1051,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.InstanceOfExpr)(nil)
 		}
 		out := &ir.InstanceOfExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.InstanceOfTkn = n.InstanceOfTkn
 		out.Expr = c.convNode(n.Expr)
 		out.Class = c.convNode(n.Class)
+		c.pop()
 		return out
 
 	case *ast.ExprIsset:
@@ -871,12 +1065,15 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.IssetExpr)(nil)
 		}
 		out := &ir.IssetExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.IssetTkn = n.IssetTkn
 		out.OpenParenthesisTkn = n.OpenParenthesisTkn
 		out.SeparatorTkns = n.SeparatorTkns
 		out.CloseParenthesisTkn = n.CloseParenthesisTkn
 		out.Variables = c.convNodeSlice(n.Vars)
+		c.pop()
 		return out
 
 	case *ast.ExprList:
@@ -884,6 +1081,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ListExpr)(nil)
 		}
 		out := &ir.ListExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		out.ListTkn = n.ListTkn
@@ -900,6 +1099,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		}
 
 		out.ShortSyntax = !hasValue(n.ListTkn)
+		c.pop()
 		return out
 
 	case *ast.ExprMethodCall:
@@ -907,6 +1107,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.MethodCallExpr)(nil)
 		}
 		out := &ir.MethodCallExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ObjectOperatorTkn = n.ObjectOperatorTkn
 		out.OpenCurlyBracketTkn = n.OpenCurlyBracketTkn
@@ -915,6 +1117,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Variable = c.convNode(n.Var)
 		out.Method = c.convNode(n.Method)
 		out.Args = c.convNodeSlice(n.Args)
+		c.pop()
 		return out
 
 	case *ast.ExprNullsafeMethodCall:
@@ -922,6 +1125,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.NullsafeMethodCallExpr)(nil)
 		}
 		out := &ir.NullsafeMethodCallExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ObjectOperatorTkn = n.ObjectOperatorTkn
 		out.OpenCurlyBracketTkn = n.OpenCurlyBracketTkn
@@ -930,6 +1135,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Variable = c.convNode(n.Var)
 		out.Method = c.convNode(n.Method)
 		out.Args = c.convNodeSlice(n.Args)
+		c.pop()
 		return out
 
 	case *ast.ExprNew:
@@ -937,6 +1143,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.NewExpr)(nil)
 		}
 		out := &ir.NewExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 
 		out.Position = n.Position
 		out.NewTkn = n.NewTkn
@@ -951,6 +1159,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			out.Args = []ir.Node{}
 		}
 
+		c.pop()
 		return out
 
 	case *ast.ExprBrackets:
@@ -958,10 +1167,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ParenExpr)(nil)
 		}
 		out := &ir.ParenExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpenParenthesisTkn = n.OpenParenthesisTkn
 		out.CloseParenthesisTkn = n.CloseParenthesisTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprPostDec:
@@ -969,9 +1181,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.PostDecExpr)(nil)
 		}
 		out := &ir.PostDecExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.DecTkn = n.DecTkn
 		out.Variable = c.convNode(n.Var)
+		c.pop()
 		return out
 
 	case *ast.ExprPostInc:
@@ -979,9 +1194,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.PostIncExpr)(nil)
 		}
 		out := &ir.PostIncExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.IncTkn = n.IncTkn
 		out.Variable = c.convNode(n.Var)
+		c.pop()
 		return out
 
 	case *ast.ExprPreDec:
@@ -989,9 +1207,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.PreDecExpr)(nil)
 		}
 		out := &ir.PreDecExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.DecTkn = n.DecTkn
 		out.Variable = c.convNode(n.Var)
+		c.pop()
 		return out
 
 	case *ast.ExprPreInc:
@@ -999,9 +1220,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.PreIncExpr)(nil)
 		}
 		out := &ir.PreIncExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.IncTkn = n.IncTkn
 		out.Variable = c.convNode(n.Var)
+		c.pop()
 		return out
 
 	case *ast.ExprPrint:
@@ -1009,9 +1233,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.PrintExpr)(nil)
 		}
 		out := &ir.PrintExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.PrintTkn = n.PrintTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprPropertyFetch:
@@ -1019,12 +1246,15 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.PropertyFetchExpr)(nil)
 		}
 		out := &ir.PropertyFetchExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ObjectOperatorTkn = n.ObjectOperatorTkn
 		out.OpenCurlyBracketTkn = n.OpenCurlyBracketTkn
 		out.CloseCurlyBracketTkn = n.CloseCurlyBracketTkn
 		out.Variable = c.convNode(n.Var)
 		out.Property = c.convNode(n.Prop)
+		c.pop()
 		return out
 
 	case *ast.ExprNullsafePropertyFetch:
@@ -1032,12 +1262,15 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.NullsafePropertyFetchExpr)(nil)
 		}
 		out := &ir.NullsafePropertyFetchExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ObjectOperatorTkn = n.ObjectOperatorTkn
 		out.OpenCurlyBracketTkn = n.OpenCurlyBracketTkn
 		out.CloseCurlyBracketTkn = n.CloseCurlyBracketTkn
 		out.Variable = c.convNode(n.Var)
 		out.Property = c.convNode(n.Prop)
+		c.pop()
 		return out
 
 	case *ast.ExprRequire:
@@ -1054,10 +1287,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ShellExecExpr)(nil)
 		}
 		out := &ir.ShellExecExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpenBacktickTkn = n.OpenBacktickTkn
 		out.CloseBacktickTkn = n.CloseBacktickTkn
 		out.Parts = c.convNodeSlice(n.Parts)
+		c.pop()
 		return out
 
 	case *ast.ExprStaticCall:
@@ -1065,6 +1301,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.StaticCallExpr)(nil)
 		}
 		out := &ir.StaticCallExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.DoubleColonTkn = n.DoubleColonTkn
 		out.OpenCurlyBracketTkn = n.OpenCurlyBracketTkn
@@ -1076,6 +1314,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Class = c.convNode(n.Class)
 		out.Call = c.convNode(n.Call)
 		out.Args = c.convNodeSlice(n.Args)
+		c.pop()
 		return out
 
 	case *ast.ExprStaticPropertyFetch:
@@ -1083,10 +1322,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.StaticPropertyFetchExpr)(nil)
 		}
 		out := &ir.StaticPropertyFetchExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.DoubleColonTkn = n.DoubleColonTkn
 		out.Class = c.convNode(n.Class)
 		out.Property = c.convNode(n.Prop)
+		c.pop()
 		return out
 
 	case *ast.ExprTernary:
@@ -1094,12 +1336,15 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.TernaryExpr)(nil)
 		}
 		out := &ir.TernaryExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.QuestionTkn = n.QuestionTkn
 		out.ColonTkn = n.ColonTkn
 		out.Condition = c.convNode(n.Cond)
 		out.IfTrue = c.convNode(n.IfTrue)
 		out.IfFalse = c.convNode(n.IfFalse)
+		c.pop()
 		return out
 
 	case *ast.ExprUnaryMinus:
@@ -1107,9 +1352,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.UnaryMinusExpr)(nil)
 		}
 		out := &ir.UnaryMinusExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.MinusTkn = n.MinusTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprUnaryPlus:
@@ -1117,9 +1365,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.UnaryPlusExpr)(nil)
 		}
 		out := &ir.UnaryPlusExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.PlusTkn = n.PlusTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprYield:
@@ -1127,11 +1378,14 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.YieldExpr)(nil)
 		}
 		out := &ir.YieldExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.YieldTkn = n.YieldTkn
 		out.DoubleArrowTkn = n.DoubleArrowTkn
 		out.Key = c.convNode(n.Key)
 		out.Value = c.convNode(n.Val)
+		c.pop()
 		return out
 
 	case *ast.ExprYieldFrom:
@@ -1139,22 +1393,27 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.YieldFromExpr)(nil)
 		}
 		out := &ir.YieldFromExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.YieldFromTkn = n.YieldFromTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.NameFullyQualified:
 		return &ir.Name{
-			Position: n.Position,
-			Value:    fullyQualifiedToString(n),
+			ParentNode: c.back(),
+			Position:   n.Position,
+			Value:      fullyQualifiedToString(n),
 		}
 	case *ast.Name:
 		tok := namePartsToToken(n.Parts)
 		return &ir.Name{
-			Position: n.Position,
-			NameTkn:  tok,
-			Value:    string(tok.Value),
+			ParentNode: c.back(),
+			Position:   n.Position,
+			NameTkn:    tok,
+			Value:      string(tok.Value),
 		}
 	case *ast.NameRelative:
 		return c.convRelativeName(n)
@@ -1164,6 +1423,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.Argument)(nil)
 		}
 		out := &ir.Argument{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		if n.Name != nil {
 			out.Name = c.convNode(n.Name).(*ir.Identifier)
@@ -1174,6 +1435,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Expr = c.convNode(n.Expr)
 		out.Variadic = hasValue(n.VariadicTkn)
 		out.IsReference = hasValue(n.AmpersandTkn)
+		c.pop()
 		return out
 
 	case *ast.Identifier:
@@ -1181,9 +1443,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.Identifier)(nil)
 		}
 		out := &ir.Identifier{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.IdentifierTkn = n.IdentifierTkn
 		out.Value = string(n.Value)
+		c.pop()
 		return out
 
 	case *ast.Nullable:
@@ -1191,9 +1456,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.Nullable)(nil)
 		}
 		out := &ir.Nullable{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.QuestionTkn = n.QuestionTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.Parameter:
@@ -1201,6 +1469,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.Parameter)(nil)
 		}
 		out := &ir.Parameter{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		if n.AttrGroups != nil {
@@ -1228,6 +1498,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 
 		out.ByRef = hasValue(n.AmpersandTkn)
 		out.Variadic = hasValue(n.VariadicTkn)
+		c.pop()
 		return out
 
 	case *ast.Root:
@@ -1235,6 +1506,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.Root)(nil)
 		}
 		out := &ir.Root{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EndTkn = n.EndTkn
 		{
@@ -1244,6 +1517,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			}
 			out.Stmts = slice
 		}
+		c.pop()
 		return out
 
 	case *ast.ExprVariable:
@@ -1265,11 +1539,14 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		nameNodeIr := c.convNode(nameNode).(*ir.Identifier)
 
 		out := &ir.SimpleVar{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.DollarTkn = n.DollarTkn
 		out.Name = string(bytes.TrimPrefix(nameNode.Value, []byte("$")))
 		out.IdentifierTkn = nameNodeIr.IdentifierTkn
 
+		c.pop()
 		return out
 
 	case *ast.ScalarDnumber:
@@ -1277,9 +1554,14 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.Dnumber)(nil)
 		}
 		out := &ir.Dnumber{}
+		out.ParentNode = c.back()
+		c.push(out)
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.NumberTkn = n.NumberTkn
 		out.Value = string(n.Value)
+		c.pop()
 		return out
 
 	case *ast.ScalarEncapsed:
@@ -1287,10 +1569,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.Encapsed)(nil)
 		}
 		out := &ir.Encapsed{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpenQuoteTkn = n.OpenQuoteTkn
 		out.CloseQuoteTkn = n.CloseQuoteTkn
 		out.Parts = c.convNodeSlice(n.Parts)
+		c.pop()
 		return out
 
 	case *ast.ScalarEncapsedStringBrackets:
@@ -1335,9 +1620,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.EncapsedStringPart)(nil)
 		}
 		out := &ir.EncapsedStringPart{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EncapsedStrTkn = n.EncapsedStrTkn
 		out.Value = string(n.Value)
+		c.pop()
 		return out
 
 	case *ast.ScalarHeredoc:
@@ -1345,11 +1633,14 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.Heredoc)(nil)
 		}
 		out := &ir.Heredoc{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpenHeredocTkn = n.OpenHeredocTkn
 		out.CloseHeredocTkn = n.CloseHeredocTkn
 		out.Label = string(n.OpenHeredocTkn.Value)
 		out.Parts = c.convNodeSlice(n.Parts)
+		c.pop()
 		return out
 
 	case *ast.ScalarLnumber:
@@ -1357,9 +1648,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.Lnumber)(nil)
 		}
 		out := &ir.Lnumber{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.NumberTkn = n.NumberTkn
 		out.Value = string(n.Value)
+		c.pop()
 		return out
 
 	case *ast.ScalarMagicConstant:
@@ -1367,9 +1661,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.MagicConstant)(nil)
 		}
 		out := &ir.MagicConstant{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.MagicConstTkn = n.MagicConstTkn
 		out.Value = string(n.Value)
+		c.pop()
 		return out
 
 	case *ast.ScalarString:
@@ -1380,10 +1677,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.BreakStmt)(nil)
 		}
 		out := &ir.BreakStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.BreakTkn = n.BreakTkn
 		out.SemiColonTkn = n.SemiColonTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.StmtCase:
@@ -1391,11 +1691,14 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.CaseStmt)(nil)
 		}
 		out := &ir.CaseStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.CaseTkn = n.CaseTkn
 		out.CaseSeparatorTkn = n.CaseSeparatorTkn
 		out.Cond = c.convNode(n.Cond)
 		out.Stmts = c.convNodeSlice(n.Stmts)
+		c.pop()
 		return out
 
 	case *ast.StmtCatch:
@@ -1403,6 +1706,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.CatchStmt)(nil)
 		}
 		out := &ir.CatchStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		out.CatchTkn = n.CatchTkn
@@ -1417,6 +1722,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			out.Variable = c.convNode(n.Var).(*ir.SimpleVar)
 		}
 		out.Stmts = c.convNodeSlice(n.Stmts)
+		c.pop()
 		return out
 
 	case *ast.StmtClass:
@@ -1430,6 +1736,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ClassConstListStmt)(nil)
 		}
 		out := &ir.ClassConstListStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		if n.AttrGroups != nil {
@@ -1455,6 +1763,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Consts = c.convNodeSlice(n.Consts)
 
 		out.Doc = c.getPHPDoc(ir.GetFirstToken(out))
+		c.pop()
 		return out
 
 	case *ast.StmtClassMethod:
@@ -1462,6 +1771,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ClassMethodStmt)(nil)
 		}
 		out := &ir.ClassMethodStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		if n.AttrGroups != nil {
 			slice := make([]*ir.AttributeGroup, len(n.AttrGroups))
@@ -1493,6 +1804,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.ReturnsRef = hasValue(n.AmpersandTkn)
 
 		out.Doc = c.getPHPDoc(ir.GetFirstToken(out))
+		c.pop()
 		return out
 
 	case *ast.StmtConstList:
@@ -1500,11 +1812,14 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ConstListStmt)(nil)
 		}
 		out := &ir.ConstListStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ConstTkn = n.ConstTkn
 		out.SeparatorTkns = n.SeparatorTkns
 		out.SemiColonTkn = n.SemiColonTkn
 		out.Consts = c.convNodeSlice(n.Consts)
+		c.pop()
 		return out
 
 	case *ast.StmtConstant:
@@ -1512,10 +1827,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ConstantStmt)(nil)
 		}
 		out := &ir.ConstantStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.ConstantName = c.convNode(n.Name).(*ir.Identifier)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.StmtContinue:
@@ -1523,10 +1841,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ContinueStmt)(nil)
 		}
 		out := &ir.ContinueStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ContinueTkn = n.ContinueTkn
 		out.SemiColonTkn = n.SemiColonTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.StmtDeclare:
@@ -1534,6 +1855,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.DeclareStmt)(nil)
 		}
 		out := &ir.DeclareStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.DeclareTkn = n.DeclareTkn
 		out.OpenParenthesisTkn = n.OpenParenthesisTkn
@@ -1545,6 +1868,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Consts = c.convNodeSlice(n.Consts)
 		out.Stmt = c.convNode(n.Stmt)
 		out.Alt = hasValue(n.EndDeclareTkn)
+		c.pop()
 		return out
 
 	case *ast.StmtDefault:
@@ -1552,10 +1876,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.DefaultStmt)(nil)
 		}
 		out := &ir.DefaultStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.DefaultTkn = n.DefaultTkn
 		out.CaseSeparatorTkn = n.CaseSeparatorTkn
 		out.Stmts = c.convNodeSlice(n.Stmts)
+		c.pop()
 		return out
 
 	case *ast.StmtDo:
@@ -1563,6 +1890,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.DoStmt)(nil)
 		}
 		out := &ir.DoStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		out.DoTkn = n.DoTkn
@@ -1573,6 +1902,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 
 		out.Stmt = c.convNode(n.Stmt)
 		out.Cond = c.convNode(n.Cond)
+		c.pop()
 		return out
 
 	case *ast.StmtEcho:
@@ -1580,11 +1910,14 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.EchoStmt)(nil)
 		}
 		out := &ir.EchoStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EchoTkn = n.EchoTkn
 		out.SeparatorTkns = n.SeparatorTkns
 		out.SemiColonTkn = n.SemiColonTkn
 		out.Exprs = c.convNodeSlice(n.Exprs)
+		c.pop()
 		return out
 
 	case *ast.StmtElse:
@@ -1592,6 +1925,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ElseStmt)(nil)
 		}
 		out := &ir.ElseStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ElseTkn = n.ElseTkn
 		out.ColonTkn = n.ColonTkn
@@ -1611,6 +1946,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return ifStmt
 		}
 
+		c.pop()
 		return out
 
 	case *ast.StmtElseIf:
@@ -1618,6 +1954,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ElseIfStmt)(nil)
 		}
 		out := &ir.ElseIfStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.ElseIfTkn = n.ElseIfTkn
 		out.OpenParenthesisTkn = n.OpenParenthesisTkn
 		out.CloseParenthesisTkn = n.CloseParenthesisTkn
@@ -1631,6 +1969,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 
 		// directly converting ElseIf always means they are merged
 		out.Merged = true
+		c.pop()
 		return out
 
 	case *ast.StmtExpression:
@@ -1638,9 +1977,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ExpressionStmt)(nil)
 		}
 		out := &ir.ExpressionStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.SemiColonTkn = n.SemiColonTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.StmtFinally:
@@ -1648,11 +1990,14 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.FinallyStmt)(nil)
 		}
 		out := &ir.FinallyStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.FinallyTkn = n.FinallyTkn
 		out.OpenCurlyBracketTkn = n.OpenCurlyBracketTkn
 		out.CloseCurlyBracketTkn = n.CloseCurlyBracketTkn
 		out.Stmts = c.convNodeSlice(n.Stmts)
+		c.pop()
 		return out
 
 	case *ast.StmtFor:
@@ -1660,6 +2005,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ForStmt)(nil)
 		}
 		out := &ir.ForStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		out.ForTkn = n.ForTkn
@@ -1680,6 +2027,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Stmt = c.convNode(n.Stmt)
 
 		out.AltSyntax = hasValue(n.EndForTkn)
+		c.pop()
 		return out
 
 	case *ast.StmtForeach:
@@ -1687,6 +2035,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ForeachStmt)(nil)
 		}
 		out := &ir.ForeachStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		out.ForeachTkn = n.ForeachTkn
@@ -1715,6 +2065,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Stmt = c.convNode(n.Stmt)
 
 		out.AltSyntax = hasValue(n.EndForeachTkn)
+		c.pop()
 		return out
 
 	case *ast.StmtFunction:
@@ -1722,6 +2073,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.FunctionStmt)(nil)
 		}
 		out := &ir.FunctionStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		if n.AttrGroups != nil {
 			slice := make([]*ir.AttributeGroup, len(n.AttrGroups))
@@ -1747,6 +2100,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.ReturnsRef = hasValue(n.AmpersandTkn)
 
 		out.Doc = c.getPHPDoc(ir.GetFirstToken(out))
+		c.pop()
 		return out
 
 	case *ast.StmtGlobal:
@@ -1754,11 +2108,14 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.GlobalStmt)(nil)
 		}
 		out := &ir.GlobalStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.GlobalTkn = n.GlobalTkn
 		out.SeparatorTkns = n.SeparatorTkns
 		out.SemiColonTkn = n.SemiColonTkn
 		out.Vars = c.convNodeSlice(n.Vars)
+		c.pop()
 		return out
 
 	case *ast.StmtGoto:
@@ -1766,10 +2123,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.GotoStmt)(nil)
 		}
 		out := &ir.GotoStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.GotoTkn = n.GotoTkn
 		out.SemiColonTkn = n.SemiColonTkn
 		out.Label = c.convNode(n.Label).(*ir.Identifier)
+		c.pop()
 		return out
 
 	case *ast.StmtGroupUseList:
@@ -1777,6 +2137,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.GroupUseStmt)(nil)
 		}
 		out := &ir.GroupUseStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		out.UseTkn = n.UseTkn
@@ -1793,6 +2155,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		}
 		out.Prefix = c.convNode(n.Prefix).(*ir.Name)
 		out.UseList = c.convNodeSlice(n.Uses)
+		c.pop()
 		return out
 
 	case *ast.StmtHaltCompiler:
@@ -1800,11 +2163,14 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.HaltCompilerStmt)(nil)
 		}
 		out := &ir.HaltCompilerStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.HaltCompilerTkn = n.HaltCompilerTkn
 		out.OpenParenthesisTkn = n.OpenParenthesisTkn
 		out.CloseParenthesisTkn = n.CloseParenthesisTkn
 		out.SemiColonTkn = n.SemiColonTkn
+		c.pop()
 		return out
 
 	case *ast.StmtIf:
@@ -1812,6 +2178,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.IfStmt)(nil)
 		}
 		out := &ir.IfStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		out.IfTkn = n.IfTkn
@@ -1856,6 +2224,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		}
 
 		out.AltSyntax = hasValue(n.ColonTkn)
+		c.pop()
 		return out
 
 	case *ast.StmtInlineHtml:
@@ -1863,9 +2232,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.InlineHTMLStmt)(nil)
 		}
 		out := &ir.InlineHTMLStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.InlineHTMLTkn = n.InlineHtmlTkn
 		out.Value = string(n.Value)
+		c.pop()
 		return out
 
 	case *ast.StmtInterface:
@@ -1873,6 +2245,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.InterfaceStmt)(nil)
 		}
 		out := &ir.InterfaceStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		if n.AttrGroups != nil {
 			slice := make([]*ir.AttributeGroup, len(n.AttrGroups))
@@ -1897,6 +2271,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Stmts = c.convNodeSlice(n.Stmts)
 
 		out.Doc = c.getPHPDoc(ir.GetFirstToken(out))
+		c.pop()
 		return out
 
 	case *ast.StmtLabel:
@@ -1904,9 +2279,12 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.LabelStmt)(nil)
 		}
 		out := &ir.LabelStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ColonTkn = n.ColonTkn
 		out.LabelName = c.convNode(n.Name).(*ir.Identifier)
+		c.pop()
 		return out
 
 	case *ast.StmtNamespace:
@@ -1914,6 +2292,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.NamespaceStmt)(nil)
 		}
 		out := &ir.NamespaceStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		out.NsTkn = n.NsTkn
@@ -1925,6 +2305,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			c.namespace = out.NamespaceName.Value
 		}
 		out.Stmts = c.convNodeSlice(n.Stmts)
+		c.pop()
 		return out
 
 	case *ast.StmtNop:
@@ -1940,8 +2321,11 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		}
 
 		out := &ir.NopStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.SemiColonTkn = n.SemiColonTkn
+		c.pop()
 		return out
 
 	case *ast.StmtProperty:
@@ -1949,10 +2333,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.PropertyStmt)(nil)
 		}
 		out := &ir.PropertyStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var).(*ir.SimpleVar)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.StmtPropertyList:
@@ -1960,6 +2347,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.PropertyListStmt)(nil)
 		}
 		out := &ir.PropertyListStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.SeparatorTkns = n.SeparatorTkns
 		out.SemiColonTkn = n.SemiColonTkn
@@ -1980,10 +2369,11 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			out.AttrGroups = slice
 		}
 
-		out.Type = c.convNode(n.Type)
+		out.PropertyType = c.convNode(n.Type)
 		out.Properties = c.convNodeSlice(n.Props)
 
 		out.Doc = c.getPHPDoc(ir.GetFirstToken(out))
+		c.pop()
 		return out
 
 	case *ast.StmtReturn:
@@ -1991,10 +2381,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ReturnStmt)(nil)
 		}
 		out := &ir.ReturnStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ReturnTkn = n.ReturnTkn
 		out.SemiColonTkn = n.SemiColonTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.StmtStatic:
@@ -2002,10 +2395,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.StaticStmt)(nil)
 		}
 		out := &ir.StaticStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.StaticTkn = n.StaticTkn
 		out.SeparatorTkns = n.SeparatorTkns
 		out.Vars = c.convNodeSlice(n.Vars)
+		c.pop()
 		return out
 
 	case *ast.StmtStaticVar:
@@ -2013,10 +2409,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.StaticVarStmt)(nil)
 		}
 		out := &ir.StaticVarStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.EqualTkn = n.EqualTkn
 		out.Variable = c.convNode(n.Var).(*ir.SimpleVar)
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.StmtStmtList:
@@ -2024,10 +2423,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.StmtList)(nil)
 		}
 		out := &ir.StmtList{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.OpenCurlyBracketTkn = n.OpenCurlyBracketTkn
 		out.CloseCurlyBracketTkn = n.CloseCurlyBracketTkn
 		out.Stmts = c.convNodeSlice(n.Stmts)
+		c.pop()
 		return out
 
 	case *ast.StmtSwitch:
@@ -2035,6 +2437,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.SwitchStmt)(nil)
 		}
 		out := &ir.SwitchStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		out.SwitchTkn = n.SwitchTkn
@@ -2050,6 +2454,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Cond = c.convNode(n.Cond)
 		out.Cases = c.convNodeSlice(n.Cases)
 		out.AltSyntax = hasValue(n.ColonTkn)
+		c.pop()
 		return out
 
 	case *ast.StmtThrow:
@@ -2057,10 +2462,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ThrowStmt)(nil)
 		}
 		out := &ir.ThrowStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ThrowTkn = n.ThrowTkn
 		out.SemiColonTkn = n.SemiColonTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.ExprThrow:
@@ -2068,10 +2476,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.ThrowExpr)(nil)
 		}
 		out := &ir.ThrowExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.ThrowTkn = n.ThrowTkn
 		out.SemiColonTkn = n.SemiColonTkn
 		out.Expr = c.convNode(n.Expr)
+		c.pop()
 		return out
 
 	case *ast.StmtTrait:
@@ -2079,6 +2490,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.TraitStmt)(nil)
 		}
 		out := &ir.TraitStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		if n.AttrGroups != nil {
 			slice := make([]*ir.AttributeGroup, len(n.AttrGroups))
@@ -2095,6 +2508,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Stmts = c.convNodeSlice(n.Stmts)
 
 		out.Doc = c.getPHPDoc(ir.GetFirstToken(out))
+		c.pop()
 		return out
 
 	case *ast.StmtTraitUse:
@@ -2102,6 +2516,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.TraitUseStmt)(nil)
 		}
 		out := &ir.TraitUseStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		out.UseTkn = n.UseTkn
@@ -2116,6 +2532,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			Position:    c.convertSliceNodeToPosition(n.Adaptations),
 			Adaptations: c.convNodeSlice(n.Adaptations),
 		}
+		c.pop()
 		return out
 
 	case *ast.StmtTraitUseAlias:
@@ -2123,6 +2540,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.TraitUseAliasStmt)(nil)
 		}
 		out := &ir.TraitUseAliasStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.DoubleColonTkn = n.DoubleColonTkn
 		out.AsTkn = n.AsTkn
@@ -2132,6 +2551,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		if n.Alias != nil {
 			out.Alias = c.convNode(n.Alias).(*ir.Identifier)
 		}
+		c.pop()
 		return out
 
 	case *ast.StmtTraitUsePrecedence:
@@ -2139,6 +2559,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.TraitUsePrecedenceStmt)(nil)
 		}
 		out := &ir.TraitUsePrecedenceStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.DoubleColonTkn = n.DoubleColonTkn
 		out.InsteadofTkn = n.InsteadofTkn
@@ -2146,6 +2568,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.SemiColonTkn = n.SemiColonTkn
 		out.Ref = c.convNode(n.Method)
 		out.Insteadof = c.convNodeSlice(n.Insteadof)
+		c.pop()
 		return out
 
 	case *ast.StmtTry:
@@ -2153,6 +2576,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.TryStmt)(nil)
 		}
 		out := &ir.TryStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.TryTkn = n.TryTkn
 		out.OpenCurlyBracketTkn = n.OpenCurlyBracketTkn
@@ -2160,6 +2585,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Stmts = c.convNodeSlice(n.Stmts)
 		out.Catches = c.convNodeSlice(n.Catches)
 		out.Finally = c.convNode(n.Finally)
+		c.pop()
 		return out
 
 	case *ast.StmtUnset:
@@ -2167,6 +2593,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.UnsetStmt)(nil)
 		}
 		out := &ir.UnsetStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.UnsetTkn = n.UnsetTkn
 		out.OpenParenthesisTkn = n.OpenParenthesisTkn
@@ -2174,6 +2602,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.CloseParenthesisTkn = n.CloseParenthesisTkn
 		out.SemiColonTkn = n.SemiColonTkn
 		out.Vars = c.convNodeSlice(n.Vars)
+		c.pop()
 		return out
 
 	case *ast.StmtUse:
@@ -2181,6 +2610,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.UseStmt)(nil)
 		}
 		out := &ir.UseStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.NsSeparatorTkn = n.NsSeparatorTkn
 		out.AsTkn = n.AsTkn
@@ -2191,6 +2622,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		if n.Alias != nil {
 			out.Alias = c.convNode(n.Alias).(*ir.Identifier)
 		}
+		c.pop()
 		return out
 
 	case *ast.StmtUseList:
@@ -2198,6 +2630,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.UseListStmt)(nil)
 		}
 		out := &ir.UseListStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 		out.UseTkn = n.UseTkn
 		out.SeparatorTkns = n.SeparatorTkns
@@ -2207,6 +2641,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			out.UseType = useType.(*ir.Identifier)
 		}
 		out.Uses = c.convNodeSlice(n.Uses)
+		c.pop()
 		return out
 
 	case *ast.StmtWhile:
@@ -2214,6 +2649,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.WhileStmt)(nil)
 		}
 		out := &ir.WhileStmt{}
+		out.ParentNode = c.back()
+		c.push(out)
 		out.Position = n.Position
 
 		out.WhileTkn = n.WhileTkn
@@ -2226,6 +2663,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Cond = c.convNode(n.Cond)
 		out.Stmt = c.convNode(n.Stmt)
 		out.AltSyntax = hasValue(n.EndWhileTkn)
+		c.pop()
 		return out
 
 	case *ast.ExprMatch:
@@ -2233,6 +2671,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.MatchExpr)(nil)
 		}
 		out := &ir.MatchExpr{}
+		out.ParentNode = c.back()
+		c.push(out)
 
 		out.Position = n.Position
 		out.MatchTkn = n.MatchTkn
@@ -2251,6 +2691,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.SeparatorTkns = n.SeparatorTkns
 		out.CloseCurlyBracketTkn = n.CloseCurlyBracketTkn
 
+		c.pop()
 		return out
 
 	case *ast.MatchArm:
@@ -2258,6 +2699,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.MatchArm)(nil)
 		}
 		out := &ir.MatchArm{}
+		out.ParentNode = c.back()
+		c.push(out)
 
 		out.Position = n.Position
 		out.DefaultTkn = n.DefaultTkn
@@ -2273,6 +2716,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.DoubleArrowTkn = n.DoubleArrowTkn
 		out.ReturnExpr = c.convNode(n.ReturnExpr)
 		out.IsDefault = out.DefaultTkn != nil
+		c.pop()
 		return out
 
 	case *ast.Union:
@@ -2280,10 +2724,13 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.MatchArm)(nil)
 		}
 		out := &ir.Union{}
+		out.ParentNode = c.back()
+		c.push(out)
 
 		out.Position = n.Position
 		out.Types = c.convNodeSlice(n.Types)
 		out.SeparatorTkns = n.SeparatorTkns
+		c.pop()
 		return out
 
 	case *ast.Attribute:
@@ -2291,6 +2738,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.MatchArm)(nil)
 		}
 		out := &ir.Attribute{}
+		out.ParentNode = c.back()
+		c.push(out)
 
 		out.Position = n.Position
 		out.Name = c.convNode(n.Name)
@@ -2298,6 +2747,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 		out.Args = c.convNodeSlice(n.Args)
 		out.SeparatorTkns = n.SeparatorTkns
 		out.CloseParenthesisTkn = n.CloseParenthesisTkn
+		c.pop()
 		return out
 
 	case *ast.AttributeGroup:
@@ -2305,6 +2755,8 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 			return (*ir.AttributeGroup)(nil)
 		}
 		out := &ir.AttributeGroup{}
+		out.ParentNode = c.back()
+		c.push(out)
 
 		out.Position = n.Position
 		out.OpenAttributeTkn = n.OpenAttributeTkn
@@ -2318,6 +2770,7 @@ func (c *Converter) convNode(n ast.Vertex) ir.Node {
 
 		out.SeparatorTkns = n.SeparatorTkns
 		out.CloseAttributeTkn = n.CloseAttributeTkn
+		c.pop()
 		return out
 
 	case *ast.StmtEnum, *ast.EnumCase:
@@ -2381,8 +2834,9 @@ func (c *Converter) convRelativeName(n *ast.NameRelative) *ir.Name {
 		value = `\` + c.namespace + `\` + value
 	}
 	return &ir.Name{
-		Position: n.Position,
-		Value:    value,
+		ParentNode: c.back(),
+		Position:   n.Position,
+		Value:      value,
 	}
 }
 
@@ -2405,6 +2859,16 @@ func (c *Converter) convCastExpr(n, e ast.Vertex, castTkn *token.Token, typ stri
 }
 
 func (c *Converter) convClass(n *ast.StmtClass) ir.Node {
+	out := &ir.ClassStmt{
+		ClassTkn:             n.ClassTkn,
+		OpenCurlyBracketTkn:  n.OpenCurlyBracketTkn,
+		CloseCurlyBracketTkn: n.CloseCurlyBracketTkn,
+		Position:             n.Position,
+	}
+
+	out.ParentNode = c.back()
+	c.push(out)
+
 	var extends *ir.ClassExtendsStmt
 	extendsNode := c.convNode(n.Extends)
 	if extendsNode != nil {
@@ -2431,7 +2895,11 @@ func (c *Converter) convClass(n *ast.StmtClass) ir.Node {
 		}
 	}
 
+	out.Class = class
+
 	if n.Name == nil {
+		out.ParentNode = c.back()
+		c.pop()
 		// Anonymous class expression.
 		out := &ir.AnonClassExpr{
 			ClassTkn:             n.ClassTkn,
@@ -2443,22 +2911,19 @@ func (c *Converter) convClass(n *ast.StmtClass) ir.Node {
 			Position:             n.Position,
 			Class:                class,
 		}
+		c.push(out)
+
 		if n.Args != nil {
 			out.Args = c.convNodeSlice(n.Args)
 		}
 
 		out.Doc = c.getPHPDoc(ir.GetFirstToken(out))
+		c.pop()
 		return out
 	}
 
-	out := &ir.ClassStmt{
-		ClassTkn:             n.ClassTkn,
-		OpenCurlyBracketTkn:  n.OpenCurlyBracketTkn,
-		CloseCurlyBracketTkn: n.CloseCurlyBracketTkn,
-		Position:             n.Position,
-		Class:                class,
-		ClassName:            c.convNode(n.Name).(*ir.Identifier),
-	}
+	out.ClassName = c.convNode(n.Name).(*ir.Identifier)
+
 	if n.AttrGroups != nil {
 		slice := make([]*ir.AttributeGroup, len(n.AttrGroups))
 		for i := range n.AttrGroups {
@@ -2476,6 +2941,7 @@ func (c *Converter) convClass(n *ast.StmtClass) ir.Node {
 	}
 
 	out.Doc = c.getPHPDoc(ir.GetFirstToken(out))
+	c.pop()
 	return out
 }
 
diff --git a/src/ir/irutil/clone.go b/src/ir/irutil/clone.go
index adad31c..dc6c226 100644
--- a/src/ir/irutil/clone.go
+++ b/src/ir/irutil/clone.go
@@ -299,7 +299,7 @@ func NodeClone(x ir.Node) ir.Node {
 		clone := *x
 		clone.Types = NodeSliceClone(x.Types)
 		if x.Variable != nil {
-			clone.Variable = NodeClone(x.Variable).(*ir.SimpleVar)
+			clone.Variable = NodeClone(x.Variable)
 		}
 		clone.Stmts = NodeSliceClone(x.Stmts)
 		return &clone
@@ -999,8 +999,8 @@ func NodeClone(x ir.Node) ir.Node {
 			}
 			clone.Modifiers = sliceClone
 		}
-		if x.Type != nil {
-			clone.Type = NodeClone(x.Type)
+		if x.PropertyType != nil {
+			clone.PropertyType = NodeClone(x.PropertyType)
 		}
 		clone.Properties = NodeSliceClone(x.Properties)
 		return &clone
diff --git a/src/ir/irutil/equal.go b/src/ir/irutil/equal.go
index 1f4b4c8..368b542 100644
--- a/src/ir/irutil/equal.go
+++ b/src/ir/irutil/equal.go
@@ -1570,7 +1570,7 @@ func NodeEqual(x, y ir.Node) bool {
 				return false
 			}
 		}
-		if !NodeEqual(x.Type, y.Type) {
+		if !NodeEqual(x.PropertyType, y.PropertyType) {
 			return false
 		}
 		if !NodeSliceEqual(x.Properties, y.Properties) {
diff --git a/src/ir/irutil/find_walker.go b/src/ir/irutil/find_walker.go
index 7ef3262..9b35ed7 100644
--- a/src/ir/irutil/find_walker.go
+++ b/src/ir/irutil/find_walker.go
@@ -4,43 +4,33 @@ import (
 	"github.com/VKCOM/noverify/src/ir"
 )
 
-type findPredicate func(what ir.Node, cur ir.Node) bool
+type findPredicate func(node ir.Node) bool
 
 // findWalker structure implements a walker to find
 // a specific node in a subtree.
 type findWalker struct {
-	what  ir.Node
 	where ir.Node
-	found bool
+	res   ir.Node
 
-	withPredicate bool
-	predicate     findPredicate
+	predicate findPredicate
 }
 
 // newFindWalker returns a walker with a predicate.
 //
 // If the predicate returns true, the search stops.
-func newFindWalkerWithPredicate(what ir.Node, where ir.Node, pred findPredicate) *findWalker {
+func newFindWalkerWithPredicate(where ir.Node, pred findPredicate) *findWalker {
 	return &findWalker{
-		what:          what,
-		where:         where,
-		withPredicate: true,
-		predicate:     pred,
+		where:     where,
+		predicate: pred,
 	}
 }
 
 func (w *findWalker) EnterNode(n ir.Node) (res bool) {
-	if w.withPredicate {
-		if w.predicate(w.what, n) {
-			w.found = true
-			return false
-		}
-	}
-
-	if NodeEqual(n, w.what) {
-		w.found = true
+	if w.predicate(n) {
+		w.res = n
 		return false
 	}
+
 	return true
 }
 
diff --git a/src/ir/irutil/irtutil.go b/src/ir/irutil/irtutil.go
index 2c700f8..36fe2f7 100644
--- a/src/ir/irutil/irtutil.go
+++ b/src/ir/irutil/irtutil.go
@@ -99,17 +99,36 @@ func FmtNode(n ir.Node) string {
 	return irfmt.Node(n)
 }
 
-// FindWithPredicate searches for a node in the passed
+// FindChild searches for a child node in the passed
 // subtree using a predicate.
 //
 // If the predicate returns true, the search ends.
-func FindWithPredicate(what ir.Node, where ir.Node, pred findPredicate) bool {
-	if what == nil || where == nil {
-		return false
+func FindChild(where ir.Node, cb func(ir.Node) bool) ir.Node {
+	if where == nil {
+		return nil
 	}
-	w := newFindWalkerWithPredicate(what, where, pred)
+	w := newFindWalkerWithPredicate(where, cb)
 	where.Walk(w)
-	return w.found
+	return w.res
+}
+
+// FindParent searches for a parent node for the passed
+// node using a predicate.
+//
+// If the predicate returns true, the search ends.
+func FindParent(n ir.Node, cb func(ir.Node) bool) ir.Node {
+	if n == nil {
+		return nil
+	}
+
+	for n != nil {
+		if cb(n) {
+			return n
+		}
+		n = n.Parent()
+	}
+
+	return nil
 }
 
 // FindPHPDoc searches for phpdoc by traversing all subtree and all tokens.
diff --git a/src/ir/node_types.go b/src/ir/node_types.go
index 690929a..61e7632 100644
--- a/src/ir/node_types.go
+++ b/src/ir/node_types.go
@@ -11,127 +11,143 @@ import (
 
 // Assign is a `$Variable = $Expression` expression.
 type Assign struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignBitwiseAnd is a `$Variable &= $Expression` expression.
 type AssignBitwiseAnd struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignBitwiseOr is a `$Variable |= $Expression` expression.
 type AssignBitwiseOr struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignBitwiseXor is a `$Variable ^= $Expression` expression.
 type AssignBitwiseXor struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignCoalesce is a `$Variable ??= $Expression` expression.
 type AssignCoalesce struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignConcat is a `$Variable .= $Expression` expression.
 type AssignConcat struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignDiv is a `$Variable /= $Expression` expression.
 type AssignDiv struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignMinus is a `$Variable -= $Expression` expression.
 type AssignMinus struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignMod is a `$Variable %= $Expression` expression.
 type AssignMod struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignMul is a `$Variable *= $Expression` expression.
 type AssignMul struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignPlus is a `$Variable += $Expression` expression.
 type AssignPlus struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignPow is a `$Variable **= $Expression` expression.
 type AssignPow struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignReference is a `$Variable &= $Expression` expression.
 type AssignReference struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignShiftLeft is a `$Variable <<= $Expression` expression.
 type AssignShiftLeft struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AssignShiftRight is a `$Variable >>= $Expression` expression.
 type AssignShiftRight struct {
-	Position *position.Position
-	Variable Node
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // AnonClassExpr is an anonymous class expression.
 // $Args may contain constructor call arguments `new class ($Args...) {}`.
 type AnonClassExpr struct {
+	ParentNode           Node
 	Position             *position.Position
 	ClassTkn             *token.Token
 	OpenParenthesisTkn   *token.Token
@@ -145,238 +161,268 @@ type AnonClassExpr struct {
 
 // BitwiseAndExpr is a `$Left & $Right` expression.
 type BitwiseAndExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // BitwiseOrExpr is a `$Left | $Right` expression.
 type BitwiseOrExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // BitwiseXorExpr is a `$Left ^ $Right` expression.
 type BitwiseXorExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // BooleanAndExpr is a `$Left && $Right` expression.
 type BooleanAndExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // BooleanOrExpr is a `$Left || $Right` expression.
 type BooleanOrExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // CoalesceExpr is a `$Left ?? $Right` expression.
 type CoalesceExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // ConcatExpr is a `$Left . $Right` expression.
 type ConcatExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // DivExpr is a `$Left / $Right` expression.
 type DivExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // EqualExpr is a `$Left == $Right` expression.
 type EqualExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // GreaterExpr is a `$Left > $Right` expression.
 type GreaterExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // GreaterOrEqualExpr is a `$Left >= $Right` expression.
 type GreaterOrEqualExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // IdenticalExpr is a `$Left === $Right` expression.
 type IdenticalExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // LogicalAndExpr is a `$Left and $Right` expression.
 type LogicalAndExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // LogicalOrExpr is a `$Left or $Right` expression.
 type LogicalOrExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // LogicalXorExpr is a `$Left xor $Right` expression.
 type LogicalXorExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // MinusExpr is a `$Left - $Right` expression.
 type MinusExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // ModExpr is a `$Left % $Right` expression.
 type ModExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // MulExpr is a `$Left * $Right` expression.
 type MulExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // NotEqualExpr is a `$Left != $Right` expression.
 type NotEqualExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // NotIdenticalExpr is a `$Left !== $Right` expression.
 type NotIdenticalExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // PlusExpr is a `$Left + $Right` expression.
 type PlusExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // PowExpr is a `$Left ** $Right` expression.
 type PowExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // ShiftLeftExpr is a `$Left << $Right` expression.
 type ShiftLeftExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // ShiftRightExpr is a `$Left >> $Right` expression.
 type ShiftRightExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // SmallerExpr is a `$Left < $Right` expression.
 type SmallerExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // SmallerOrEqualExpr is a `$Left <= $Right` expression.
 type SmallerOrEqualExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // SpaceshipExpr is a `$Left <=> $Right` expression.
 type SpaceshipExpr struct {
-	Position *position.Position
-	Left     Node
-	OpTkn    *token.Token
-	Right    Node
+	ParentNode Node
+	Position   *position.Position
+	Left       Node
+	OpTkn      *token.Token
+	Right      Node
 }
 
 // TypeCastExpr is a `($Type)$Expr` expression.
 type TypeCastExpr struct {
-	Position *position.Position
-	CastTkn  *token.Token
-	Type     string // "array" "bool" "int" "float" "object" "string"
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	CastTkn    *token.Token
+	Type       string // "array" "bool" "int" "float" "object" "string"
+	Expr       Node
 }
 
 // UnsetCastExpr is a `(unset)$Expr` expression.
 type UnsetCastExpr struct {
-	Position *position.Position
-	CastTkn  *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	CastTkn    *token.Token
+	Expr       Node
 }
 
 // ArrayExpr is a `array($Items...)` expression.
 // If $ShortSyntax is true, it's `[$Items...]`.
 type ArrayExpr struct {
+	ParentNode      Node
 	Position        *position.Position
 	ArrayTkn        *token.Token
 	OpenBracketTkn  *token.Token
@@ -389,6 +435,7 @@ type ArrayExpr struct {
 // ArrayDimFetchExpr is a `$Variable[$Dim]` expression.
 // If $CurlyBrace is true, it's `$Variable{$Dim}`
 type ArrayDimFetchExpr struct {
+	ParentNode      Node
 	Position        *position.Position
 	Variable        Node
 	OpenBracketTkn  *token.Token
@@ -402,6 +449,7 @@ type ArrayDimFetchExpr struct {
 //
 // TODO: make unpack a separate node?
 type ArrayItemExpr struct {
+	ParentNode     Node
 	Position       *position.Position
 	EllipsisTkn    *token.Token
 	Key            Node
@@ -416,6 +464,7 @@ type ArrayItemExpr struct {
 // If $Static is true, it's `static fn($Params...): $ReturnType => $Expr`.
 // $ReturnType is optional, without it we have `fn($Params...) => $Expr` syntax.
 type ArrowFunctionExpr struct {
+	ParentNode          Node
 	Position            *position.Position
 	AttrGroups          []*AttributeGroup
 	StaticTkn           *token.Token
@@ -437,13 +486,15 @@ type ArrowFunctionExpr struct {
 
 // BitwiseNotExpr is a `~$Expr` expression.
 type BitwiseNotExpr struct {
-	TildaTkn *token.Token
-	Position *position.Position
-	Expr     Node
+	ParentNode Node
+	TildaTkn   *token.Token
+	Position   *position.Position
+	Expr       Node
 }
 
 // BooleanNotExpr is a `!$Expr` expression.
 type BooleanNotExpr struct {
+	ParentNode     Node
 	ExclamationTkn *token.Token
 	Position       *position.Position
 	Expr           Node
@@ -451,6 +502,7 @@ type BooleanNotExpr struct {
 
 // ClassConstFetchExpr is a `$Class::$ConstantName` expression.
 type ClassConstFetchExpr struct {
+	ParentNode     Node
 	Position       *position.Position
 	Class          Node
 	DoubleColonTkn *token.Token
@@ -459,9 +511,10 @@ type ClassConstFetchExpr struct {
 
 // CloneExpr is a `clone $Expr` expression.
 type CloneExpr struct {
-	Position *position.Position
-	CloneTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	CloneTkn   *token.Token
+	Expr       Node
 }
 
 // ClosureExpr is a `#[$AttrGroups] function($Params...) use ($ClosureUse) : $ReturnType { $Stmts... }` expression.
@@ -470,6 +523,7 @@ type CloneExpr struct {
 // $ReturnType is optional, without it we have `function($Params...) use ($ClosureUse) { $Stmts... }` syntax.
 // $ClosureUse is optional, without it we have `function($Params...) : $ReturnType { $Stmts... }` syntax.
 type ClosureExpr struct {
+	ParentNode           Node
 	Position             *position.Position
 	AttrGroups           []*AttributeGroup
 	StaticTkn            *token.Token
@@ -494,6 +548,7 @@ type ClosureExpr struct {
 // ClosureUsesExpr is a `use ($Uses...)` expression.
 // TODO: it's not a expression really.
 type ClosureUsesExpr struct {
+	ParentNode             Node
 	Position               *position.Position
 	UseTkn                 *token.Token
 	UseOpenParenthesisTkn  *token.Token
@@ -504,12 +559,14 @@ type ClosureUsesExpr struct {
 
 // ConstFetchExpr is a `$Constant` expression.
 type ConstFetchExpr struct {
-	Position *position.Position
-	Constant *Name
+	ParentNode Node
+	Position   *position.Position
+	Constant   *Name
 }
 
 // EmptyExpr is a `empty($Expr)` expression.
 type EmptyExpr struct {
+	ParentNode          Node
 	Position            *position.Position
 	EmptyTkn            *token.Token
 	OpenParenthesisTkn  *token.Token
@@ -519,13 +576,15 @@ type EmptyExpr struct {
 
 // ErrorSuppressExpr is a `@$Expr` expression.
 type ErrorSuppressExpr struct {
-	Position *position.Position
-	AtTkn    *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	AtTkn      *token.Token
+	Expr       Node
 }
 
 // EvalExpr is a `eval($Expr)` expression.
 type EvalExpr struct {
+	ParentNode          Node
 	Position            *position.Position
 	EvalTkn             *token.Token
 	OpenParenthesisTkn  *token.Token
@@ -536,6 +595,7 @@ type EvalExpr struct {
 // ExitExpr is a `exit($Expr)` expression.
 // If $Die is true, it's `die($Expr)`.
 type ExitExpr struct {
+	ParentNode          Node
 	Position            *position.Position
 	ExitTkn             *token.Token
 	OpenParenthesisTkn  *token.Token
@@ -546,6 +606,7 @@ type ExitExpr struct {
 
 // FunctionCallExpr is a `$Function($Args...)` expression.
 type FunctionCallExpr struct {
+	ParentNode          Node
 	Position            *position.Position
 	Function            Node
 	OpenParenthesisTkn  *token.Token
@@ -557,14 +618,16 @@ type FunctionCallExpr struct {
 // ImportExpr is a `$Func $Expr` expression.
 // It could be `include $Expr`, `require $Expr` and so on.
 type ImportExpr struct {
-	Position  *position.Position
-	ImportTkn *token.Token
-	Func      string // "include" "include_once" "require" "require_once"
-	Expr      Node
+	ParentNode Node
+	Position   *position.Position
+	ImportTkn  *token.Token
+	Func       string // "include" "include_once" "require" "require_once"
+	Expr       Node
 }
 
 // InstanceOfExpr is a `$Expr instanceof $Class` expression.
 type InstanceOfExpr struct {
+	ParentNode    Node
 	Position      *position.Position
 	Expr          Node
 	InstanceOfTkn *token.Token
@@ -573,6 +636,7 @@ type InstanceOfExpr struct {
 
 // IssetExpr is a `isset($Variables...)` expression.
 type IssetExpr struct {
+	ParentNode          Node
 	Position            *position.Position
 	IssetTkn            *token.Token
 	OpenParenthesisTkn  *token.Token
@@ -586,6 +650,7 @@ type IssetExpr struct {
 // also in foreach value expressions.
 // If $ShortSyntax is true, it's `[$Items]`.
 type ListExpr struct {
+	ParentNode      Node
 	Position        *position.Position
 	ListTkn         *token.Token
 	OpenBracketTkn  *token.Token
@@ -597,6 +662,7 @@ type ListExpr struct {
 
 // MethodCallExpr is a `$Variable->$Method($Args...)` expression.
 type MethodCallExpr struct {
+	ParentNode           Node
 	Position             *position.Position
 	Variable             Node
 	ObjectOperatorTkn    *token.Token
@@ -612,6 +678,7 @@ type MethodCallExpr struct {
 // NewExpr is a `new $Class($Args...)` expression.
 // If $Args is nil, it's `new $Class`.
 type NewExpr struct {
+	ParentNode          Node
 	Position            *position.Position
 	NewTkn              *token.Token
 	Class               Node
@@ -623,6 +690,7 @@ type NewExpr struct {
 
 // ParenExpr is a `($Expr)` expression.
 type ParenExpr struct {
+	ParentNode          Node
 	Position            *position.Position
 	OpenParenthesisTkn  *token.Token
 	Expr                Node
@@ -631,41 +699,47 @@ type ParenExpr struct {
 
 // PostDecExpr is a `$Variable--` expression.
 type PostDecExpr struct {
-	Position *position.Position
-	Variable Node
-	DecTkn   *token.Token
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	DecTkn     *token.Token
 }
 
 // PostIncExpr is a `$Variable++` expression.
 type PostIncExpr struct {
-	Position *position.Position
-	Variable Node
-	IncTkn   *token.Token
+	ParentNode Node
+	Position   *position.Position
+	Variable   Node
+	IncTkn     *token.Token
 }
 
 // PreDecExpr is a `--$Variable` expression.
 type PreDecExpr struct {
-	Position *position.Position
-	DecTkn   *token.Token
-	Variable Node
+	ParentNode Node
+	Position   *position.Position
+	DecTkn     *token.Token
+	Variable   Node
 }
 
 // PreIncExpr is a `++$Variable` expression.
 type PreIncExpr struct {
-	Position *position.Position
-	IncTkn   *token.Token
-	Variable Node
+	ParentNode Node
+	Position   *position.Position
+	IncTkn     *token.Token
+	Variable   Node
 }
 
 // PrintExpr is a `print $Expr` expression.
 type PrintExpr struct {
-	Position *position.Position
-	PrintTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	PrintTkn   *token.Token
+	Expr       Node
 }
 
 // PropertyFetchExpr is a `$Variable->$Property` expression.
 type PropertyFetchExpr struct {
+	ParentNode           Node
 	Position             *position.Position
 	Variable             Node
 	ObjectOperatorTkn    *token.Token
@@ -676,6 +750,7 @@ type PropertyFetchExpr struct {
 
 // ReferenceExpr is a `&$Variable` expression.
 type ReferenceExpr struct {
+	ParentNode   Node
 	AmpersandTkn *token.Token
 	Position     *position.Position
 	Variable     Node
@@ -683,6 +758,7 @@ type ReferenceExpr struct {
 
 // ShellExecExpr is a ``-quoted string.
 type ShellExecExpr struct {
+	ParentNode       Node
 	Position         *position.Position
 	OpenBacktickTkn  *token.Token
 	Parts            []Node
@@ -691,6 +767,7 @@ type ShellExecExpr struct {
 
 // StaticCallExpr is a `$Class::$Call($Args...)` expression.
 type StaticCallExpr struct {
+	ParentNode           Node
 	Position             *position.Position
 	Class                Node
 	DoubleColonTkn       *token.Token
@@ -705,6 +782,7 @@ type StaticCallExpr struct {
 
 // StaticPropertyFetchExpr is a `$Class::$Property` expression.
 type StaticPropertyFetchExpr struct {
+	ParentNode     Node
 	Position       *position.Position
 	Class          Node
 	DoubleColonTkn *token.Token
@@ -714,6 +792,7 @@ type StaticPropertyFetchExpr struct {
 // TernaryExpr is a `$Condition ? $IfTrue : $IfFalse` expression.
 // If $IfTrue is nil, it's `$Condition ?: $IfFalse`.
 type TernaryExpr struct {
+	ParentNode  Node
 	Position    *position.Position
 	Condition   Node
 	QuestionTkn *token.Token
@@ -724,21 +803,24 @@ type TernaryExpr struct {
 
 // UnaryMinusExpr is a `-$Expr` expression.
 type UnaryMinusExpr struct {
-	Position *position.Position
-	MinusTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	MinusTkn   *token.Token
+	Expr       Node
 }
 
 // UnaryPlusExpr is a `+$Expr` expression.
 type UnaryPlusExpr struct {
-	Position *position.Position
-	PlusTkn  *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	PlusTkn    *token.Token
+	Expr       Node
 }
 
 // YieldExpr is a `yield $Key => $Value` expression.
 // If $Key is nil, it's `yield $Value`.
 type YieldExpr struct {
+	ParentNode     Node
 	Position       *position.Position
 	YieldTkn       *token.Token
 	Key            Node
@@ -748,6 +830,7 @@ type YieldExpr struct {
 
 // YieldFromExpr is a `yield from $Expr` expression.
 type YieldFromExpr struct {
+	ParentNode   Node
 	Position     *position.Position
 	YieldFromTkn *token.Token
 	Expr         Node
@@ -756,9 +839,10 @@ type YieldFromExpr struct {
 // Name is either a FQN, local name or a name that may need a further resolving.
 // Use Name methods to interpret the $Value correctly.
 type Name struct {
-	Position *position.Position
-	NameTkn  *token.Token
-	Value    string
+	ParentNode Node
+	Position   *position.Position
+	NameTkn    *token.Token
+	Value      string
 }
 
 // Argument is a wrapper node for func/method arguments.
@@ -768,6 +852,7 @@ type Name struct {
 // If $Variadic is true, it's `...$Expr`.
 // If $IsReference is true, it's `&$Expr`.
 type Argument struct {
+	ParentNode   Node
 	Position     *position.Position
 	Name         *Identifier
 	ColonTkn     *token.Token
@@ -781,6 +866,7 @@ type Argument struct {
 // Identifier is like a name, but it's always resolved to itself.
 // Identifier always consists of a single part.
 type Identifier struct {
+	ParentNode    Node
 	Position      *position.Position
 	IdentifierTkn *token.Token
 	Value         string
@@ -788,6 +874,7 @@ type Identifier struct {
 
 // Nullable is a `?$Expr` expression.
 type Nullable struct {
+	ParentNode  Node
 	Position    *position.Position
 	QuestionTkn *token.Token
 	Expr        Node
@@ -803,6 +890,7 @@ type Nullable struct {
 // If $ByRef is true, it's `&$Variable`.
 // If $Variadic is true, it's `...$Variable`.
 type Parameter struct {
+	ParentNode   Node
 	Position     *position.Position
 	AttrGroups   []*AttributeGroup
 	Modifiers    []*Identifier
@@ -818,13 +906,15 @@ type Parameter struct {
 
 // Root is a node that wraps all file statements.
 type Root struct {
-	Position *position.Position
-	Stmts    []Node
-	EndTkn   *token.Token
+	ParentNode Node
+	Position   *position.Position
+	Stmts      []Node
+	EndTkn     *token.Token
 }
 
 // SimpleVar is a normal PHP variable like `$foo` or `$bar`.
 type SimpleVar struct {
+	ParentNode    Node
 	Position      *position.Position
 	DollarTkn     *token.Token
 	IdentifierTkn *token.Token
@@ -833,6 +923,7 @@ type SimpleVar struct {
 
 // Var is variable variable expression like `$$foo` or `${"foo"}`.
 type Var struct {
+	ParentNode           Node
 	Position             *position.Position
 	DollarTkn            *token.Token
 	OpenCurlyBracketTkn  *token.Token
@@ -842,13 +933,15 @@ type Var struct {
 
 // Dnumber is a floating point literal.
 type Dnumber struct {
-	Position  *position.Position
-	NumberTkn *token.Token
-	Value     string
+	ParentNode Node
+	Position   *position.Position
+	NumberTkn  *token.Token
+	Value      string
 }
 
 // Encapsed is a string literal with interpolated parts.
 type Encapsed struct {
+	ParentNode    Node
 	Position      *position.Position
 	OpenQuoteTkn  *token.Token
 	Parts         []Node
@@ -857,6 +950,7 @@ type Encapsed struct {
 
 // EncapsedStringPart is a part of the Encapsed literal.
 type EncapsedStringPart struct {
+	ParentNode     Node
 	Position       *position.Position
 	EncapsedStrTkn *token.Token
 	Value          string
@@ -865,6 +959,7 @@ type EncapsedStringPart struct {
 // Heredoc is special PHP literal.
 // Note that it may be a nowdoc, depending on the label.
 type Heredoc struct {
+	ParentNode      Node
 	Position        *position.Position
 	Label           string
 	OpenHeredocTkn  *token.Token
@@ -874,13 +969,15 @@ type Heredoc struct {
 
 // Lnumber is an integer literal.
 type Lnumber struct {
-	Position  *position.Position
-	NumberTkn *token.Token
-	Value     string
+	ParentNode Node
+	Position   *position.Position
+	NumberTkn  *token.Token
+	Value      string
 }
 
 // MagicConstant is a special PHP constant like __FILE__ or __CLASS__.
 type MagicConstant struct {
+	ParentNode    Node
 	Position      *position.Position
 	MagicConstTkn *token.Token
 	Value         string
@@ -893,6 +990,7 @@ type MagicConstant struct {
 //
 // $DoubleQuotes tell whether originally this string literal was ""-quoted.
 type String struct {
+	ParentNode   Node
 	Position     *position.Position
 	MinusTkn     *token.Token
 	StringTkn    *token.Token
@@ -904,12 +1002,14 @@ type String struct {
 // The $Value contains uninterpreted (raw) string bytes.
 // $Error contains the reason why this string is "bad".
 type BadString struct {
+	ParentNode Node
 	String
 	Error string
 }
 
 // BreakStmt is a `break $Expr` statement.
 type BreakStmt struct {
+	ParentNode   Node
 	Position     *position.Position
 	BreakTkn     *token.Token
 	Expr         Node
@@ -918,6 +1018,7 @@ type BreakStmt struct {
 
 // CaseStmt is a `case $Cond: $Stmts...` statement.
 type CaseStmt struct {
+	ParentNode       Node
 	Position         *position.Position
 	CaseTkn          *token.Token
 	Cond             Node
@@ -928,6 +1029,7 @@ type CaseStmt struct {
 // CatchStmt is a `catch ($Types... $Variable) { $Stmts... }` statement.
 // Note that $Types are |-separated, like in `T1 | T2`.
 type CatchStmt struct {
+	ParentNode           Node
 	Position             *position.Position
 	CatchTkn             *token.Token
 	OpenParenthesisTkn   *token.Token
@@ -943,6 +1045,7 @@ type CatchStmt struct {
 // ClassStmt is a named class declaration.
 // $Modifiers consist of identifiers like `final` and `abstract`.
 type ClassStmt struct {
+	ParentNode           Node
 	Position             *position.Position
 	AttrGroups           []*AttributeGroup
 	Modifiers            []*Identifier
@@ -957,6 +1060,7 @@ type ClassStmt struct {
 // $Modifiers may specify the constant access level.
 // Every element in $Consts is a *ConstantStmt.
 type ClassConstListStmt struct {
+	ParentNode    Node
 	Position      *position.Position
 	AttrGroups    []*AttributeGroup
 	Modifiers     []*Identifier
@@ -970,6 +1074,7 @@ type ClassConstListStmt struct {
 
 // ClassExtendsStmt is a `extends $ClassName` statement.
 type ClassExtendsStmt struct {
+	ParentNode Node
 	Position   *position.Position
 	ExtendsTkn *token.Token
 	ClassName  *Name
@@ -978,6 +1083,7 @@ type ClassExtendsStmt struct {
 // ClassImplementsStmt is a `implements $InterfaceNames...` statement.
 // TODO: shouldn't every InterfaceName be a *Name?
 type ClassImplementsStmt struct {
+	ParentNode              Node
 	Position                *position.Position
 	ImplementsTkn           *token.Token
 	ImplementsSeparatorTkns []*token.Token
@@ -986,6 +1092,7 @@ type ClassImplementsStmt struct {
 
 // ClassMethodStmt is a class method declaration.
 type ClassMethodStmt struct {
+	ParentNode          Node
 	Position            *position.Position
 	AttrGroups          []*AttributeGroup
 	Modifiers           []*Identifier
@@ -1007,6 +1114,7 @@ type ClassMethodStmt struct {
 // ConstListStmt is a `const $Consts` statement.
 // Every element in $Consts is a *ConstantStmt.
 type ConstListStmt struct {
+	ParentNode    Node
 	Position      *position.Position
 	ConstTkn      *token.Token
 	Consts        []Node
@@ -1017,6 +1125,7 @@ type ConstListStmt struct {
 // ConstantStmt is a `$ConstantName = $Expr` statement.
 // It's a part of the *ConstListStmt, *ClassConstListStmt and *DeclareStmt.
 type ConstantStmt struct {
+	ParentNode   Node
 	Position     *position.Position
 	ConstantName *Identifier
 	EqualTkn     *token.Token
@@ -1025,6 +1134,7 @@ type ConstantStmt struct {
 
 // ContinueStmt is a `continue $Expr` statement.
 type ContinueStmt struct {
+	ParentNode   Node
 	Position     *position.Position
 	ContinueTkn  *token.Token
 	Expr         Node
@@ -1037,6 +1147,7 @@ type ContinueStmt struct {
 // If $Alt is true, the block will begin with `:` and end with `enddeclare`.
 // Every element in $Consts is a *ConstantStmt.
 type DeclareStmt struct {
+	ParentNode          Node
 	Position            *position.Position
 	DeclareTkn          *token.Token
 	OpenParenthesisTkn  *token.Token
@@ -1052,6 +1163,7 @@ type DeclareStmt struct {
 
 // DefaultStmt is a `default: $Stmts...` statement.
 type DefaultStmt struct {
+	ParentNode       Node
 	Position         *position.Position
 	DefaultTkn       *token.Token
 	CaseSeparatorTkn *token.Token
@@ -1060,6 +1172,7 @@ type DefaultStmt struct {
 
 // DoStmt is a `do $Stmt while ($Cond)` statement.
 type DoStmt struct {
+	ParentNode          Node
 	Position            *position.Position
 	DoTkn               *token.Token
 	Stmt                Node
@@ -1072,6 +1185,7 @@ type DoStmt struct {
 
 // EchoStmt is a `echo $Exprs...` statement.
 type EchoStmt struct {
+	ParentNode    Node
 	Position      *position.Position
 	EchoTkn       *token.Token
 	Exprs         []Node
@@ -1082,17 +1196,19 @@ type EchoStmt struct {
 // ElseStmt is a `else $Stmt` statement.
 // If $AltSyntax is true, the block will begin with `:`.
 type ElseStmt struct {
-	Position  *position.Position
-	ElseTkn   *token.Token
-	ColonTkn  *token.Token
-	Stmt      Node
-	AltSyntax bool
+	ParentNode Node
+	Position   *position.Position
+	ElseTkn    *token.Token
+	ColonTkn   *token.Token
+	Stmt       Node
+	AltSyntax  bool
 }
 
 // ElseIfStmt is a `elseif ($Cond) $Stmt` statement.
 // If $AltSyntax is true, the block will begin with `:` and end with `endif`.
 // $Merged tells whether this elseif is a merged `else if` statement.
 type ElseIfStmt struct {
+	ParentNode          Node
 	Position            *position.Position
 	ElseIfTkn           *token.Token
 	ElseTkn             *token.Token
@@ -1110,6 +1226,7 @@ type ElseIfStmt struct {
 // When expression is used in a place where statement is expected, it
 // becomes an ExpressionStmt.
 type ExpressionStmt struct {
+	ParentNode   Node
 	Position     *position.Position
 	Expr         Node
 	SemiColonTkn *token.Token
@@ -1117,6 +1234,7 @@ type ExpressionStmt struct {
 
 // FinallyStmt is a `finally { $Stmts... }` statement.
 type FinallyStmt struct {
+	ParentNode           Node
 	Position             *position.Position
 	FinallyTkn           *token.Token
 	OpenCurlyBracketTkn  *token.Token
@@ -1127,6 +1245,7 @@ type FinallyStmt struct {
 // ForStmt is a `for ($Init; $Cond; $Loop) $Stmt` statement.
 // If $AltSyntax is true, the block will begin with `:` and end with `endfor`.
 type ForStmt struct {
+	ParentNode          Node
 	Position            *position.Position
 	ForTkn              *token.Token
 	OpenParenthesisTkn  *token.Token
@@ -1149,6 +1268,7 @@ type ForStmt struct {
 // ForeachStmt is a `foreach ($Expr as $Key => $Variable) $Stmt` statement.
 // If $AltSyntax is true, the block will begin with `:` and end with `endforeach`.
 type ForeachStmt struct {
+	ParentNode          Node
 	Position            *position.Position
 	ForeachTkn          *token.Token
 	OpenParenthesisTkn  *token.Token
@@ -1168,6 +1288,7 @@ type ForeachStmt struct {
 
 // FunctionStmt is a named function declaration.
 type FunctionStmt struct {
+	ParentNode           Node
 	Position             *position.Position
 	AttrGroups           []*AttributeGroup
 	FunctionTkn          *token.Token
@@ -1189,6 +1310,7 @@ type FunctionStmt struct {
 
 // GlobalStmt is a `global $Vars` statement.
 type GlobalStmt struct {
+	ParentNode    Node
 	Position      *position.Position
 	GlobalTkn     *token.Token
 	Vars          []Node
@@ -1198,6 +1320,7 @@ type GlobalStmt struct {
 
 // GotoStmt is a `goto $Label` statement.
 type GotoStmt struct {
+	ParentNode   Node
 	Position     *position.Position
 	GotoTkn      *token.Token
 	Label        *Identifier
@@ -1207,6 +1330,7 @@ type GotoStmt struct {
 // GroupUseStmt is a `use $UseType $Prefix\{ $UseList }` statement.
 // $UseType is a "function" or "const".
 type GroupUseStmt struct {
+	ParentNode            Node
 	Position              *position.Position
 	UseTkn                *token.Token
 	UseType               *Identifier
@@ -1222,6 +1346,7 @@ type GroupUseStmt struct {
 
 // HaltCompilerStmt is a `__halt_compiler()` statement.
 type HaltCompilerStmt struct {
+	ParentNode          Node
 	Position            *position.Position
 	HaltCompilerTkn     *token.Token
 	OpenParenthesisTkn  *token.Token
@@ -1233,6 +1358,7 @@ type HaltCompilerStmt struct {
 // $ElseIf contains an entire elseif chain, if any.
 // $Else may contain an else part of the statement.
 type IfStmt struct {
+	ParentNode          Node
 	Position            *position.Position
 	IfTkn               *token.Token
 	OpenParenthesisTkn  *token.Token
@@ -1252,6 +1378,7 @@ type IfStmt struct {
 // as a PHP script. In other words, it's everything outside of
 // the <? and ?> tags.
 type InlineHTMLStmt struct {
+	ParentNode    Node
 	Position      *position.Position
 	InlineHTMLTkn *token.Token
 	Value         string
@@ -1259,6 +1386,7 @@ type InlineHTMLStmt struct {
 
 // InterfaceStmt is an interface declaration.
 type InterfaceStmt struct {
+	ParentNode           Node
 	Position             *position.Position
 	AttrGroups           []*AttributeGroup
 	InterfaceTkn         *token.Token
@@ -1274,6 +1402,7 @@ type InterfaceStmt struct {
 // InterfaceExtendsStmt is a `extends $InterfaceNames...` statement.
 // TODO: InterfaceNames could be a []*Name.
 type InterfaceExtendsStmt struct {
+	ParentNode           Node
 	Position             *position.Position
 	ExtendsTkn           *token.Token
 	InterfaceNames       []Node
@@ -1282,14 +1411,16 @@ type InterfaceExtendsStmt struct {
 
 // LabelStmt is a `$LabelName:` statement.
 type LabelStmt struct {
-	Position  *position.Position
-	LabelName *Identifier
-	ColonTkn  *token.Token
+	ParentNode Node
+	Position   *position.Position
+	LabelName  *Identifier
+	ColonTkn   *token.Token
 }
 
 // NamespaceStmt is a `namespace $NamespaceName` statement.
 // If $Stmts is not nil, it's `namespace $NamespaceName { $Stmts... }`.
 type NamespaceStmt struct {
+	ParentNode           Node
 	Position             *position.Position
 	NsTkn                *token.Token
 	NamespaceName        *Name
@@ -1302,32 +1433,36 @@ type NamespaceStmt struct {
 // NopStmt is a `;` statement.
 // It's also known as "empty statement".
 type NopStmt struct {
+	ParentNode   Node
 	Position     *position.Position
 	SemiColonTkn *token.Token
 }
 
 // CloseTagStmt is `?>` (script closing marker).
 type CloseTagStmt struct {
-	Position *position.Position
-	TagTkn   *token.Token
+	ParentNode Node
+	Position   *position.Position
+	TagTkn     *token.Token
 }
 
 // PropertyStmt is a `$Variable = $Expr` statement.
 // It's a part of the *PropertyListStmt.
 type PropertyStmt struct {
-	Position *position.Position
-	Variable *SimpleVar
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   *SimpleVar
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // PropertyListStmt is a `#[$AttrGroups] $Modifiers $Type $Properties` statement.
 // Every element in $Properties is a *PropertyStmt.
 type PropertyListStmt struct {
+	ParentNode    Node
 	Position      *position.Position
 	AttrGroups    []*AttributeGroup
 	Modifiers     []*Identifier
-	Type          Node
+	PropertyType  Node
 	Properties    []Node
 	SeparatorTkns []*token.Token
 	SemiColonTkn  *token.Token
@@ -1337,6 +1472,7 @@ type PropertyListStmt struct {
 
 // ReturnStmt is a `return $Expr` statement.
 type ReturnStmt struct {
+	ParentNode   Node
 	Position     *position.Position
 	ReturnTkn    *token.Token
 	Expr         Node
@@ -1346,6 +1482,7 @@ type ReturnStmt struct {
 // StaticStmt is a `static $Vars...` statement.
 // Every element in $Vars is a *StaticVarStmt.
 type StaticStmt struct {
+	ParentNode    Node
 	Position      *position.Position
 	StaticTkn     *token.Token
 	Vars          []Node
@@ -1356,15 +1493,17 @@ type StaticStmt struct {
 // StaticVarStmt is a `$Variable = $Expr`.
 // It's a part of the *StaticStmt.
 type StaticVarStmt struct {
-	Position *position.Position
-	Variable *SimpleVar
-	EqualTkn *token.Token
-	Expr     Node
+	ParentNode Node
+	Position   *position.Position
+	Variable   *SimpleVar
+	EqualTkn   *token.Token
+	Expr       Node
 }
 
 // StmtList is a `{ $Stmts... }` statement.
 // It's also known as "block statement".
 type StmtList struct {
+	ParentNode           Node
 	Position             *position.Position
 	OpenCurlyBracketTkn  *token.Token
 	Stmts                []Node
@@ -1374,6 +1513,7 @@ type StmtList struct {
 // SwitchStmt is a `switch ($Cond) $CaseList` statement.
 // If $AltSyntax is true, the block will begin with `:` and end with `endswitch`.
 type SwitchStmt struct {
+	ParentNode           Node
 	Position             *position.Position
 	SwitchTkn            *token.Token
 	OpenParenthesisTkn   *token.Token
@@ -1391,6 +1531,7 @@ type SwitchStmt struct {
 
 // ThrowStmt is a `throw $Expr` statement.
 type ThrowStmt struct {
+	ParentNode   Node
 	Position     *position.Position
 	ThrowTkn     *token.Token
 	Expr         Node
@@ -1399,6 +1540,7 @@ type ThrowStmt struct {
 
 // TraitStmt is a trait declaration.
 type TraitStmt struct {
+	ParentNode           Node
 	Position             *position.Position
 	AttrGroups           []*AttributeGroup
 	TraitTkn             *token.Token
@@ -1412,17 +1554,20 @@ type TraitStmt struct {
 
 // TraitAdaptationListStmt is a block inside a *TraitUseStmt.
 type TraitAdaptationListStmt struct {
+	ParentNode  Node
 	Position    *position.Position
 	Adaptations []Node
 }
 
 type TraitMethodRefStmt struct {
-	Position *position.Position
-	Trait    Node
-	Method   *Identifier
+	ParentNode Node
+	Position   *position.Position
+	Trait      Node
+	Method     *Identifier
 }
 
 type TraitUseStmt struct {
+	ParentNode           Node
 	Position             *position.Position
 	UseTkn               *token.Token
 	Traits               []Node
@@ -1434,6 +1579,7 @@ type TraitUseStmt struct {
 }
 
 type TraitUseAliasStmt struct {
+	ParentNode     Node
 	Position       *position.Position
 	DoubleColonTkn *token.Token
 	Ref            Node
@@ -1444,6 +1590,7 @@ type TraitUseAliasStmt struct {
 }
 
 type TraitUsePrecedenceStmt struct {
+	ParentNode     Node
 	Position       *position.Position
 	DoubleColonTkn *token.Token
 	Ref            Node
@@ -1456,6 +1603,7 @@ type TraitUsePrecedenceStmt struct {
 // TryStmt is a `try { $Stmts... } $Catches` statement.
 // $Finally only presents if `finally {...}` block exists.
 type TryStmt struct {
+	ParentNode           Node
 	Position             *position.Position
 	TryTkn               *token.Token
 	OpenCurlyBracketTkn  *token.Token
@@ -1467,6 +1615,7 @@ type TryStmt struct {
 
 // UnsetStmt is a `unset($Vars...)` statement.
 type UnsetStmt struct {
+	ParentNode          Node
 	Position            *position.Position
 	UnsetTkn            *token.Token
 	OpenParenthesisTkn  *token.Token
@@ -1477,6 +1626,7 @@ type UnsetStmt struct {
 }
 
 type UseStmt struct {
+	ParentNode     Node
 	Position       *position.Position
 	UseType        *Identifier
 	NsSeparatorTkn *token.Token
@@ -1486,6 +1636,7 @@ type UseStmt struct {
 }
 
 type UseListStmt struct {
+	ParentNode    Node
 	Position      *position.Position
 	UseTkn        *token.Token
 	UseType       *Identifier
@@ -1497,6 +1648,7 @@ type UseListStmt struct {
 // WhileStmt is a `while ($Cond) $Stmt` statement.
 // If $AltSyntax is true, the block will begin with `:` and end with `endwhile`.
 type WhileStmt struct {
+	ParentNode          Node
 	Position            *position.Position
 	WhileTkn            *token.Token
 	OpenParenthesisTkn  *token.Token
@@ -1511,6 +1663,7 @@ type WhileStmt struct {
 
 // Union node is a `Type|Type1|...`
 type Union struct {
+	ParentNode    Node
 	Position      *position.Position
 	Types         []Node
 	SeparatorTkns []*token.Token
@@ -1518,6 +1671,7 @@ type Union struct {
 
 // Attribute node is a `$Name($Args)` inside `#[...]`
 type Attribute struct {
+	ParentNode          Node
 	Position            *position.Position
 	Name                Node
 	OpenParenthesisTkn  *token.Token
@@ -1528,6 +1682,7 @@ type Attribute struct {
 
 // AttributeGroup node is #[$Attrs]
 type AttributeGroup struct {
+	ParentNode        Node
 	Position          *position.Position
 	OpenAttributeTkn  *token.Token
 	Attrs             []*Attribute
@@ -1537,6 +1692,7 @@ type AttributeGroup struct {
 
 // NullsafeMethodCallExpr node is a `$Var?->$Method($Args)`
 type NullsafeMethodCallExpr struct {
+	ParentNode           Node
 	Position             *position.Position
 	Variable             Node
 	ObjectOperatorTkn    *token.Token
@@ -1551,6 +1707,7 @@ type NullsafeMethodCallExpr struct {
 
 // NullsafePropertyFetchExpr node is a `$Var?->Prop`
 type NullsafePropertyFetchExpr struct {
+	ParentNode           Node
 	Position             *position.Position
 	Variable             Node
 	ObjectOperatorTkn    *token.Token
@@ -1561,6 +1718,7 @@ type NullsafePropertyFetchExpr struct {
 
 // MatchExpr node is a `match($Expr) { $Arms }`
 type MatchExpr struct {
+	ParentNode           Node
 	Position             *position.Position
 	MatchTkn             *token.Token
 	OpenParenthesisTkn   *token.Token
@@ -1574,6 +1732,7 @@ type MatchExpr struct {
 
 // MatchArm node is a `$Exprs or 'default' => $ReturnExpr`
 type MatchArm struct {
+	ParentNode      Node
 	Position        *position.Position
 	DefaultTkn      *token.Token
 	DefaultCommaTkn *token.Token
@@ -1586,6 +1745,7 @@ type MatchArm struct {
 
 // ThrowExpr node is a `throw $Expr;`
 type ThrowExpr struct {
+	ParentNode   Node
 	Position     *position.Position
 	ThrowTkn     *token.Token
 	Expr         Node
diff --git a/src/ir/utils.go b/src/ir/utils.go
index 5037245..ae6d01d 100644
--- a/src/ir/utils.go
+++ b/src/ir/utils.go
@@ -1,6 +1,8 @@
 package ir
 
-import "github.com/VKCOM/php-parser/pkg/token"
+import (
+	"github.com/VKCOM/php-parser/pkg/token"
+)
 
 // traverseToken calls the passed function with the passed token and
 // recursively traverse all FreeFloating of the passed token.
diff --git a/src/ir/walk.go b/src/ir/walk.go
index f65aad3..09ff762 100644
--- a/src/ir/walk.go
+++ b/src/ir/walk.go
@@ -1483,8 +1483,8 @@ func (n *PropertyListStmt) Walk(v Visitor) {
 	for _, nn := range n.Modifiers {
 		nn.Walk(v)
 	}
-	if n.Type != nil {
-		n.Type.Walk(v)
+	if n.PropertyType != nil {
+		n.PropertyType.Walk(v)
 	}
 	for _, nn := range n.Properties {
 		nn.Walk(v)
diff --git a/src/linter/root.go b/src/linter/root.go
index 0a3be47..2f2cd44 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -918,7 +918,7 @@ func (d *rootWalker) enterPropertyList(pl *ir.PropertyListStmt) bool {
 	}
 
 	phpDocType := d.parsePHPDocVar(pl.Doc)
-	typeHintType, _ := d.parseTypeHintNode(pl.Type)
+	typeHintType, _ := d.parseTypeHintNode(pl.PropertyType)
 
 	for _, pNode := range pl.Properties {
 		prop := pNode.(*ir.PropertyStmt)
diff --git a/src/linter/root_checker.go b/src/linter/root_checker.go
index 40763c1..36c2e85 100644
--- a/src/linter/root_checker.go
+++ b/src/linter/root_checker.go
@@ -115,12 +115,12 @@ func (r *rootChecker) CheckPropertyList(pl *ir.PropertyListStmt) bool {
 	r.CheckCommentMisspellings(pl, pl.Doc.Raw)
 	r.CheckPHPDocVar(pl, pl.Doc, docblockType)
 
-	typeHintType, ok := r.walker.parseTypeHintNode(pl.Type)
+	typeHintType, ok := r.walker.parseTypeHintNode(pl.PropertyType)
 	if ok && !types.TypeHintHasMoreAccurateType(typeHintType, docblockType) {
 		r.walker.Report(pl, LevelNotice, "typeHint", "Specify the type for the property in PHPDoc, 'array' type hint too generic")
 	}
 
-	r.CheckTypeHintNode(pl.Type, "property type")
+	r.CheckTypeHintNode(pl.PropertyType, "property type")
 
 	for _, p := range pl.Properties {
 		prop := p.(*ir.PropertyStmt)
diff --git a/src/phpgrep/meta_nodes.go b/src/phpgrep/meta_nodes.go
index a774a70..9ddb1a2 100644
--- a/src/phpgrep/meta_nodes.go
+++ b/src/phpgrep/meta_nodes.go
@@ -12,6 +12,7 @@ type metaNode struct {
 
 func (metaNode) Walk(v ir.Visitor)                           {}
 func (metaNode) IterateTokens(func(token *token.Token) bool) {}
+func (metaNode) Parent() ir.Node                             { return nil }
 
 type (
 	anyConst struct{ metaNode }
diff --git a/src/rules/parser.go b/src/rules/parser.go
index 3eb6fd3..310f966 100644
--- a/src/rules/parser.go
+++ b/src/rules/parser.go
@@ -472,17 +472,22 @@ func (p *parser) checkForVariableInPattern(name string, pattern ir.Node, verifie
 		return true
 	}
 
-	found := irutil.FindWithPredicate(&ir.SimpleVar{Name: name}, pattern, func(what ir.Node, cur ir.Node) bool {
+	foundNode := irutil.FindChild(pattern, func(node ir.Node) bool {
 		// We need to check if there is a variable with this name
 		// in case the pattern contains the ${"[varName]:var"} template.
-		if s, ok := cur.(*ir.Var); ok {
-			if s, ok := s.Expr.(*ir.String); ok {
-				return strings.Contains(s.Value, what.(*ir.SimpleVar).Name+":var")
+		switch n := node.(type) {
+		case *ir.Var:
+			if s, ok := n.Expr.(*ir.String); ok {
+				return strings.Contains(s.Value, name+":var")
 			}
+		case *ir.SimpleVar:
+			return n.Name == name
 		}
+
 		return false
 	})
 
+	found := foundNode != nil
 	if found {
 		verifiedVars[name] = struct{}{}
 	}


Test patch:
diff --git a/src/ir/irutil/irutil_test.go b/src/ir/irutil/irutil_test.go
new file mode 100644
index 0000000..e483697
--- /dev/null
+++ b/src/ir/irutil/irutil_test.go
@@ -0,0 +1,55 @@
+package irutil_test
+
+import (
+	"testing"
+
+	"github.com/VKCOM/noverify/src/ir"
+	"github.com/VKCOM/noverify/src/ir/irconv"
+	"github.com/VKCOM/noverify/src/ir/irutil"
+	"github.com/VKCOM/noverify/src/php/parseutil"
+)
+
+func TestParent(t *testing.T) {
+	code := `<?php
+  class Foo {
+    public function bar() {
+      $a = 1;
+      $b = 2;
+    }
+  }
+  
+  function foo($с, string $d) {
+    echo $с;
+  }
+`
+
+	rootAst, _, err := parseutil.Parse([]byte(code))
+	if err != nil {
+		return
+	}
+
+	root := irconv.ConvertNode(rootAst).(*ir.Root)
+
+	varNode := irutil.FindChild(root, func(node ir.Node) bool {
+		variable, ok := node.(*ir.SimpleVar)
+		return ok && variable.Name == "a"
+	})
+
+	barFuncNode := irutil.FindChild(root, func(node ir.Node) bool {
+		fun, ok := node.(*ir.ClassMethodStmt)
+		return ok && fun.MethodName.Value == "bar"
+	})
+
+	if barFuncNode != varNode.Parent().Parent().Parent().Parent() {
+		t.Errorf("expected %v to be parent of %v", irutil.FmtNode(barFuncNode), irutil.FmtNode(varNode))
+	}
+
+	rootNew := irutil.FindParent(varNode, func(node ir.Node) bool {
+		_, ok := node.(*ir.Root)
+		return ok
+	})
+
+	if rootNew != root {
+		t.Errorf("expected root, got %T", rootNew)
+	}
+}