Repository: VKCOM/noverify
Pull request #659 (2020-08-11 15:43:27)

ir: rework Name nodes
Merge Name+FullyQualifiedName and store all parts as a
combined string.

When we need to access name individual parts, we can
do so via Name methods. They are zero-alloc and are
somewhat cheap to compute (not more expensive than
having to turn old name parts to string for sure and
compare them via NameEquals instead of just ==).

FQN are expressed with Name.Value with leading `\`.
Normal names do not have leading `\`.

RelativeName is converted into Name with namespace expansion
during the irgen phase, so we eliminate that node type as well.
Resolved RelativeNames are FQNs.

Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>


Patch:
diff --git a/example/custom/main.go b/example/custom/main.go
index ae39241..6cd58cb 100644
--- a/example/custom/main.go
+++ b/example/custom/main.go
@@ -9,7 +9,6 @@ import (
 	"github.com/VKCOM/noverify/src/cmd"
 	"github.com/VKCOM/noverify/src/ir"
 	"github.com/VKCOM/noverify/src/linter"
-	"github.com/VKCOM/noverify/src/meta"
 	"github.com/VKCOM/noverify/src/solver"
 )
 
@@ -75,7 +74,7 @@ func (b *block) handleFunctionCall(e *ir.FunctionCallExpr) {
 		return
 	}
 
-	if meta.NameEquals(nm, `in_array`) {
+	if nm.Value == `in_array` {
 		b.handleInArrayCall(e)
 		return
 	}
diff --git a/src/ir/freefloating.go b/src/ir/freefloating.go
index 6bfdc3c..0a488ce 100644
--- a/src/ir/freefloating.go
+++ b/src/ir/freefloating.go
@@ -168,14 +168,8 @@ func (n *YieldExpr) GetFreeFloating() *freefloating.Collection { return &n.FreeF
 
 func (n *YieldFromExpr) GetFreeFloating() *freefloating.Collection { return &n.FreeFloating }
 
-func (n *FullyQualifiedName) GetFreeFloating() *freefloating.Collection { return &n.FreeFloating }
-
 func (n *Name) GetFreeFloating() *freefloating.Collection { return &n.FreeFloating }
 
-func (n *NamePart) GetFreeFloating() *freefloating.Collection { return &n.FreeFloating }
-
-func (n *RelativeName) GetFreeFloating() *freefloating.Collection { return &n.FreeFloating }
-
 func (n *Argument) GetFreeFloating() *freefloating.Collection { return &n.FreeFloating }
 
 func (n *ArgumentList) GetFreeFloating() *freefloating.Collection { return &n.FreeFloating }
diff --git a/src/ir/get_node_kind.go b/src/ir/get_node_kind.go
index dc84ce7..9ab8a1d 100644
--- a/src/ir/get_node_kind.go
+++ b/src/ir/get_node_kind.go
@@ -89,10 +89,7 @@ const (
 	KindUnaryPlusExpr
 	KindYieldExpr
 	KindYieldFromExpr
-	KindFullyQualifiedName
 	KindName
-	KindNamePart
-	KindRelativeName
 	KindArgument
 	KindArgumentList
 	KindIdentifier
@@ -331,14 +328,8 @@ func GetNodeKind(n Node) NodeKind {
 		return KindYieldExpr
 	case *YieldFromExpr:
 		return KindYieldFromExpr
-	case *FullyQualifiedName:
-		return KindFullyQualifiedName
 	case *Name:
 		return KindName
-	case *NamePart:
-		return KindNamePart
-	case *RelativeName:
-		return KindRelativeName
 	case *Argument:
 		return KindArgument
 	case *ArgumentList:
diff --git a/src/ir/get_position.go b/src/ir/get_position.go
index 927e2b8..3dcf1e6 100644
--- a/src/ir/get_position.go
+++ b/src/ir/get_position.go
@@ -172,14 +172,8 @@ func GetPosition(n Node) *position.Position {
 		return n.Position
 	case *YieldFromExpr:
 		return n.Position
-	case *FullyQualifiedName:
-		return n.Position
 	case *Name:
 		return n.Position
-	case *NamePart:
-		return n.Position
-	case *RelativeName:
-		return n.Position
 	case *Argument:
 		return n.Position
 	case *ArgumentList:
diff --git a/src/ir/irfmt/printer.go b/src/ir/irfmt/printer.go
index ed40ca1..f773c80 100644
--- a/src/ir/irfmt/printer.go
+++ b/src/ir/irfmt/printer.go
@@ -92,16 +92,8 @@ func (p *PrettyPrinter) printNode(n ir.Node) {
 	case *ir.Argument:
 		p.printNodeArgument(n)
 
-		// name
-
-	case *ir.NamePart:
-		p.printNameNamePart(n)
 	case *ir.Name:
 		p.printNameName(n)
-	case *ir.FullyQualifiedName:
-		p.printNameFullyQualified(n)
-	case *ir.RelativeName:
-		p.printNameRelative(n)
 
 		// scalar
 
@@ -464,33 +456,8 @@ func (p *PrettyPrinter) printNodeArgument(n *ir.Argument) {
 
 // name
 
-func (p *PrettyPrinter) printNameNamePart(n *ir.NamePart) {
-	io.WriteString(p.w, n.Value)
-}
-
 func (p *PrettyPrinter) printNameName(n *ir.Name) {
-	for k, part := range n.Parts {
-		if k > 0 {
-			io.WriteString(p.w, "\\")
-		}
-
-		p.Print(part)
-	}
-}
-
-func (p *PrettyPrinter) printNameFullyQualified(n *ir.FullyQualifiedName) {
-	for _, part := range n.Parts {
-		io.WriteString(p.w, "\\")
-		p.Print(part)
-	}
-}
-
-func (p *PrettyPrinter) printNameRelative(n *ir.RelativeName) {
-	io.WriteString(p.w, "namespace")
-	for _, part := range n.Parts {
-		io.WriteString(p.w, "\\")
-		p.Print(part)
-	}
+	io.WriteString(p.w, n.Value)
 }
 
 // scalar
diff --git a/src/ir/irutil/clone.go b/src/ir/irutil/clone.go
index efbe8cc..2f79d68 100644
--- a/src/ir/irutil/clone.go
+++ b/src/ir/irutil/clone.go
@@ -469,7 +469,7 @@ func NodeClone(x ir.Node) ir.Node {
 	case *ir.ConstFetchExpr:
 		clone := *x
 		if x.Constant != nil {
-			clone.Constant = NodeClone(x.Constant).(ir.Node)
+			clone.Constant = NodeClone(x.Constant).(*ir.Name)
 		}
 		return &clone
 	case *ir.ConstListStmt:
@@ -670,16 +670,6 @@ func NodeClone(x ir.Node) ir.Node {
 			clone.Stmt = NodeClone(x.Stmt).(ir.Node)
 		}
 		return &clone
-	case *ir.FullyQualifiedName:
-		clone := *x
-		{
-			sliceClone := make([]ir.Node, len(x.Parts))
-			for i := range x.Parts {
-				sliceClone[i] = NodeClone(x.Parts[i]).(ir.Node)
-			}
-			clone.Parts = sliceClone
-		}
-		return &clone
 	case *ir.FunctionCallExpr:
 		clone := *x
 		if x.Function != nil {
@@ -949,22 +939,12 @@ func NodeClone(x ir.Node) ir.Node {
 		}
 		return &clone
 	case *ir.Name:
-		clone := *x
-		{
-			sliceClone := make([]ir.Node, len(x.Parts))
-			for i := range x.Parts {
-				sliceClone[i] = NodeClone(x.Parts[i]).(ir.Node)
-			}
-			clone.Parts = sliceClone
-		}
-		return &clone
-	case *ir.NamePart:
 		clone := *x
 		return &clone
 	case *ir.NamespaceStmt:
 		clone := *x
 		if x.NamespaceName != nil {
-			clone.NamespaceName = NodeClone(x.NamespaceName).(ir.Node)
+			clone.NamespaceName = NodeClone(x.NamespaceName).(*ir.Name)
 		}
 		{
 			sliceClone := make([]ir.Node, len(x.Stmts))
@@ -1120,16 +1100,6 @@ func NodeClone(x ir.Node) ir.Node {
 			clone.Variable = NodeClone(x.Variable).(ir.Node)
 		}
 		return &clone
-	case *ir.RelativeName:
-		clone := *x
-		{
-			sliceClone := make([]ir.Node, len(x.Parts))
-			for i := range x.Parts {
-				sliceClone[i] = NodeClone(x.Parts[i]).(ir.Node)
-			}
-			clone.Parts = sliceClone
-		}
-		return &clone
 	case *ir.ReturnStmt:
 		clone := *x
 		if x.Expr != nil {
diff --git a/src/ir/irutil/equal.go b/src/ir/irutil/equal.go
index 778c120..aeace2a 100644
--- a/src/ir/irutil/equal.go
+++ b/src/ir/irutil/equal.go
@@ -963,20 +963,6 @@ func NodeEqual(x, y ir.Node) bool {
 			return false
 		}
 		return true
-	case *ir.FullyQualifiedName:
-		y, ok := y.(*ir.FullyQualifiedName)
-		if !ok || x == nil || y == nil {
-			return x == y
-		}
-		if len(x.Parts) != len(y.Parts) {
-			return false
-		}
-		for i := range x.Parts {
-			if !NodeEqual(x.Parts[i], y.Parts[i]) {
-				return false
-			}
-		}
-		return true
 	case *ir.FunctionCallExpr:
 		y, ok := y.(*ir.FunctionCallExpr)
 		if !ok || x == nil || y == nil {
@@ -1380,20 +1366,6 @@ func NodeEqual(x, y ir.Node) bool {
 		if !ok || x == nil || y == nil {
 			return x == y
 		}
-		if len(x.Parts) != len(y.Parts) {
-			return false
-		}
-		for i := range x.Parts {
-			if !NodeEqual(x.Parts[i], y.Parts[i]) {
-				return false
-			}
-		}
-		return true
-	case *ir.NamePart:
-		y, ok := y.(*ir.NamePart)
-		if !ok || x == nil || y == nil {
-			return x == y
-		}
 		if x.Value != y.Value {
 			return false
 		}
@@ -1626,20 +1598,6 @@ func NodeEqual(x, y ir.Node) bool {
 			return false
 		}
 		return true
-	case *ir.RelativeName:
-		y, ok := y.(*ir.RelativeName)
-		if !ok || x == nil || y == nil {
-			return x == y
-		}
-		if len(x.Parts) != len(y.Parts) {
-			return false
-		}
-		for i := range x.Parts {
-			if !NodeEqual(x.Parts[i], y.Parts[i]) {
-				return false
-			}
-		}
-		return true
 	case *ir.ReturnStmt:
 		y, ok := y.(*ir.ReturnStmt)
 		if !ok || x == nil || y == nil {
diff --git a/src/ir/methods.go b/src/ir/methods.go
new file mode 100644
index 0000000..e043eed
--- /dev/null
+++ b/src/ir/methods.go
@@ -0,0 +1,75 @@
+package ir
+
+import (
+	"strings"
+)
+
+// IsFullyQualified reports whether the name is fully qualified.
+// FQN don't need any further resolution.
+func (n *Name) IsFullyQualified() bool {
+	return strings.HasPrefix(n.Value, `\`)
+}
+
+// NumParts reports number of the name parts.
+func (n *Name) NumParts() int {
+	s := n.Value
+	if n.IsFullyQualified() {
+		s = s[len(`\`):]
+	}
+	return strings.Count(s, `\`) + 1
+}
+
+// HeadTail is an efficient way to get <FirstPart, RestParts> pair.
+func (n *Name) HeadTail() (head, tail string) {
+	s := n.Value
+	if n.IsFullyQualified() {
+		s = s[len(`\`):]
+	}
+	slash := strings.IndexByte(s, '\\')
+	if slash == -1 {
+		return s, ""
+	}
+	return s[:slash], s[len(`\`)+slash:]
+}
+
+// LastPart returns only the last name part.
+// If name contains only one part, that part is returned.
+func (n *Name) LastPart() string {
+	s := n.Value
+	if n.IsFullyQualified() {
+		s = s[len(`\`):]
+	}
+	lastSlash := strings.LastIndexByte(s, '\\')
+	if lastSlash == -1 {
+		return s
+	}
+	return s[len(`\`)+lastSlash:]
+}
+
+// FirstPart returns only the first name part.
+// If name contains only one part, that part is returned.
+func (n *Name) FirstPart() string {
+	s := n.Value
+	if n.IsFullyQualified() {
+		s = s[len(`\`):]
+	}
+	slash := strings.IndexByte(s, '\\')
+	if slash == -1 {
+		return s
+	}
+	return s[:slash]
+}
+
+// RestParts returns all but first name parts.
+// If name contains only one part, empty string is returned.
+func (n *Name) RestParts() string {
+	s := n.Value
+	if n.IsFullyQualified() {
+		s = s[len(`\`):]
+	}
+	slash := strings.IndexByte(s, '\\')
+	if slash == -1 {
+		return ""
+	}
+	return s[len(`\`)+slash:]
+}
diff --git a/src/ir/types.go b/src/ir/types.go
index fe63fe8..4392952 100644
--- a/src/ir/types.go
+++ b/src/ir/types.go
@@ -401,7 +401,7 @@ type ClosureUseExpr struct {
 type ConstFetchExpr struct {
 	FreeFloating freefloating.Collection
 	Position     *position.Position
-	Constant     Node
+	Constant     *Name
 }
 
 type EmptyExpr struct {
@@ -581,30 +581,12 @@ type YieldFromExpr struct {
 	Expr         Node
 }
 
-type FullyQualifiedName struct {
-	FreeFloating freefloating.Collection
-	Position     *position.Position
-	Parts        []Node
-}
-
 type Name struct {
-	FreeFloating freefloating.Collection
-	Position     *position.Position
-	Parts        []Node
-}
-
-type NamePart struct {
 	FreeFloating freefloating.Collection
 	Position     *position.Position
 	Value        string
 }
 
-type RelativeName struct {
-	FreeFloating freefloating.Collection
-	Position     *position.Position
-	Parts        []Node
-}
-
 type Argument struct {
 	FreeFloating freefloating.Collection
 	Position     *position.Position
@@ -943,7 +925,7 @@ type LabelStmt struct {
 type NamespaceStmt struct {
 	FreeFloating  freefloating.Collection
 	Position      *position.Position
-	NamespaceName Node
+	NamespaceName *Name
 	Stmts         []Node
 }
 
diff --git a/src/ir/walk.go b/src/ir/walk.go
index 925eadf..3b2f452 100644
--- a/src/ir/walk.go
+++ b/src/ir/walk.go
@@ -1022,46 +1022,10 @@ func (n *YieldFromExpr) Walk(v Visitor) {
 	v.LeaveNode(n)
 }
 
-func (n *FullyQualifiedName) Walk(v Visitor) {
-	if !v.EnterNode(n) {
-		return
-	}
-	for i := range n.Parts {
-		if n.Parts[i] != nil {
-			n.Parts[i].Walk(v)
-		}
-	}
-	v.LeaveNode(n)
-}
-
 func (n *Name) Walk(v Visitor) {
 	if !v.EnterNode(n) {
 		return
 	}
-	for i := range n.Parts {
-		if n.Parts[i] != nil {
-			n.Parts[i].Walk(v)
-		}
-	}
-	v.LeaveNode(n)
-}
-
-func (n *NamePart) Walk(v Visitor) {
-	if !v.EnterNode(n) {
-		return
-	}
-	v.LeaveNode(n)
-}
-
-func (n *RelativeName) Walk(v Visitor) {
-	if !v.EnterNode(n) {
-		return
-	}
-	for i := range n.Parts {
-		if n.Parts[i] != nil {
-			n.Parts[i].Walk(v)
-		}
-	}
 	v.LeaveNode(n)
 }
 
diff --git a/src/irgen/irgen.go b/src/irgen/irgen.go
index 26a5f57..8ab1325 100644
--- a/src/irgen/irgen.go
+++ b/src/irgen/irgen.go
@@ -25,6 +25,7 @@ func ConvertNode(n node.Node) ir.Node {
 }
 
 type convState struct {
+	namespace string
 }
 
 func convertNodeSlice(state *convState, xs []node.Node) []ir.Node {
@@ -47,8 +48,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.Assign{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.BitwiseAnd:
@@ -58,8 +59,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignBitwiseAnd{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.BitwiseOr:
@@ -69,8 +70,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignBitwiseOr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.BitwiseXor:
@@ -80,8 +81,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignBitwiseXor{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.Coalesce:
@@ -91,8 +92,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignCoalesce{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.Concat:
@@ -102,8 +103,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignConcat{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.Div:
@@ -113,8 +114,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignDiv{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.Minus:
@@ -124,8 +125,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignMinus{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.Mod:
@@ -135,8 +136,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignMod{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.Mul:
@@ -146,8 +147,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignMul{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.Plus:
@@ -157,8 +158,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignPlus{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.Pow:
@@ -168,8 +169,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignPow{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.Reference:
@@ -179,8 +180,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignReference{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.ShiftLeft:
@@ -190,8 +191,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignShiftLeft{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *assign.ShiftRight:
@@ -201,8 +202,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.AssignShiftRight{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Expression = ConvertNode(n.Expression)
+		out.Variable = convertNode(state, n.Variable)
+		out.Expression = convertNode(state, n.Expression)
 		return out
 
 	case *binary.BitwiseAnd:
@@ -212,8 +213,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.BitwiseAndExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.BitwiseOr:
@@ -223,8 +224,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.BitwiseOrExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.BitwiseXor:
@@ -234,8 +235,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.BitwiseXorExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.BooleanAnd:
@@ -245,8 +246,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.BooleanAndExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.BooleanOr:
@@ -256,8 +257,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.BooleanOrExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Coalesce:
@@ -267,8 +268,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.CoalesceExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Concat:
@@ -278,8 +279,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ConcatExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Div:
@@ -289,8 +290,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.DivExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Equal:
@@ -300,8 +301,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.EqualExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Greater:
@@ -311,8 +312,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.GreaterExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.GreaterOrEqual:
@@ -322,8 +323,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.GreaterOrEqualExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Identical:
@@ -333,8 +334,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.IdenticalExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.LogicalAnd:
@@ -344,8 +345,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.LogicalAndExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.LogicalOr:
@@ -355,8 +356,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.LogicalOrExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.LogicalXor:
@@ -366,8 +367,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.LogicalXorExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Minus:
@@ -377,8 +378,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.MinusExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Mod:
@@ -388,8 +389,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ModExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Mul:
@@ -399,8 +400,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.MulExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.NotEqual:
@@ -410,8 +411,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.NotEqualExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.NotIdentical:
@@ -421,8 +422,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.NotIdenticalExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Plus:
@@ -432,8 +433,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.PlusExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Pow:
@@ -443,8 +444,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.PowExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.ShiftLeft:
@@ -454,8 +455,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ShiftLeftExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.ShiftRight:
@@ -465,8 +466,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ShiftRightExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Smaller:
@@ -476,8 +477,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.SmallerExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.SmallerOrEqual:
@@ -487,8 +488,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.SmallerOrEqualExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *binary.Spaceship:
@@ -498,22 +499,22 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.SpaceshipExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Left = ConvertNode(n.Left)
-		out.Right = ConvertNode(n.Right)
+		out.Left = convertNode(state, n.Left)
+		out.Right = convertNode(state, n.Right)
 		return out
 
 	case *cast.Array:
-		return convCastExpr(n, n.Expr, "array")
+		return convCastExpr(state, n, n.Expr, "array")
 	case *cast.Bool:
-		return convCastExpr(n, n.Expr, "bool")
+		return convCastExpr(state, n, n.Expr, "bool")
 	case *cast.Int:
-		return convCastExpr(n, n.Expr, "int")
+		return convCastExpr(state, n, n.Expr, "int")
 	case *cast.Double:
-		return convCastExpr(n, n.Expr, "float")
+		return convCastExpr(state, n, n.Expr, "float")
 	case *cast.Object:
-		return convCastExpr(n, n.Expr, "object")
+		return convCastExpr(state, n, n.Expr, "object")
 	case *cast.String:
-		return convCastExpr(n, n.Expr, "string")
+		return convCastExpr(state, n, n.Expr, "string")
 
 	case *cast.Unset:
 		// We dont convert (unset)$x into CastExpr deliberately.
@@ -523,7 +524,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.UnsetCastExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.Array:
@@ -536,7 +537,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		{
 			slice := make([]*ir.ArrayItemExpr, len(n.Items))
 			for i := range n.Items {
-				slice[i] = ConvertNode(n.Items[i]).(*ir.ArrayItemExpr)
+				slice[i] = convertNode(state, n.Items[i]).(*ir.ArrayItemExpr)
 			}
 			out.Items = slice
 		}
@@ -550,8 +551,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ArrayDimFetchExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Dim = ConvertNode(n.Dim)
+		out.Variable = convertNode(state, n.Variable)
+		out.Dim = convertNode(state, n.Dim)
 		return out
 
 	case *expr.ArrayItem:
@@ -561,8 +562,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ArrayItemExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Key = ConvertNode(n.Key)
-		out.Val = ConvertNode(n.Val)
+		out.Key = convertNode(state, n.Key)
+		out.Val = convertNode(state, n.Val)
 		out.Unpack = n.Unpack
 		return out
 
@@ -577,8 +578,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.Static = n.Static
 		out.PhpDocComment = n.PhpDocComment
 		out.Params = convertNodeSlice(state, n.Params)
-		out.ReturnType = ConvertNode(n.ReturnType)
-		out.Expr = ConvertNode(n.Expr)
+		out.ReturnType = convertNode(state, n.ReturnType)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.BitwiseNot:
@@ -588,7 +589,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.BitwiseNotExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.BooleanNot:
@@ -598,7 +599,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.BooleanNotExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.ClassConstFetch:
@@ -608,8 +609,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ClassConstFetchExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Class = ConvertNode(n.Class)
-		out.ConstantName = ConvertNode(n.ConstantName).(*ir.Identifier)
+		out.Class = convertNode(state, n.Class)
+		out.ConstantName = convertNode(state, n.ConstantName).(*ir.Identifier)
 		return out
 
 	case *expr.Clone:
@@ -619,7 +620,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.CloneExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.Closure:
@@ -633,8 +634,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.Static = n.Static
 		out.PhpDocComment = n.PhpDocComment
 		out.Params = convertNodeSlice(state, n.Params)
-		out.ClosureUse = ConvertNode(n.ClosureUse).(*ir.ClosureUseExpr)
-		out.ReturnType = ConvertNode(n.ReturnType)
+		out.ClosureUse = convertNode(state, n.ClosureUse).(*ir.ClosureUseExpr)
+		out.ReturnType = convertNode(state, n.ReturnType)
 		out.Stmts = convertNodeSlice(state, n.Stmts)
 		return out
 
@@ -655,7 +656,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ConstFetchExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Constant = ConvertNode(n.Constant)
+		out.Constant = convertNode(state, n.Constant).(*ir.Name)
 		return out
 
 	case *expr.Empty:
@@ -665,7 +666,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.EmptyExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.ErrorSuppress:
@@ -675,7 +676,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ErrorSuppressExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.Eval:
@@ -685,7 +686,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.EvalExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.Exit:
@@ -696,7 +697,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.FreeFloating = n.FreeFloating
 		out.Die = n.Die
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.FunctionCall:
@@ -706,8 +707,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.FunctionCallExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Function = ConvertNode(n.Function)
-		out.ArgumentList = ConvertNode(n.ArgumentList).(*ir.ArgumentList)
+		out.Function = convertNode(state, n.Function)
+		out.ArgumentList = convertNode(state, n.ArgumentList).(*ir.ArgumentList)
 		return out
 
 	case *expr.InstanceOf:
@@ -717,8 +718,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.InstanceOfExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
-		out.Class = ConvertNode(n.Class)
+		out.Expr = convertNode(state, n.Expr)
+		out.Class = convertNode(state, n.Class)
 		return out
 
 	case *expr.Isset:
@@ -741,7 +742,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		{
 			slice := make([]*ir.ArrayItemExpr, len(n.Items))
 			for i := range n.Items {
-				slice[i] = ConvertNode(n.Items[i]).(*ir.ArrayItemExpr)
+				slice[i] = convertNode(state, n.Items[i]).(*ir.ArrayItemExpr)
 			}
 			out.Items = slice
 		}
@@ -755,9 +756,9 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.MethodCallExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Method = ConvertNode(n.Method)
-		out.ArgumentList = ConvertNode(n.ArgumentList).(*ir.ArgumentList)
+		out.Variable = convertNode(state, n.Variable)
+		out.Method = convertNode(state, n.Method)
+		out.ArgumentList = convertNode(state, n.ArgumentList).(*ir.ArgumentList)
 		return out
 
 	case *expr.New:
@@ -767,8 +768,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.NewExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Class = ConvertNode(n.Class)
-		out.ArgumentList = ConvertNode(n.ArgumentList).(*ir.ArgumentList)
+		out.Class = convertNode(state, n.Class)
+		out.ArgumentList = convertNode(state, n.ArgumentList).(*ir.ArgumentList)
 		return out
 
 	case *expr.Paren:
@@ -778,7 +779,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ParenExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.PostDec:
@@ -788,7 +789,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.PostDecExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
+		out.Variable = convertNode(state, n.Variable)
 		return out
 
 	case *expr.PostInc:
@@ -798,7 +799,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.PostIncExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
+		out.Variable = convertNode(state, n.Variable)
 		return out
 
 	case *expr.PreDec:
@@ -808,7 +809,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.PreDecExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
+		out.Variable = convertNode(state, n.Variable)
 		return out
 
 	case *expr.PreInc:
@@ -818,7 +819,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.PreIncExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
+		out.Variable = convertNode(state, n.Variable)
 		return out
 
 	case *expr.Print:
@@ -828,7 +829,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.PrintExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.PropertyFetch:
@@ -838,8 +839,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.PropertyFetchExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
-		out.Property = ConvertNode(n.Property)
+		out.Variable = convertNode(state, n.Variable)
+		out.Property = convertNode(state, n.Property)
 		return out
 
 	case *expr.Reference:
@@ -849,17 +850,17 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ReferenceExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable)
+		out.Variable = convertNode(state, n.Variable)
 		return out
 
 	case *expr.Require:
-		return convImportExpr(n, n.Expr, "require")
+		return convImportExpr(state, n, n.Expr, "require")
 	case *expr.RequireOnce:
-		return convImportExpr(n, n.Expr, "require_once")
+		return convImportExpr(state, n, n.Expr, "require_once")
 	case *expr.Include:
-		return convImportExpr(n, n.Expr, "include")
+		return convImportExpr(state, n, n.Expr, "include")
 	case *expr.IncludeOnce:
-		return convImportExpr(n, n.Expr, "include_once")
+		return convImportExpr(state, n, n.Expr, "include_once")
 
 	case *expr.ShellExec:
 		if n == nil {
@@ -878,9 +879,9 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.StaticCallExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Class = ConvertNode(n.Class)
-		out.Call = ConvertNode(n.Call)
-		out.ArgumentList = ConvertNode(n.ArgumentList).(*ir.ArgumentList)
+		out.Class = convertNode(state, n.Class)
+		out.Call = convertNode(state, n.Call)
+		out.ArgumentList = convertNode(state, n.ArgumentList).(*ir.ArgumentList)
 		return out
 
 	case *expr.StaticPropertyFetch:
@@ -890,8 +891,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.StaticPropertyFetchExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Class = ConvertNode(n.Class)
-		out.Property = ConvertNode(n.Property)
+		out.Class = convertNode(state, n.Class)
+		out.Property = convertNode(state, n.Property)
 		return out
 
 	case *expr.Ternary:
@@ -901,9 +902,9 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.TernaryExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Condition = ConvertNode(n.Condition)
-		out.IfTrue = ConvertNode(n.IfTrue)
-		out.IfFalse = ConvertNode(n.IfFalse)
+		out.Condition = convertNode(state, n.Condition)
+		out.IfTrue = convertNode(state, n.IfTrue)
+		out.IfFalse = convertNode(state, n.IfFalse)
 		return out
 
 	case *expr.UnaryMinus:
@@ -913,7 +914,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.UnaryMinusExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.UnaryPlus:
@@ -923,7 +924,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.UnaryPlusExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *expr.Yield:
@@ -933,8 +934,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.YieldExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Key = ConvertNode(n.Key)
-		out.Value = ConvertNode(n.Value)
+		out.Key = convertNode(state, n.Key)
+		out.Value = convertNode(state, n.Value)
 		return out
 
 	case *expr.YieldFrom:
@@ -944,48 +945,23 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.YieldFromExpr{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *name.FullyQualified:
-		if n == nil {
-			return (*ir.FullyQualifiedName)(nil)
+		return &ir.Name{
+			FreeFloating: n.FreeFloating,
+			Position:     n.Position,
+			Value:        fullyQualifiedToString(n),
 		}
-		out := &ir.FullyQualifiedName{}
-		out.FreeFloating = n.FreeFloating
-		out.Position = n.Position
-		out.Parts = convertNodeSlice(state, n.Parts)
-		return out
-
 	case *name.Name:
-		if n == nil {
-			return (*ir.Name)(nil)
+		return &ir.Name{
+			FreeFloating: n.FreeFloating,
+			Position:     n.Position,
+			Value:        namePartsToString(n.Parts),
 		}
-		out := &ir.Name{}
-		out.FreeFloating = n.FreeFloating
-		out.Position = n.Position
-		out.Parts = convertNodeSlice(state, n.Parts)
-		return out
-
-	case *name.NamePart:
-		if n == nil {
-			return (*ir.NamePart)(nil)
-		}
-		out := &ir.NamePart{}
-		out.FreeFloating = n.FreeFloating
-		out.Position = n.Position
-		out.Value = n.Value
-		return out
-
 	case *name.Relative:
-		if n == nil {
-			return (*ir.RelativeName)(nil)
-		}
-		out := &ir.RelativeName{}
-		out.FreeFloating = n.FreeFloating
-		out.Position = n.Position
-		out.Parts = convertNodeSlice(state, n.Parts)
-		return out
+		return convRelativeName(state, n)
 
 	case *node.Argument:
 		if n == nil {
@@ -996,7 +972,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.Position = n.Position
 		out.Variadic = n.Variadic
 		out.IsReference = n.IsReference
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *node.ArgumentList:
@@ -1009,7 +985,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		{
 			slice := make([]ir.Node, len(n.Arguments))
 			for i := range n.Arguments {
-				slice[i] = ConvertNode(n.Arguments[i])
+				slice[i] = convertNode(state, n.Arguments[i])
 			}
 			out.Arguments = slice
 		}
@@ -1032,7 +1008,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.Nullable{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *node.Parameter:
@@ -1044,9 +1020,9 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.Position = n.Position
 		out.ByRef = n.ByRef
 		out.Variadic = n.Variadic
-		out.VariableType = ConvertNode(n.VariableType)
-		out.Variable = ConvertNode(n.Variable).(*ir.SimpleVar)
-		out.DefaultValue = ConvertNode(n.DefaultValue)
+		out.VariableType = convertNode(state, n.VariableType)
+		out.Variable = convertNode(state, n.Variable).(*ir.SimpleVar)
+		out.DefaultValue = convertNode(state, n.DefaultValue)
 		return out
 
 	case *node.Root:
@@ -1059,7 +1035,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		{
 			slice := make([]ir.Node, len(n.Stmts))
 			for i := range n.Stmts {
-				slice[i] = ConvertNode(n.Stmts[i])
+				slice[i] = convertNode(state, n.Stmts[i])
 			}
 			out.Stmts = slice
 		}
@@ -1082,7 +1058,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.Var{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *scalar.Dnumber:
@@ -1163,7 +1139,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.BreakStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *stmt.Case:
@@ -1173,7 +1149,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.CaseStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Cond = ConvertNode(n.Cond)
+		out.Cond = convertNode(state, n.Cond)
 		out.Stmts = convertNodeSlice(state, n.Stmts)
 		return out
 
@@ -1195,7 +1171,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
 		out.Types = convertNodeSlice(state, n.Types)
-		out.Variable = ConvertNode(n.Variable).(*ir.SimpleVar)
+		out.Variable = convertNode(state, n.Variable).(*ir.SimpleVar)
 		out.Stmts = convertNodeSlice(state, n.Stmts)
 		return out
 
@@ -1207,17 +1183,17 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
 		out.PhpDocComment = n.PhpDocComment
-		out.ClassName = ConvertNode(n.ClassName).(*ir.Identifier)
+		out.ClassName = convertNode(state, n.ClassName).(*ir.Identifier)
 		{
 			slice := make([]*ir.Identifier, len(n.Modifiers))
 			for i := range n.Modifiers {
-				slice[i] = ConvertNode(n.Modifiers[i]).(*ir.Identifier)
+				slice[i] = convertNode(state, n.Modifiers[i]).(*ir.Identifier)
 			}
 			out.Modifiers = slice
 		}
-		out.ArgumentList = ConvertNode(n.ArgumentList).(*ir.ArgumentList)
-		out.Extends = ConvertNode(n.Extends).(*ir.ClassExtendsStmt)
-		out.Implements = ConvertNode(n.Implements).(*ir.ClassImplementsStmt)
+		out.ArgumentList = convertNode(state, n.ArgumentList).(*ir.ArgumentList)
+		out.Extends = convertNode(state, n.Extends).(*ir.ClassExtendsStmt)
+		out.Implements = convertNode(state, n.Implements).(*ir.ClassImplementsStmt)
 		out.Stmts = convertNodeSlice(state, n.Stmts)
 		return out
 
@@ -1231,7 +1207,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		{
 			slice := make([]*ir.Identifier, len(n.Modifiers))
 			for i := range n.Modifiers {
-				slice[i] = ConvertNode(n.Modifiers[i]).(*ir.Identifier)
+				slice[i] = convertNode(state, n.Modifiers[i]).(*ir.Identifier)
 			}
 			out.Modifiers = slice
 		}
@@ -1245,7 +1221,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ClassExtendsStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.ClassName = ConvertNode(n.ClassName)
+		out.ClassName = convertNode(state, n.ClassName)
 		return out
 
 	case *stmt.ClassImplements:
@@ -1267,17 +1243,17 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.Position = n.Position
 		out.ReturnsRef = n.ReturnsRef
 		out.PhpDocComment = n.PhpDocComment
-		out.MethodName = ConvertNode(n.MethodName).(*ir.Identifier)
+		out.MethodName = convertNode(state, n.MethodName).(*ir.Identifier)
 		{
 			slice := make([]*ir.Identifier, len(n.Modifiers))
 			for i := range n.Modifiers {
-				slice[i] = ConvertNode(n.Modifiers[i]).(*ir.Identifier)
+				slice[i] = convertNode(state, n.Modifiers[i]).(*ir.Identifier)
 			}
 			out.Modifiers = slice
 		}
 		out.Params = convertNodeSlice(state, n.Params)
-		out.ReturnType = ConvertNode(n.ReturnType)
-		out.Stmt = ConvertNode(n.Stmt)
+		out.ReturnType = convertNode(state, n.ReturnType)
+		out.Stmt = convertNode(state, n.Stmt)
 		return out
 
 	case *stmt.ConstList:
@@ -1298,8 +1274,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
 		out.PhpDocComment = n.PhpDocComment
-		out.ConstantName = ConvertNode(n.ConstantName).(*ir.Identifier)
-		out.Expr = ConvertNode(n.Expr)
+		out.ConstantName = convertNode(state, n.ConstantName).(*ir.Identifier)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *stmt.Continue:
@@ -1309,7 +1285,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ContinueStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *stmt.Declare:
@@ -1320,7 +1296,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
 		out.Consts = convertNodeSlice(state, n.Consts)
-		out.Stmt = ConvertNode(n.Stmt)
+		out.Stmt = convertNode(state, n.Stmt)
 		out.Alt = n.Alt
 		return out
 
@@ -1341,8 +1317,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.DoStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Stmt = ConvertNode(n.Stmt)
-		out.Cond = ConvertNode(n.Cond)
+		out.Stmt = convertNode(state, n.Stmt)
+		out.Cond = convertNode(state, n.Cond)
 		return out
 
 	case *stmt.Echo:
@@ -1362,7 +1338,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ElseStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Stmt = ConvertNode(n.Stmt)
+		out.Stmt = convertNode(state, n.Stmt)
 		out.AltSyntax = n.AltSyntax
 		return out
 
@@ -1373,8 +1349,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ElseIfStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Cond = ConvertNode(n.Cond)
-		out.Stmt = ConvertNode(n.Stmt)
+		out.Cond = convertNode(state, n.Cond)
+		out.Stmt = convertNode(state, n.Stmt)
 		out.AltSyntax = n.AltSyntax
 		out.Merged = n.Merged
 		return out
@@ -1386,7 +1362,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ExpressionStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *stmt.Finally:
@@ -1409,7 +1385,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.Init = convertNodeSlice(state, n.Init)
 		out.Cond = convertNodeSlice(state, n.Cond)
 		out.Loop = convertNodeSlice(state, n.Loop)
-		out.Stmt = ConvertNode(n.Stmt)
+		out.Stmt = convertNode(state, n.Stmt)
 		out.AltSyntax = n.AltSyntax
 		return out
 
@@ -1420,10 +1396,10 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ForeachStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
-		out.Key = ConvertNode(n.Key)
-		out.Variable = ConvertNode(n.Variable)
-		out.Stmt = ConvertNode(n.Stmt)
+		out.Expr = convertNode(state, n.Expr)
+		out.Key = convertNode(state, n.Key)
+		out.Variable = convertNode(state, n.Variable)
+		out.Stmt = convertNode(state, n.Stmt)
 		out.AltSyntax = n.AltSyntax
 		return out
 
@@ -1436,9 +1412,9 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.Position = n.Position
 		out.ReturnsRef = n.ReturnsRef
 		out.PhpDocComment = n.PhpDocComment
-		out.FunctionName = ConvertNode(n.FunctionName).(*ir.Identifier)
+		out.FunctionName = convertNode(state, n.FunctionName).(*ir.Identifier)
 		out.Params = convertNodeSlice(state, n.Params)
-		out.ReturnType = ConvertNode(n.ReturnType)
+		out.ReturnType = convertNode(state, n.ReturnType)
 		out.Stmts = convertNodeSlice(state, n.Stmts)
 		return out
 
@@ -1459,7 +1435,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.GotoStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Label = ConvertNode(n.Label).(*ir.Identifier)
+		out.Label = convertNode(state, n.Label).(*ir.Identifier)
 		return out
 
 	case *stmt.GroupUse:
@@ -1469,8 +1445,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.GroupUseStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.UseType = ConvertNode(n.UseType)
-		out.Prefix = ConvertNode(n.Prefix)
+		out.UseType = convertNode(state, n.UseType)
+		out.Prefix = convertNode(state, n.Prefix)
 		out.UseList = convertNodeSlice(state, n.UseList)
 		return out
 
@@ -1490,10 +1466,10 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.IfStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Cond = ConvertNode(n.Cond)
-		out.Stmt = ConvertNode(n.Stmt)
+		out.Cond = convertNode(state, n.Cond)
+		out.Stmt = convertNode(state, n.Stmt)
 		out.ElseIf = convertNodeSlice(state, n.ElseIf)
-		out.Else = ConvertNode(n.Else)
+		out.Else = convertNode(state, n.Else)
 		out.AltSyntax = n.AltSyntax
 		return out
 
@@ -1515,8 +1491,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
 		out.PhpDocComment = n.PhpDocComment
-		out.InterfaceName = ConvertNode(n.InterfaceName).(*ir.Identifier)
-		out.Extends = ConvertNode(n.Extends).(*ir.InterfaceExtendsStmt)
+		out.InterfaceName = convertNode(state, n.InterfaceName).(*ir.Identifier)
+		out.Extends = convertNode(state, n.Extends).(*ir.InterfaceExtendsStmt)
 		out.Stmts = convertNodeSlice(state, n.Stmts)
 		return out
 
@@ -1537,7 +1513,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.LabelStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.LabelName = ConvertNode(n.LabelName).(*ir.Identifier)
+		out.LabelName = convertNode(state, n.LabelName).(*ir.Identifier)
 		return out
 
 	case *stmt.Namespace:
@@ -1547,7 +1523,10 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.NamespaceStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.NamespaceName = ConvertNode(n.NamespaceName)
+		if n.NamespaceName != nil {
+			out.NamespaceName = convertNode(state, n.NamespaceName).(*ir.Name)
+			state.namespace = out.NamespaceName.Value
+		}
 		out.Stmts = convertNodeSlice(state, n.Stmts)
 		return out
 
@@ -1568,8 +1547,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
 		out.PhpDocComment = n.PhpDocComment
-		out.Variable = ConvertNode(n.Variable).(*ir.SimpleVar)
-		out.Expr = ConvertNode(n.Expr)
+		out.Variable = convertNode(state, n.Variable).(*ir.SimpleVar)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *stmt.PropertyList:
@@ -1582,11 +1561,11 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		{
 			slice := make([]*ir.Identifier, len(n.Modifiers))
 			for i := range n.Modifiers {
-				slice[i] = ConvertNode(n.Modifiers[i]).(*ir.Identifier)
+				slice[i] = convertNode(state, n.Modifiers[i]).(*ir.Identifier)
 			}
 			out.Modifiers = slice
 		}
-		out.Type = ConvertNode(n.Type)
+		out.Type = convertNode(state, n.Type)
 		out.Properties = convertNodeSlice(state, n.Properties)
 		return out
 
@@ -1597,7 +1576,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ReturnStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *stmt.Static:
@@ -1617,8 +1596,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.StaticVarStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Variable = ConvertNode(n.Variable).(*ir.SimpleVar)
-		out.Expr = ConvertNode(n.Expr)
+		out.Variable = convertNode(state, n.Variable).(*ir.SimpleVar)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *stmt.StmtList:
@@ -1638,8 +1617,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.SwitchStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Cond = ConvertNode(n.Cond)
-		out.CaseList = ConvertNode(n.CaseList).(*ir.CaseListStmt)
+		out.Cond = convertNode(state, n.Cond)
+		out.CaseList = convertNode(state, n.CaseList).(*ir.CaseListStmt)
 		out.AltSyntax = n.AltSyntax
 		return out
 
@@ -1650,7 +1629,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.ThrowStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Expr = ConvertNode(n.Expr)
+		out.Expr = convertNode(state, n.Expr)
 		return out
 
 	case *stmt.Trait:
@@ -1661,7 +1640,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
 		out.PhpDocComment = n.PhpDocComment
-		out.TraitName = ConvertNode(n.TraitName).(*ir.Identifier)
+		out.TraitName = convertNode(state, n.TraitName).(*ir.Identifier)
 		out.Stmts = convertNodeSlice(state, n.Stmts)
 		return out
 
@@ -1682,8 +1661,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.TraitMethodRefStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Trait = ConvertNode(n.Trait)
-		out.Method = ConvertNode(n.Method).(*ir.Identifier)
+		out.Trait = convertNode(state, n.Trait)
+		out.Method = convertNode(state, n.Method).(*ir.Identifier)
 		return out
 
 	case *stmt.TraitUse:
@@ -1694,7 +1673,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
 		out.Traits = convertNodeSlice(state, n.Traits)
-		out.TraitAdaptationList = ConvertNode(n.TraitAdaptationList)
+		out.TraitAdaptationList = convertNode(state, n.TraitAdaptationList)
 		return out
 
 	case *stmt.TraitUseAlias:
@@ -1704,9 +1683,9 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.TraitUseAliasStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Ref = ConvertNode(n.Ref)
-		out.Modifier = ConvertNode(n.Modifier)
-		out.Alias = ConvertNode(n.Alias).(*ir.Identifier)
+		out.Ref = convertNode(state, n.Ref)
+		out.Modifier = convertNode(state, n.Modifier)
+		out.Alias = convertNode(state, n.Alias).(*ir.Identifier)
 		return out
 
 	case *stmt.TraitUsePrecedence:
@@ -1716,7 +1695,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.TraitUsePrecedenceStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Ref = ConvertNode(n.Ref)
+		out.Ref = convertNode(state, n.Ref)
 		out.Insteadof = convertNodeSlice(state, n.Insteadof)
 		return out
 
@@ -1729,7 +1708,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out.Position = n.Position
 		out.Stmts = convertNodeSlice(state, n.Stmts)
 		out.Catches = convertNodeSlice(state, n.Catches)
-		out.Finally = ConvertNode(n.Finally)
+		out.Finally = convertNode(state, n.Finally)
 		return out
 
 	case *stmt.Unset:
@@ -1749,9 +1728,9 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.UseStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.UseType = ConvertNode(n.UseType).(*ir.Identifier)
-		out.Use = ConvertNode(n.Use)
-		out.Alias = ConvertNode(n.Alias).(*ir.Identifier)
+		out.UseType = convertNode(state, n.UseType).(*ir.Identifier)
+		out.Use = convertNode(state, n.Use)
+		out.Alias = convertNode(state, n.Alias).(*ir.Identifier)
 		return out
 
 	case *stmt.UseList:
@@ -1761,7 +1740,7 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.UseListStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.UseType = ConvertNode(n.UseType)
+		out.UseType = convertNode(state, n.UseType)
 		out.Uses = convertNodeSlice(state, n.Uses)
 		return out
 
@@ -1772,8 +1751,8 @@ func convertNode(state *convState, n node.Node) ir.Node {
 		out := &ir.WhileStmt{}
 		out.FreeFloating = n.FreeFloating
 		out.Position = n.Position
-		out.Cond = ConvertNode(n.Cond)
-		out.Stmt = ConvertNode(n.Stmt)
+		out.Cond = convertNode(state, n.Cond)
+		out.Stmt = convertNode(state, n.Stmt)
 		out.AltSyntax = n.AltSyntax
 		return out
 	}
@@ -1781,20 +1760,32 @@ func convertNode(state *convState, n node.Node) ir.Node {
 	panic(fmt.Sprintf("unhandled type %T", n))
 }
 
-func convImportExpr(n, e node.Node, fn string) *ir.ImportExpr {
+func convRelativeName(state *convState, n *name.Relative) *ir.Name {
+	value := namePartsToString(n.Parts)
+	if state.namespace != "" {
+		value = `\` + state.namespace + `\` + value
+	}
+	return &ir.Name{
+		FreeFloating: n.FreeFloating,
+		Position:     n.Position,
+		Value:        value,
+	}
+}
+
+func convImportExpr(state *convState, n, e node.Node, fn string) *ir.ImportExpr {
 	return &ir.ImportExpr{
 		FreeFloating: *n.GetFreeFloating(),
 		Position:     n.GetPosition(),
 		Func:         fn,
-		Expr:         ConvertNode(e),
+		Expr:         convertNode(state, e),
 	}
 }
 
-func convCastExpr(n, e node.Node, typ string) *ir.TypeCastExpr {
+func convCastExpr(state *convState, n, e node.Node, typ string) *ir.TypeCastExpr {
 	return &ir.TypeCastExpr{
 		FreeFloating: *n.GetFreeFloating(),
 		Position:     n.GetPosition(),
 		Type:         typ,
-		Expr:         ConvertNode(e),
+		Expr:         convertNode(state, e),
 	}
 }
diff --git a/src/irgen/utils.go b/src/irgen/utils.go
new file mode 100644
index 0000000..8f803fd
--- /dev/null
+++ b/src/irgen/utils.go
@@ -0,0 +1,25 @@
+package irgen
+
+import (
+	"strings"
+
+	"github.com/VKCOM/noverify/src/php/parser/node"
+	"github.com/VKCOM/noverify/src/php/parser/node/name"
+)
+
+func fullyQualifiedToString(n *name.FullyQualified) string {
+	s := make([]string, 1, len(n.Parts)+1)
+	for _, v := range n.Parts {
+		s = append(s, v.(*name.NamePart).Value)
+	}
+	return strings.Join(s, `\`)
+}
+
+// namePartsToString converts slice of *name.NamePart to string
+func namePartsToString(parts []node.Node) string {
+	s := make([]string, 0, len(parts))
+	for _, v := range parts {
+		s = append(s, v.(*name.NamePart).Value)
+	}
+	return strings.Join(s, `\`)
+}
diff --git a/src/langsrv/defs.go b/src/langsrv/defs.go
index dde413d..e8f2cba 100644
--- a/src/langsrv/defs.go
+++ b/src/langsrv/defs.go
@@ -54,16 +54,17 @@ func (d *definitionWalker) EnterNode(n ir.Node) bool {
 		var ok bool
 		var nameStr string
 
-		switch nm := n.Function.(type) {
-		case *ir.Name:
-			nameStr = meta.NameToString(nm)
-			fun, ok = meta.Info.GetFunction(d.st.Namespace + `\` + nameStr)
-			if !ok && d.st.Namespace != "" {
-				fun, ok = meta.Info.GetFunction(`\` + nameStr)
+		if nm, isName := n.Function.(*ir.Name); isName {
+			if nm.IsFullyQualified() {
+				nameStr = nm.Value
+				fun, ok = meta.Info.GetFunction(nameStr)
+			} else {
+				nameStr = nm.Value
+				fun, ok = meta.Info.GetFunction(d.st.Namespace + `\` + nameStr)
+				if !ok && d.st.Namespace != "" {
+					fun, ok = meta.Info.GetFunction(`\` + nameStr)
+				}
 			}
-		case *ir.FullyQualifiedName:
-			nameStr = meta.FullyQualifiedToString(nm)
-			fun, ok = meta.Info.GetFunction(nameStr)
 		}
 
 		if ok {
@@ -250,32 +251,6 @@ func (d *definitionWalker) EnterNode(n ir.Node) bool {
 			return true
 		}
 
-		d.result = append(d.result, vscode.Location{
-			URI: uri.File(c.Pos.Filename),
-			Range: vscode.Range{
-				Start: vscode.Position{Line: int(c.Pos.Line) - 1},
-				End:   vscode.Position{Line: int(c.Pos.Line) - 1},
-			},
-		})
-	case *ir.FullyQualifiedName:
-		pos := ir.GetPosition(n)
-		if d.position > pos.EndPos || d.position < pos.StartPos {
-			return true
-		}
-
-		className, ok := solver.GetClassName(&d.st, n)
-		if !ok {
-			return true
-		}
-
-		c, ok := meta.Info.GetClassOrTrait(className)
-
-		if !ok {
-			return true
-		}
-
-		lintdebug.Send("name:%s , uri:%s", c.Pos.Filename, uri.File(c.Pos.Filename))
-
 		d.result = append(d.result, vscode.Location{
 			URI: uri.File(c.Pos.Filename),
 			Range: vscode.Range{
diff --git a/src/langsrv/langsrv.go b/src/langsrv/langsrv.go
index e4b9cba..edd5bab 100644
--- a/src/langsrv/langsrv.go
+++ b/src/langsrv/langsrv.go
@@ -545,19 +545,19 @@ func getHoverForVariable(v *ir.SimpleVar, sc *meta.Scope, cs *meta.ClassParseSta
 
 func getHoverForFunctionCall(n *ir.FunctionCallExpr, sc *meta.Scope, cs *meta.ClassParseState) string {
 	var fun meta.FuncInfo
-	var ok bool
 	var nameStr string
 
-	switch nm := n.Function.(type) {
-	case *ir.Name:
-		nameStr = meta.NameToString(nm)
-		fun, ok = meta.Info.GetFunction(cs.Namespace + `\` + nameStr)
-		if !ok && cs.Namespace != "" {
-			fun, _ = meta.Info.GetFunction(`\` + nameStr)
+	if nm, ok := n.Function.(*ir.Name); ok {
+		if nm.IsFullyQualified() {
+			nameStr = nm.Value
+			fun, _ = meta.Info.GetFunction(nameStr)
+		} else {
+			nameStr = nm.Value
+			fun, ok = meta.Info.GetFunction(cs.Namespace + `\` + nameStr)
+			if !ok && cs.Namespace != "" {
+				fun, _ = meta.Info.GetFunction(`\` + nameStr)
+			}
 		}
-	case *ir.FullyQualifiedName:
-		nameStr = meta.FullyQualifiedToString(nm)
-		fun, _ = meta.Info.GetFunction(nameStr)
 	}
 
 	return linter.FlagsToString(fun.ExitFlags)
diff --git a/src/langsrv/refs.go b/src/langsrv/refs.go
index 2f96221..c0366ad 100644
--- a/src/langsrv/refs.go
+++ b/src/langsrv/refs.go
@@ -28,9 +28,15 @@ type referencesWalker struct {
 }
 
 func getFunction(st *meta.ClassParseState, n *ir.FunctionCallExpr) (fun meta.FuncInfo, nameStr string, ok bool) {
-	switch nm := n.Function.(type) {
-	case *ir.Name:
-		nameStr = meta.NameToString(nm)
+	nm, ok := n.Function.(*ir.Name)
+	if !ok {
+		return fun, nameStr, ok
+	}
+
+	nameStr = nm.Value
+	if nm.IsFullyQualified() {
+		fun, ok = meta.Info.GetFunction(nameStr)
+	} else {
 		tryStr := st.Namespace + `\` + nameStr
 
 		fun, ok = meta.Info.GetFunction(tryStr)
@@ -45,9 +51,6 @@ func getFunction(st *meta.ClassParseState, n *ir.FunctionCallExpr) (fun meta.Fun
 				return fun, tryStr, true
 			}
 		}
-	case *ir.FullyQualifiedName:
-		nameStr = meta.FullyQualifiedToString(nm)
-		fun, ok = meta.Info.GetFunction(nameStr)
 	}
 
 	return fun, nameStr, ok
diff --git a/src/linter/and_walker.go b/src/linter/and_walker.go
index fd08df2..22188f5 100644
--- a/src/linter/and_walker.go
+++ b/src/linter/and_walker.go
@@ -26,14 +26,14 @@ func (a *andWalker) EnterNode(w ir.Node) (res bool) {
 			break
 		}
 		switch {
-		case len(args) == 2 && meta.NameEquals(nm, `method_exists`):
+		case len(args) == 2 && nm.Value == `method_exists`:
 			obj := args[0].(*ir.Argument).Expr
 			methodName := args[1].(*ir.Argument).Expr
 			lit, ok := methodName.(*ir.String)
 			if ok {
 				a.b.ctx.addCustomMethod(obj, unquote(lit.Value))
 			}
-		case len(args) == 1 && meta.NameEquals(nm, `function_exists`):
+		case len(args) == 1 && nm.Value == `function_exists`:
 			functionName := args[0].(*ir.Argument).Expr
 			lit, ok := functionName.(*ir.String)
 			if ok {
diff --git a/src/linter/block.go b/src/linter/block.go
index f111782..b160cb9 100644
--- a/src/linter/block.go
+++ b/src/linter/block.go
@@ -888,7 +888,7 @@ func (b *BlockWalker) handleStaticCall(e *ir.StaticCallExpr) bool {
 	}
 
 	classNameNode, ok := e.Class.(*ir.Name)
-	parentCall := ok && meta.NameEquals(classNameNode, "parent")
+	parentCall := ok && classNameNode.Value == "parent"
 	if parentCall && methodName == "__construct" {
 		b.callsParentConstructor = true
 	}
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index c112f82..fca6c38 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -268,13 +268,8 @@ func (b *blockLinter) checkStrictCmp(n ir.Node, left, right ir.Node) {
 		if !ok {
 			return false
 		}
-		nm, ok := c.Constant.(*ir.Name)
-		if !ok {
-			return false
-		}
-		return meta.NameEquals(nm, "true") ||
-			meta.NameEquals(nm, "false") ||
-			meta.NameEquals(nm, "null")
+		nm := c.Constant
+		return nm.Value == "true" || nm.Value == "false" || nm.Value == "null"
 	}
 
 	var badNode ir.Node
diff --git a/src/linter/root.go b/src/linter/root.go
index f786b7e..1da2bdf 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -1574,11 +1574,11 @@ func (d *RootWalker) enterFunctionCall(s *ir.FunctionCallExpr) bool {
 		return true
 	}
 
-	if d.ctx.st.Namespace == `\PHPSTORM_META` && meta.NameEquals(nm, `override`) {
+	if d.ctx.st.Namespace == `\PHPSTORM_META` && nm.Value == `override` {
 		return d.handleOverride(s)
 	}
 
-	if !meta.NameEquals(nm, `define`) || len(s.ArgumentList.Arguments) < 2 {
+	if nm.Value != `define` || len(s.ArgumentList.Arguments) < 2 {
 		// TODO: actually we could warn about bogus defines
 		return true
 	}
@@ -1623,8 +1623,8 @@ func (d *RootWalker) handleOverride(s *ir.FunctionCallExpr) bool {
 		return true
 	}
 
-	fnNameNode, ok := fc0.Function.(*ir.FullyQualifiedName)
-	if !ok {
+	fnNameNode, ok := fc0.Function.(*ir.Name)
+	if !ok || !fnNameNode.IsFullyQualified() {
 		return true
 	}
 
@@ -1651,15 +1651,15 @@ func (d *RootWalker) handleOverride(s *ir.FunctionCallExpr) bool {
 
 	var overrideTyp meta.OverrideType
 	switch {
-	case meta.NameEquals(overrideNameNode, `type`):
+	case overrideNameNode.Value == `type`:
 		overrideTyp = meta.OverrideArgType
-	case meta.NameEquals(overrideNameNode, `elementType`):
+	case overrideNameNode.Value == `elementType`:
 		overrideTyp = meta.OverrideElementType
 	default:
 		return true
 	}
 
-	fnName := meta.FullyQualifiedToString(fnNameNode)
+	fnName := fnNameNode.Value
 
 	if d.meta.FunctionOverrides == nil {
 		d.meta.FunctionOverrides = make(meta.FunctionsOverrideMap)
diff --git a/src/linter/types.go b/src/linter/types.go
index 3a4a92b..65a5aad 100644
--- a/src/linter/types.go
+++ b/src/linter/types.go
@@ -224,7 +224,7 @@ func (n typeNormalizer) NormalizeTypes(types []meta.Type) {
 func (n typeNormalizer) string2name(s string) *ir.Name {
 	// TODO: Can avoid extra work by holding 1 tmp name inside
 	// typeNormalizer, since we never need more than one at the time.
-	return meta.StringToName(s)
+	return &ir.Name{Value: s}
 }
 
 func (n typeNormalizer) normalizeType(typ *meta.Type) {
diff --git a/src/meta/metainfo.go b/src/meta/metainfo.go
index 2a15043..2895053 100644
--- a/src/meta/metainfo.go
+++ b/src/meta/metainfo.go
@@ -581,45 +581,12 @@ func IsIndexingComplete() bool {
 	return indexingComplete
 }
 
-func FullyQualifiedToString(n *ir.FullyQualifiedName) string {
-	s := make([]string, 1, len(n.Parts)+1)
-	for _, v := range n.Parts {
-		s = append(s, v.(*ir.NamePart).Value)
-	}
-	return strings.Join(s, `\`)
-}
-
-// NameToString returns string like 'NS\SomeClass' for given name node
-func NameToString(n *ir.Name) string {
-	return NamePartsToString(n.Parts)
-}
-
-// StringToName creates name node that can be analyzed using solver
-func StringToName(nm string) *ir.Name {
-	var parts []ir.Node
-	for _, p := range strings.Split(nm, `\`) {
-		parts = append(parts, &ir.NamePart{Value: p})
-	}
-	return &ir.Name{Parts: parts}
-}
-
-// NamePartsToString converts slice of *name.NamePart to string
-func NamePartsToString(parts []ir.Node) string {
-	s := make([]string, 0, len(parts))
-	for _, v := range parts {
-		s = append(s, v.(*ir.NamePart).Value)
-	}
-	return strings.Join(s, `\`)
-}
-
-// NameNodeToString converts nodes of *name.Name, *name.FullyQualified and *node.Identifier to string.
+// NameNodeToString converts nodes of *name.Name, and *node.Identifier to string.
 // This function is a helper function to aid printing function names, not for actual code analysis.
 func NameNodeToString(n ir.Node) string {
 	switch n := n.(type) {
 	case *ir.Name:
-		return NameToString(n)
-	case *ir.FullyQualifiedName:
-		return FullyQualifiedToString(n)
+		return n.Value
 	case *ir.Identifier:
 		return n.Value
 	case *ir.SimpleVar:
@@ -635,35 +602,10 @@ func NameNodeToString(n ir.Node) string {
 func NameNodeEquals(n ir.Node, s string) bool {
 	switch n := n.(type) {
 	case *ir.Name:
-		return NameEquals(n, s)
+		return n.Value == s
 	case *ir.Identifier:
 		return n.Value == s
-	case *ir.FullyQualifiedName:
-		return FullyQualifiedToString(n) == s
 	default:
 		return false
 	}
 }
-
-func NameEquals(n *ir.Name, s string) bool {
-	if len(n.Parts) != strings.Count(s, `\`)+1 {
-		return false
-	}
-
-	rest := s
-	for i, part := range n.Parts {
-		part := part.(*ir.NamePart)
-		if i == len(n.Parts)-1 {
-			if part.Value != rest {
-				return false
-			}
-		} else {
-			if !strings.HasPrefix(rest, part.Value) {
-				return false
-			}
-			rest = rest[len(part.Value)+len(`\`):]
-		}
-	}
-
-	return true
-}
diff --git a/src/phpgrep/matcher.go b/src/phpgrep/matcher.go
index c2e05d0..195b9cb 100644
--- a/src/phpgrep/matcher.go
+++ b/src/phpgrep/matcher.go
@@ -40,20 +40,6 @@ func (m *matcher) match(state *matcherState, n ir.Node) (data MatchData, ok bool
 	return data, true
 }
 
-func (m *matcher) eqNameParts(xs, ys []ir.Node) bool {
-	if len(xs) != len(ys) {
-		return false
-	}
-	for i, p1 := range xs {
-		p1 := p1.(*ir.NamePart).Value
-		p2 := ys[i].(*ir.NamePart).Value
-		if p1 != p2 {
-			return false
-		}
-	}
-	return true
-}
-
 func (m *matcher) eqNodeSliceNoMeta(state *matcherState, xs, ys []ir.Node) bool {
 	if len(xs) != len(ys) {
 		return false
@@ -595,10 +581,7 @@ func (m *matcher) eqNode(state *matcherState, x, y ir.Node) bool {
 		return ok && m.eqNode(state, x.Constant, y.Constant)
 	case *ir.Name:
 		y, ok := y.(*ir.Name)
-		return ok && m.eqNameParts(x.Parts, y.Parts)
-	case *ir.FullyQualifiedName:
-		y, ok := y.(*ir.FullyQualifiedName)
-		return ok && m.eqNameParts(x.Parts, y.Parts)
+		return ok && x.Value == y.Value
 	case *ir.Identifier:
 		y, ok := y.(*ir.Identifier)
 		return ok && x.Value == y.Value
diff --git a/src/solver/exprtype.go b/src/solver/exprtype.go
index 0a7e2e5..1acd199 100644
--- a/src/solver/exprtype.go
+++ b/src/solver/exprtype.go
@@ -149,26 +149,22 @@ func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 	case *ir.FunctionCallExpr:
 		nm, ok := n.Function.(*ir.Name)
 		if !ok {
-			if nm, ok := n.Function.(*ir.FullyQualifiedName); ok {
-				funcName := meta.FullyQualifiedToString(nm)
-				if strings.Count(funcName, `\`) == 1 {
-					typ, ok := internalFuncType(strings.TrimPrefix(funcName, `\`), sc, cs, n, custom)
-					if ok {
-						return typ
-					}
+			return meta.TypesMap{}
+		}
+		if nm.IsFullyQualified() {
+			if nm.NumParts() == 1 {
+				typ, ok := internalFuncType(strings.TrimPrefix(nm.Value, `\`), sc, cs, n, custom)
+				if ok {
+					return typ
 				}
-				return meta.NewTypesMap(meta.WrapFunctionCall(funcName))
 			}
-			return meta.TypesMap{}
+			return meta.NewTypesMap(meta.WrapFunctionCall(nm.Value))
 		}
-
-		funcName := meta.NameToString(nm)
-		typ, ok := internalFuncType(`\`+funcName, sc, cs, n, custom)
+		typ, ok := internalFuncType(`\`+nm.Value, sc, cs, n, custom)
 		if ok {
 			return typ
 		}
-
-		return meta.NewTypesMap(meta.WrapFunctionCall(cs.Namespace + `\` + funcName))
+		return meta.NewTypesMap(meta.WrapFunctionCall(cs.Namespace + `\` + nm.Value))
 	case *ir.StaticCallExpr:
 		id, ok := n.Call.(*ir.Identifier)
 		if !ok {
@@ -324,25 +320,17 @@ func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 		}
 		return meta.NewTypesMap(meta.WrapClassConstFetch(className, n.ConstantName.Value))
 	case *ir.ConstFetchExpr:
-		nm, ok := n.Constant.(*ir.Name)
-		if !ok {
-			return meta.TypesMap{}
-		}
-
 		// TODO: handle namespaces
-		p := nm.Parts
-		if len(p) == 1 {
-			constName := p[0].(*ir.NamePart).Value
-
-			if constName == "false" || constName == "true" {
-				return meta.PreciseBoolType
-			}
-
-			if constName == "null" {
-				return meta.NewTypesMap("null")
+		nm := n.Constant
+		switch nm.Value {
+		case "false", "true":
+			return meta.PreciseBoolType
+		case "null":
+			return meta.NewTypesMap("null")
+		default:
+			if nm.NumParts() == 0 {
+				return meta.NewTypesMap(meta.WrapConstant(nm.Value))
 			}
-
-			return meta.NewTypesMap(meta.WrapConstant(constName))
 		}
 	case *ir.String, *ir.Encapsed, *ir.Heredoc:
 		return meta.PreciseStringType
diff --git a/src/solver/oop.go b/src/solver/oop.go
index 7fb7038..4cc92e1 100644
--- a/src/solver/oop.go
+++ b/src/solver/oop.go
@@ -14,14 +14,18 @@ import (
 func GetFuncName(cs *meta.ClassParseState, funcNode ir.Node) (funcName string, ok bool) {
 	switch nm := funcNode.(type) {
 	case *ir.Name:
-		nameStr := meta.NameToString(nm)
-		firstPart := nm.Parts[0].(*ir.NamePart).Value
+		if nm.IsFullyQualified() {
+			return nm.Value, true
+		}
+
+		nameStr := nm.Value
+		firstPart := nm.FirstPart()
 		if alias, ok := cs.FunctionUses[firstPart]; ok {
-			if len(nm.Parts) == 1 {
+			if nm.NumParts() == 1 {
 				nameStr = alias
 			} else {
 				// handle situations like 'use NS\Foo; Foo\Bar::doSomething();'
-				nameStr = alias + `\` + meta.NamePartsToString(nm.Parts[1:])
+				nameStr = alias + `\` + nm.RestParts()
 			}
 			return nameStr, true
 		}
@@ -32,9 +36,6 @@ func GetFuncName(cs *meta.ClassParseState, funcNode ir.Node) (funcName string, o
 		}
 		return `\` + nameStr, true
 
-	case *ir.FullyQualifiedName:
-		return meta.FullyQualifiedToString(nm), true
-
 	default:
 		return "", false
 	}
@@ -42,13 +43,9 @@ func GetFuncName(cs *meta.ClassParseState, funcNode ir.Node) (funcName string, o
 
 // GetClassName resolves class name for specified class node (as used in static calls, property fetch, etc)
 func GetClassName(cs *meta.ClassParseState, classNode ir.Node) (className string, ok bool) {
-	if nm, ok := classNode.(*ir.FullyQualifiedName); ok {
-		return meta.FullyQualifiedToString(nm), true
-	}
-
 	var firstPart string
-	var parts []ir.Node
 	var partsCount int
+	var restParts string
 
 	switch nm := classNode.(type) {
 	case *ir.Identifier:
@@ -57,10 +54,12 @@ func GetClassName(cs *meta.ClassParseState, classNode ir.Node) (className string
 		firstPart = nm.Value
 		partsCount = 1 // hack for the later if partsCount == 1
 	case *ir.Name:
-		className = meta.NameToString(nm)
-		firstPart = nm.Parts[0].(*ir.NamePart).Value
-		parts = nm.Parts
-		partsCount = len(parts)
+		if nm.IsFullyQualified() {
+			return nm.Value, true
+		}
+		className = nm.Value
+		firstPart, restParts = nm.HeadTail()
+		partsCount = nm.NumParts()
 	default:
 		return "", false
 	}
@@ -74,7 +73,7 @@ func GetClassName(cs *meta.ClassParseState, classNode ir.Node) (className string
 			className = alias
 		} else {
 			// handle situations like 'use NS\Foo; Foo\Bar::doSomething();'
-			className = alias + `\` + meta.NamePartsToString(parts[1:])
+			className = alias + `\` + restParts
 		}
 	} else {
 		className = cs.Namespace + `\` + className
@@ -85,29 +84,32 @@ func GetClassName(cs *meta.ClassParseState, classNode ir.Node) (className string
 
 // GetConstant searches for specified constant in const fetch.
 func GetConstant(cs *meta.ClassParseState, constNode ir.Node) (constName string, ci meta.ConstantInfo, ok bool) {
-	switch nm := constNode.(type) {
-	case *ir.Name:
-		nameStr := meta.NameToString(nm)
-		nameWithNs := cs.Namespace + `\` + nameStr
-		ci, ok = meta.Info.GetConstant(nameWithNs)
-		if ok {
-			return nameWithNs, ci, true
-		}
+	nm, ok := constNode.(*ir.Name)
+	if !ok {
+		return "", meta.ConstantInfo{}, false
+	}
 
-		if cs.Namespace != "" {
-			nameRootNs := `\` + nameStr
-			ci, ok = meta.Info.GetConstant(nameRootNs)
-			if ok {
-				return nameRootNs, ci, ok
-			}
-		}
-	case *ir.FullyQualifiedName:
-		nameStr := meta.FullyQualifiedToString(nm)
+	nameStr := nm.Value
+	if nm.IsFullyQualified() {
 		ci, ok = meta.Info.GetConstant(nameStr)
 		if ok {
 			return nameStr, ci, true
 		}
 	}
 
+	nameWithNs := cs.Namespace + `\` + nameStr
+	ci, ok = meta.Info.GetConstant(nameWithNs)
+	if ok {
+		return nameWithNs, ci, true
+	}
+
+	if cs.Namespace != "" {
+		nameRootNs := `\` + nameStr
+		ci, ok = meta.Info.GetConstant(nameRootNs)
+		if ok {
+			return nameRootNs, ci, ok
+		}
+	}
+
 	return "", meta.ConstantInfo{}, false
 }
diff --git a/src/solver/side_effects.go b/src/solver/side_effects.go
index 0c8bf15..e5fce01 100644
--- a/src/solver/side_effects.go
+++ b/src/solver/side_effects.go
@@ -100,14 +100,12 @@ func (f *sideEffectsFinder) functionCallIsPure(n *ir.FunctionCallExpr) bool {
 	// We allow referencing builtin funcs even before indexing is completed.
 
 	var funcName string
-	switch nm := n.Function.(type) {
-	case *ir.Name:
-		if len(nm.Parts) == 1 {
-			funcName = `\` + meta.NameToString(nm)
-		}
-	case *ir.FullyQualifiedName:
-		if len(nm.Parts) == 1 {
-			funcName = meta.FullyQualifiedToString(nm)
+	nm, ok := n.Function.(*ir.Name)
+	if ok {
+		if nm.IsFullyQualified() {
+			funcName = nm.Value
+		} else {
+			funcName = `\` + nm.Value
 		}
 	}
 
diff --git a/src/state/update.go b/src/state/update.go
index 80c42ca..5ac5710 100644
--- a/src/state/update.go
+++ b/src/state/update.go
@@ -17,9 +17,8 @@ func EnterNode(st *meta.ClassParseState, n ir.Node) {
 	case *ir.NamespaceStmt:
 		// TODO: handle another namespace syntax:
 		// namespace NS { ... }
-		nm, ok := n.NamespaceName.(*ir.Name)
-		if ok {
-			st.Namespace = `\` + meta.NameToString(nm)
+		if n.NamespaceName != nil {
+			st.Namespace = `\` + n.NamespaceName.Value
 		}
 	case *ir.UseListStmt:
 		if n.UseType == nil {
@@ -73,16 +72,16 @@ func handleUseClass(st *meta.ClassParseState, n *ir.UseStmt) {
 		st.Uses = make(map[string]string)
 	}
 
-	parts := n.Use.(*ir.Name).Parts
+	nm := n.Use.(*ir.Name)
 	var alias string
 
 	if n.Alias != nil {
 		alias = n.Alias.Value
 	} else {
-		alias = parts[len(parts)-1].(*ir.NamePart).Value
+		alias = nm.LastPart()
 	}
 
-	st.Uses[alias] = `\` + meta.NameToString(n.Use.(*ir.Name))
+	st.Uses[alias] = `\` + nm.Value
 }
 
 func handleUseFunction(st *meta.ClassParseState, n *ir.UseStmt) {
@@ -91,16 +90,16 @@ func handleUseFunction(st *meta.ClassParseState, n *ir.UseStmt) {
 		st.FunctionUses = make(map[string]string)
 	}
 
-	parts := n.Use.(*ir.Name).Parts
+	nm := n.Use.(*ir.Name)
 	var alias string
 
 	if n.Alias != nil {
 		alias = n.Alias.Value
 	} else {
-		alias = parts[len(parts)-1].(*ir.NamePart).Value
+		alias = nm.LastPart()
 	}
 
-	st.FunctionUses[alias] = `\` + meta.NameToString(n.Use.(*ir.Name))
+	st.FunctionUses[alias] = `\` + nm.Value
 }
 
 // LeaveNode must be called upon leaving a node to update current state.


Test patch:
diff --git a/src/ir/irfmt/printer_test.go b/src/ir/irfmt/printer_test.go
index 42244b5..dec167f 100644
--- a/src/ir/irfmt/printer_test.go
+++ b/src/ir/irfmt/printer_test.go
@@ -14,21 +14,13 @@ func TestPrintFile(t *testing.T) {
 	p.Print(&ir.Root{
 		Stmts: []ir.Node{
 			&ir.NamespaceStmt{
-				NamespaceName: &ir.Name{
-					Parts: []ir.Node{
-						&ir.NamePart{Value: "Foo"},
-					},
-				},
+				NamespaceName: &ir.Name{Value: "Foo"},
 			},
 			&ir.ClassStmt{
 				Modifiers: []*ir.Identifier{{Value: "abstract"}},
 				ClassName: &ir.Identifier{Value: "Bar"},
 				Extends: &ir.ClassExtendsStmt{
-					ClassName: &ir.Name{
-						Parts: []ir.Node{
-							&ir.NamePart{Value: "Baz"},
-						},
-					},
+					ClassName: &ir.Name{Value: "Baz"},
 				},
 				Stmts: []ir.Node{
 					&ir.ClassMethodStmt{
@@ -116,7 +108,7 @@ func TestPrintParameter(t *testing.T) {
 	p.Print(&ir.Parameter{
 		ByRef:        false,
 		Variadic:     true,
-		VariableType: &ir.FullyQualifiedName{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+		VariableType: &ir.Name{Value: `\Foo`},
 		Variable:     &ir.SimpleVar{Name: "var"},
 		DefaultValue: &ir.String{Value: "'default'"},
 	})
@@ -137,7 +129,7 @@ func TestPrintNullable(t *testing.T) {
 		Expr: &ir.Parameter{
 			ByRef:        false,
 			Variadic:     true,
-			VariableType: &ir.FullyQualifiedName{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+			VariableType: &ir.Name{Value: `\Foo`},
 			Variable:     &ir.SimpleVar{Name: "var"},
 			DefaultValue: &ir.String{Value: "'default'"},
 		},
@@ -186,38 +178,11 @@ func TestPrintArgumentByRef(t *testing.T) {
 	}
 }
 
-// name
-
-func TestPrintNameNamePart(t *testing.T) {
-	o := bytes.NewBufferString("")
-
-	p := NewPrettyPrinter(o, "    ")
-	p.Print(&ir.NamePart{
-		Value: "foo",
-	})
-
-	expected := "foo"
-	actual := o.String()
-
-	if expected != actual {
-		t.Errorf("\nexpected: %s\ngot: %s\n", expected, actual)
-	}
-}
-
 func TestPrintNameName(t *testing.T) {
 	o := bytes.NewBufferString("")
 
 	p := NewPrettyPrinter(o, "    ")
-	p.Print(&ir.Name{
-		Parts: []ir.Node{
-			&ir.NamePart{
-				Value: "Foo",
-			},
-			&ir.NamePart{
-				Value: "Bar",
-			},
-		},
-	})
+	p.Print(&ir.Name{Value: `Foo\Bar`})
 
 	expected := "Foo\\Bar"
 	actual := o.String()
@@ -227,52 +192,6 @@ func TestPrintNameName(t *testing.T) {
 	}
 }
 
-func TestPrintNameFullyQualified(t *testing.T) {
-	o := bytes.NewBufferString("")
-
-	p := NewPrettyPrinter(o, "    ")
-	p.Print(&ir.FullyQualifiedName{
-		Parts: []ir.Node{
-			&ir.NamePart{
-				Value: "Foo",
-			},
-			&ir.NamePart{
-				Value: "Bar",
-			},
-		},
-	})
-
-	expected := "\\Foo\\Bar"
-	actual := o.String()
-
-	if expected != actual {
-		t.Errorf("\nexpected: %s\ngot: %s\n", expected, actual)
-	}
-}
-
-func TestPrintNameRelative(t *testing.T) {
-	o := bytes.NewBufferString("")
-
-	p := NewPrettyPrinter(o, "    ")
-	p.Print(&ir.RelativeName{
-		Parts: []ir.Node{
-			&ir.NamePart{
-				Value: "Foo",
-			},
-			&ir.NamePart{
-				Value: "Bar",
-			},
-		},
-	})
-
-	expected := "namespace\\Foo\\Bar"
-	actual := o.String()
-
-	if expected != actual {
-		t.Errorf("\nexpected: %s\ngot: %s\n", expected, actual)
-	}
-}
-
 // scalar
 
 func TestPrintScalarLNumber(t *testing.T) {
@@ -1401,7 +1320,7 @@ func TestPrintExprClosure(t *testing.T) {
 						&ir.SimpleVar{Name: "b"},
 					},
 				},
-				ReturnType: &ir.FullyQualifiedName{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+				ReturnType: &ir.Name{Value: `\Foo`},
 				Stmts: []ir.Node{
 					&ir.ExpressionStmt{Expr: &ir.SimpleVar{Name: "a"}},
 				},
@@ -1426,7 +1345,7 @@ func TestPrintExprConstFetch(t *testing.T) {
 
 	p := NewPrettyPrinter(o, "    ")
 	p.Print(&ir.ConstFetchExpr{
-		Constant: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "null"}}},
+		Constant: &ir.Name{Value: "null"},
 	})
 
 	expected := "null"
@@ -1572,7 +1491,7 @@ func TestPrintInstanceOf(t *testing.T) {
 	p := NewPrettyPrinter(o, "    ")
 	p.Print(&ir.InstanceOfExpr{
 		Expr:  &ir.SimpleVar{Name: "var"},
-		Class: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+		Class: &ir.Name{Value: "Foo"},
 	})
 
 	expected := `$var instanceof Foo`
@@ -1666,7 +1585,7 @@ func TestPrintNew(t *testing.T) {
 
 	p := NewPrettyPrinter(o, "    ")
 	p.Print(&ir.NewExpr{
-		Class: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+		Class: &ir.Name{Value: "Foo"},
 		ArgumentList: &ir.ArgumentList{
 			Arguments: []ir.Node{
 				&ir.Argument{
@@ -2428,8 +2347,8 @@ func TestPrintStmtCatch(t *testing.T) {
 		Stmts: []ir.Node{
 			&ir.CatchStmt{
 				Types: []ir.Node{
-					&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Exception"}}},
-					&ir.FullyQualifiedName{Parts: []ir.Node{&ir.NamePart{Value: "RuntimeException"}}},
+					&ir.Name{Value: "Exception"},
+					&ir.Name{Value: `\RuntimeException`},
 				},
 				Variable: &ir.SimpleVar{Name: "e"},
 				Stmts: []ir.Node{
@@ -2462,16 +2381,16 @@ func TestPrintStmtClassMethod(t *testing.T) {
 		Params: []ir.Node{
 			&ir.Parameter{
 				ByRef:        true,
-				VariableType: &ir.Nullable{Expr: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "int"}}}},
+				VariableType: &ir.Nullable{Expr: &ir.Name{Value: "int"}},
 				Variable:     &ir.SimpleVar{Name: "a"},
-				DefaultValue: &ir.ConstFetchExpr{Constant: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "null"}}}},
+				DefaultValue: &ir.ConstFetchExpr{Constant: &ir.Name{Value: "null"}},
 			},
 			&ir.Parameter{
 				Variadic: true,
 				Variable: &ir.SimpleVar{Name: "b"},
 			},
 		},
-		ReturnType: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "void"}}},
+		ReturnType: &ir.Name{Value: "void"},
 		Stmt: &ir.StmtList{
 			Stmts: []ir.Node{
 				&ir.ExpressionStmt{Expr: &ir.SimpleVar{Name: "a"}},
@@ -2500,16 +2419,16 @@ func TestPrintStmtAbstractClassMethod(t *testing.T) {
 		Params: []ir.Node{
 			&ir.Parameter{
 				ByRef:        true,
-				VariableType: &ir.Nullable{Expr: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "int"}}}},
+				VariableType: &ir.Nullable{Expr: &ir.Name{Value: "int"}},
 				Variable:     &ir.SimpleVar{Name: "a"},
-				DefaultValue: &ir.ConstFetchExpr{Constant: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "null"}}}},
+				DefaultValue: &ir.ConstFetchExpr{Constant: &ir.Name{Value: "null"}},
 			},
 			&ir.Parameter{
 				Variadic: true,
 				Variable: &ir.SimpleVar{Name: "b"},
 			},
 		},
-		ReturnType: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "void"}}},
+		ReturnType: &ir.Name{Value: "void"},
 		Stmt:       &ir.NopStmt{},
 	})
 
@@ -2531,12 +2450,12 @@ func TestPrintStmtClass(t *testing.T) {
 				Modifiers: []*ir.Identifier{{Value: "abstract"}},
 				ClassName: &ir.Identifier{Value: "Foo"},
 				Extends: &ir.ClassExtendsStmt{
-					ClassName: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Bar"}}},
+					ClassName: &ir.Name{Value: "Bar"},
 				},
 				Implements: &ir.ClassImplementsStmt{
 					InterfaceNames: []ir.Node{
-						&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Baz"}}},
-						&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Quuz"}}},
+						&ir.Name{Value: "Baz"},
+						&ir.Name{Value: "Quuz"},
 					},
 				},
 				Stmts: []ir.Node{
@@ -2586,12 +2505,12 @@ func TestPrintStmtAnonymousClass(t *testing.T) {
 					},
 				},
 				Extends: &ir.ClassExtendsStmt{
-					ClassName: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Bar"}}},
+					ClassName: &ir.Name{Value: "Bar"},
 				},
 				Implements: &ir.ClassImplementsStmt{
 					InterfaceNames: []ir.Node{
-						&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Baz"}}},
-						&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Quuz"}}},
+						&ir.Name{Value: "Baz"},
+						&ir.Name{Value: "Quuz"},
 					},
 				},
 				Stmts: []ir.Node{
@@ -3217,7 +3136,7 @@ func TestPrintStmtFunction(t *testing.T) {
 						Variable: &ir.SimpleVar{Name: "var"},
 					},
 				},
-				ReturnType: &ir.FullyQualifiedName{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+				ReturnType: &ir.Name{Value: `\Foo`},
 				Stmts: []ir.Node{
 					&ir.NopStmt{},
 				},
@@ -3278,14 +3197,14 @@ func TestPrintStmtGroupUse(t *testing.T) {
 	p := NewPrettyPrinter(o, "    ")
 	p.Print(&ir.GroupUseStmt{
 		UseType: &ir.Identifier{Value: "function"},
-		Prefix:  &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+		Prefix:  &ir.Name{Value: "Foo"},
 		UseList: []ir.Node{
 			&ir.UseStmt{
-				Use:   &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Bar"}}},
+				Use:   &ir.Name{Value: "Bar"},
 				Alias: &ir.Identifier{Value: "Baz"},
 			},
 			&ir.UseStmt{
-				Use: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Quuz"}}},
+				Use: &ir.Name{Value: "Quuz"},
 			},
 		},
 	})
@@ -3439,8 +3358,8 @@ func TestPrintInterface(t *testing.T) {
 				InterfaceName: &ir.Identifier{Value: "Foo"},
 				Extends: &ir.InterfaceExtendsStmt{
 					InterfaceNames: []ir.Node{
-						&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Bar"}}},
-						&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Baz"}}},
+						&ir.Name{Value: "Bar"},
+						&ir.Name{Value: "Baz"},
 					},
 				},
 				Stmts: []ir.Node{
@@ -3496,7 +3415,7 @@ func TestPrintNamespace(t *testing.T) {
 
 	p := NewPrettyPrinter(o, "    ")
 	p.Print(&ir.NamespaceStmt{
-		NamespaceName: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+		NamespaceName: &ir.Name{Value: "Foo"},
 	})
 
 	expected := `namespace Foo;`
@@ -3514,7 +3433,7 @@ func TestPrintNamespaceWithStmts(t *testing.T) {
 	p.Print(&ir.StmtList{
 		Stmts: []ir.Node{
 			&ir.NamespaceStmt{
-				NamespaceName: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+				NamespaceName: &ir.Name{Value: "Foo"},
 				Stmts: []ir.Node{
 					&ir.ExpressionStmt{Expr: &ir.SimpleVar{Name: "a"}},
 				},
@@ -3770,7 +3689,7 @@ func TestPrintStmtTraitMethodRef(t *testing.T) {
 
 	p := NewPrettyPrinter(o, "    ")
 	p.Print(&ir.TraitMethodRefStmt{
-		Trait:  &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+		Trait:  &ir.Name{Value: "Foo"},
 		Method: &ir.Identifier{Value: "a"},
 	})
 
@@ -3788,7 +3707,7 @@ func TestPrintStmtTraitUseAlias(t *testing.T) {
 	p := NewPrettyPrinter(o, "    ")
 	p.Print(&ir.TraitUseAliasStmt{
 		Ref: &ir.TraitMethodRefStmt{
-			Trait:  &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+			Trait:  &ir.Name{Value: "Foo"},
 			Method: &ir.Identifier{Value: "a"},
 		},
 		Modifier: &ir.Identifier{Value: "public"},
@@ -3809,12 +3728,12 @@ func TestPrintStmtTraitUsePrecedence(t *testing.T) {
 	p := NewPrettyPrinter(o, "    ")
 	p.Print(&ir.TraitUsePrecedenceStmt{
 		Ref: &ir.TraitMethodRefStmt{
-			Trait:  &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+			Trait:  &ir.Name{Value: "Foo"},
 			Method: &ir.Identifier{Value: "a"},
 		},
 		Insteadof: []ir.Node{
-			&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Bar"}}},
-			&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Baz"}}},
+			&ir.Name{Value: "Bar"},
+			&ir.Name{Value: "Baz"},
 		},
 	})
 
@@ -3832,8 +3751,8 @@ func TestPrintStmtTraitUse(t *testing.T) {
 	p := NewPrettyPrinter(o, "    ")
 	p.Print(&ir.TraitUseStmt{
 		Traits: []ir.Node{
-			&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
-			&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Bar"}}},
+			&ir.Name{Value: "Foo"},
+			&ir.Name{Value: "Bar"},
 		},
 	})
 
@@ -3853,14 +3772,14 @@ func TestPrintStmtTraitAdaptations(t *testing.T) {
 		Stmts: []ir.Node{
 			&ir.TraitUseStmt{
 				Traits: []ir.Node{
-					&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
-					&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Bar"}}},
+					&ir.Name{Value: "Foo"},
+					&ir.Name{Value: "Bar"},
 				},
 				TraitAdaptationList: &ir.TraitAdaptationListStmt{
 					Adaptations: []ir.Node{
 						&ir.TraitUseAliasStmt{
 							Ref: &ir.TraitMethodRefStmt{
-								Trait:  &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+								Trait:  &ir.Name{Value: "Foo"},
 								Method: &ir.Identifier{Value: "a"},
 							},
 							Alias: &ir.Identifier{Value: "b"},
@@ -3936,8 +3855,8 @@ func TestPrintStmtTry(t *testing.T) {
 				Catches: []ir.Node{
 					&ir.CatchStmt{
 						Types: []ir.Node{
-							&ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Exception"}}},
-							&ir.FullyQualifiedName{Parts: []ir.Node{&ir.NamePart{Value: "RuntimeException"}}},
+							&ir.Name{Value: "Exception"},
+							&ir.Name{Value: `\RuntimeException`},
 						},
 						Variable: &ir.SimpleVar{Name: "e"},
 						Stmts: []ir.Node{
@@ -3999,11 +3918,11 @@ func TestPrintStmtUseList(t *testing.T) {
 		UseType: &ir.Identifier{Value: "function"},
 		Uses: []ir.Node{
 			&ir.UseStmt{
-				Use:   &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+				Use:   &ir.Name{Value: "Foo"},
 				Alias: &ir.Identifier{Value: "Bar"},
 			},
 			&ir.UseStmt{
-				Use: &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Baz"}}},
+				Use: &ir.Name{Value: "Baz"},
 			},
 		},
 	})
@@ -4022,7 +3941,7 @@ func TestPrintUse(t *testing.T) {
 	p := NewPrettyPrinter(o, "    ")
 	p.Print(&ir.UseStmt{
 		UseType: &ir.Identifier{Value: "function"},
-		Use:     &ir.Name{Parts: []ir.Node{&ir.NamePart{Value: "Foo"}}},
+		Use:     &ir.Name{Value: "Foo"},
 		Alias:   &ir.Identifier{Value: "Bar"},
 	})
 
diff --git a/src/ir/irutil/nodeSet_test.go b/src/ir/irutil/nodeSet_test.go
index 61e50cf..42cfbcc 100644
--- a/src/ir/irutil/nodeSet_test.go
+++ b/src/ir/irutil/nodeSet_test.go
@@ -2,6 +2,7 @@ package irutil
 
 import (
 	"fmt"
+	"strings"
 	"testing"
 
 	"github.com/VKCOM/noverify/src/ir"
@@ -32,14 +33,8 @@ func BenchmarkNodeSet(b *testing.B) {
 	runBench(1000)
 }
 
-func createFQN(parts ...string) *ir.FullyQualifiedName {
-	nm := &ir.FullyQualifiedName{
-		Parts: make([]ir.Node, len(parts)),
-	}
-	for i, s := range parts {
-		nm.Parts[i] = &ir.NamePart{Value: s}
-	}
-	return nm
+func createFQN(parts ...string) *ir.Name {
+	return &ir.Name{Value: `\` + strings.Join(parts, `\`)}
 }
 
 func createSwitchCases(ncases int) []ir.Node {
diff --git a/src/linter/cache_test.go b/src/linter/cache_test.go
index 3477892..bf5f223 100644
--- a/src/linter/cache_test.go
+++ b/src/linter/cache_test.go
@@ -111,7 +111,7 @@ main();
 		//
 		// If cache encoding changes, there is a very high chance that
 		// encoded data lengh will change as well.
-		wantLen := 4078
+		wantLen := 4066
 		haveLen := buf.Len()
 		if haveLen != wantLen {
 			t.Errorf("cache len mismatch:\nhave: %d\nwant: %d", haveLen, wantLen)
@@ -120,7 +120,7 @@ main();
 		// 2. Check cache "strings" hash.
 		//
 		// It catches new fields in cached types, field renames and encoding of additional named attributes.
-		wantStrings := "5229b096d5a9f0ed73cea28dce9677760e620ed5fd33ccda12355159735e181b09ef152b4d518af886f13b1c7bebd2f1ca274b5485b91d0f23813d05ee646382"
+		wantStrings := "7987e3df6f3073570d28eaac3e197f6a09866d1c384aeb4a093e02aeeb345ade12210a73467ae55a939e22d50e8bdd4d45bae7e149787f2f7327c39d382d3d18"
 		haveStrings := collectCacheStrings(buf.String())
 		if haveStrings != wantStrings {
 			t.Errorf("cache strings mismatch:\nhave: %q\nwant: %q", haveStrings, wantStrings)
diff --git a/src/meta/meta_test.go b/src/meta/meta_test.go
deleted file mode 100644
index 8b7c82c..0000000
--- a/src/meta/meta_test.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package meta
-
-import (
-	"strings"
-	"testing"
-
-	"github.com/VKCOM/noverify/src/ir"
-)
-
-func TestNameEquals(t *testing.T) {
-	tests := []struct {
-		x    string
-		y    string
-		want bool
-	}{
-		{`foo`, `foo`, true},
-		{`foo\bar2`, `foo\bar2`, true},
-		{`foo\bar2\BazBaz`, `foo\bar2\BazBaz`, true},
-		{`foo \ bar`, `foo\bar`, true},
-		{`a\b\c\d`, `a\b\c\d`, true},
-
-		{`a\b`, `a\bb`, false},
-		{`a b`, `a\b`, false},
-		{`a b\`, `a\b`, false},
-		{`first`, ``, false},
-		{`first\second`, `first`, false},
-		{`first`, `first\second`, false},
-		{`first\first`, `first\second`, false},
-		{`first\second`, `firstsecond`, false},
-		{`firstsecond`, `first\second`, false},
-		{`a\b\c`, `a\b\x`, false},
-	}
-
-	makeNameNode := func(s string) *ir.Name {
-		parts := strings.Split(s, `\`)
-		nm := &ir.Name{Parts: make([]ir.Node, len(parts))}
-		for i := range parts {
-			nm.Parts[i] = &ir.NamePart{Value: strings.TrimSpace(parts[i])}
-		}
-		return nm
-	}
-
-	for _, test := range tests {
-		x := makeNameNode(test.x)
-		y := test.y
-		have := NameEquals(x, y)
-		if have != test.want {
-			t.Errorf("NameEquals(%q, %q): have %v, want %v",
-				test.x, test.y, have, test.want)
-		}
-	}
-}
-
-func BenchmarkNameEquals(b *testing.B) {
-	var theName1 = &ir.Name{
-		Parts: []ir.Node{
-			&ir.NamePart{Value: `method_exists`},
-		},
-	}
-	var theName3 = &ir.Name{
-		Parts: []ir.Node{
-			&ir.NamePart{Value: `a`},
-			&ir.NamePart{Value: `b`},
-			&ir.NamePart{Value: `c`},
-		},
-	}
-
-	b.Run("1part", func(b *testing.B) {
-		for i := 0; i < b.N; i++ {
-			_ = NameEquals(theName1, `method_exists`)
-		}
-	})
-	b.Run("3parts", func(b *testing.B) {
-		for i := 0; i < b.N; i++ {
-			_ = NameEquals(theName3, `a\b\c`)
-		}
-	})
-}
diff --git a/src/tests/bench/bench_test.go b/src/tests/bench/bench_test.go
index 140c6ec..ce7491e 100644
--- a/src/tests/bench/bench_test.go
+++ b/src/tests/bench/bench_test.go
@@ -1,7 +1,6 @@
 package bench_test
 
 import (
-	"strings"
 	"testing"
 
 	"github.com/VKCOM/noverify/src/ir"
@@ -36,12 +35,7 @@ class Foo {
 	meta.SetIndexingComplete(true)
 
 	newName := func(nm string) *ir.Name {
-		stringParts := strings.Split(nm, `\`)
-		nameParts := make([]ir.Node, len(stringParts))
-		for i, p := range stringParts {
-			nameParts[i] = &ir.NamePart{Value: p}
-		}
-		return &ir.Name{Parts: nameParts}
+		return &ir.Name{Value: nm}
 	}
 	f1call := &ir.FunctionCallExpr{Function: newName("f1")}
 	f3call := &ir.FunctionCallExpr{Function: newName("f3")}
diff --git a/src/tests/checkers/relative_name_test.go b/src/tests/checkers/relative_name_test.go
new file mode 100644
index 0000000..fa90e2a
--- /dev/null
+++ b/src/tests/checkers/relative_name_test.go
@@ -0,0 +1,33 @@
+package checkers_test
+
+import (
+	"testing"
+
+	"github.com/VKCOM/noverify/src/linttest"
+)
+
+func TestRelativeName1(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.AddFile(`<?php
+namespace A;
+
+class ClassA {}
+
+$_ = new namespace\B\ClassB();
+$_ = new namespace\ClassA();
+`)
+
+	test.AddFile(`<?php
+namespace A\B;
+
+class ClassB {}
+
+$_ = new namespace\ClassB();
+`)
+
+	test.AddFile(`<?php
+$_ = new namespace\A\B\ClassB();
+`)
+
+	test.RunAndMatch()
+}