Repository: VKCOM/noverify
Pull request #960 (2021-04-01 15:14:42)

linter,meta: move all types-related code to `types` package
There are a lot of changes since all `meta.TypesMap` things were
renamed to `types.Map` (and there more examples like this).

One benefit is that if we decouple types and meta it
will be easier to further decompose indexdata types and
meta types (they both would need to use `types`, so we
more it to a separate package to avoid circular deps).


Patch:
diff --git a/src/linter/and_walker.go b/src/linter/and_walker.go
index 3ecf7a1..535eedf 100644
--- a/src/linter/and_walker.go
+++ b/src/linter/and_walker.go
@@ -4,6 +4,7 @@ import (
 	"github.com/VKCOM/noverify/src/ir"
 	"github.com/VKCOM/noverify/src/meta"
 	"github.com/VKCOM/noverify/src/solver"
+	"github.com/VKCOM/noverify/src/types"
 )
 
 // andWalker walks if conditions and adds isset/!empty/instanceof variables
@@ -55,7 +56,7 @@ func (a *andWalker) EnterNode(w ir.Node) (res bool) {
 
 			switch v := varNode.(type) {
 			case *ir.SimpleVar:
-				a.b.addVar(v, meta.NewTypesMap("isset_$"+v.Name), "isset", meta.VarAlwaysDefined)
+				a.b.addVar(v, types.NewMap("isset_$"+v.Name), "isset", meta.VarAlwaysDefined)
 				a.varsToDelete = append(a.varsToDelete, v)
 			case *ir.Var:
 				a.b.handleVariable(v.Expr)
@@ -63,7 +64,7 @@ func (a *andWalker) EnterNode(w ir.Node) (res bool) {
 				if !ok {
 					continue
 				}
-				a.b.addVar(v, meta.NewTypesMap("isset_$$"+vv.Name), "isset", meta.VarAlwaysDefined)
+				a.b.addVar(v, types.NewMap("isset_$$"+vv.Name), "isset", meta.VarAlwaysDefined)
 				a.varsToDelete = append(a.varsToDelete, v)
 			}
 		}
@@ -72,11 +73,11 @@ func (a *andWalker) EnterNode(w ir.Node) (res bool) {
 		if className, ok := solver.GetClassName(a.b.r.ctx.st, n.Class); ok {
 			switch v := n.Expr.(type) {
 			case *ir.Var, *ir.SimpleVar:
-				a.b.ctx.sc.AddVar(v, meta.NewTypesMap(className), "instanceof", 0)
+				a.b.ctx.sc.AddVar(v, types.NewMap(className), "instanceof", 0)
 			default:
 				a.b.ctx.customTypes = append(a.b.ctx.customTypes, solver.CustomType{
 					Node: n.Expr,
-					Typ:  meta.NewTypesMap(className),
+					Typ:  types.NewMap(className),
 				})
 			}
 			// TODO: actually this needs to be present inside if body only
@@ -99,7 +100,7 @@ func (a *andWalker) EnterNode(w ir.Node) (res bool) {
 		if a.b.ctx.sc.HaveVar(v) {
 			break
 		}
-		a.b.addVar(v, meta.NewTypesMap("isset_$"+v.Name), "!empty", meta.VarAlwaysDefined)
+		a.b.addVar(v, types.NewMap("isset_$"+v.Name), "!empty", meta.VarAlwaysDefined)
 		a.varsToDelete = append(a.varsToDelete, v)
 	}
 
diff --git a/src/linter/block.go b/src/linter/block.go
index f7e3ccf..54251d8 100644
--- a/src/linter/block.go
+++ b/src/linter/block.go
@@ -11,6 +11,7 @@ import (
 	"github.com/VKCOM/noverify/src/meta"
 	"github.com/VKCOM/noverify/src/phpdoc"
 	"github.com/VKCOM/noverify/src/solver"
+	"github.com/VKCOM/noverify/src/types"
 )
 
 // loopKind describes current looping statement context.
@@ -56,7 +57,7 @@ const (
 // arrayKeyType is an universal PHP array key type.
 // In PHP, all array keys are converted to either int or string,
 // so we can always assume that `int|string` is good enough.
-var arrayKeyType = meta.NewTypesMap("int|string").Immutable()
+var arrayKeyType = types.NewMap("int|string").Immutable()
 
 // blockWalker is used to process function/method contents.
 type blockWalker struct {
@@ -65,7 +66,7 @@ type blockWalker struct {
 	linter blockLinter
 
 	// inferred return types if any
-	returnTypes meta.TypesMap
+	returnTypes types.Map
 
 	r *rootWalker
 
@@ -198,11 +199,11 @@ func (b *blockWalker) handleCommentToken(n ir.Node, t *token.Token) {
 			continue
 		}
 
-		types, warning := typesFromPHPDoc(&b.r.ctx, p.Type)
+		typeList, warning := typesFromPHPDoc(&b.r.ctx, p.Type)
 		if warning != "" {
 			b.r.Report(n, LevelWarning, "phpdocType", "%s on line %d", warning, p.Line())
 		}
-		m := newTypesMap(&b.r.ctx, types)
+		m := newTypesMap(&b.r.ctx, typeList)
 		b.ctx.sc.AddVarFromPHPDoc(strings.TrimPrefix(p.Var, "$"), m, "@var")
 	}
 }
@@ -342,7 +343,7 @@ func (b *blockWalker) EnterNode(n ir.Node) (res bool) {
 			res = false
 		}
 	case *ir.ClosureExpr:
-		var typ meta.TypesMap
+		var typ types.Map
 		isInstance := b.ctx.sc.IsInInstanceMethod()
 		if isInstance {
 			typ, _ = b.ctx.sc.GetVarNameType("this")
@@ -413,7 +414,7 @@ func (b *blockWalker) handleAndCheckGlobalStmt(s *ir.GlobalStmt) {
 			continue
 		}
 
-		b.addVar(v, meta.NewTypesMap(meta.WrapGlobal(nm)), "global", meta.VarAlwaysDefined)
+		b.addVar(v, types.NewMap(types.WrapGlobal(nm)), "global", meta.VarAlwaysDefined)
 		if b.path.Conditional() {
 			b.addNonLocalVar(v, varCondGlobal)
 		} else {
@@ -474,7 +475,7 @@ func (b *blockWalker) addNonLocalVar(v ir.Node, kind variableKind) {
 }
 
 // replaceVar must be used to track assignments to conrete var nodes if they are available
-func (b *blockWalker) replaceVar(v ir.Node, typ meta.TypesMap, reason string, flags meta.VarFlags) {
+func (b *blockWalker) replaceVar(v ir.Node, typ types.Map, reason string, flags meta.VarFlags) {
 	b.ctx.sc.ReplaceVar(v, typ, reason, flags)
 	sv, ok := v.(*ir.SimpleVar)
 	if !ok {
@@ -503,13 +504,13 @@ func (b *blockWalker) untrackVarName(nm string) {
 	delete(b.unusedParams, nm)
 }
 
-func (b *blockWalker) addVarName(n ir.Node, nm string, typ meta.TypesMap, reason string, flags meta.VarFlags) {
+func (b *blockWalker) addVarName(n ir.Node, nm string, typ types.Map, reason string, flags meta.VarFlags) {
 	b.ctx.sc.AddVarName(nm, typ, reason, flags)
 	b.trackVarName(n, nm)
 }
 
 // addVar must be used to track assignments to conrete var nodes if they are available
-func (b *blockWalker) addVar(v ir.Node, typ meta.TypesMap, reason string, flags meta.VarFlags) {
+func (b *blockWalker) addVar(v ir.Node, typ types.Map, reason string, flags meta.VarFlags) {
 	b.ctx.sc.AddVar(v, typ, reason, flags)
 	sv, ok := v.(*ir.SimpleVar)
 	if !ok {
@@ -652,7 +653,7 @@ func (b *blockWalker) handleTry(s *ir.TryStmt) bool {
 
 	contexts = append(contexts, ctx)
 
-	varTypes := make(map[string]meta.TypesMap, b.ctx.sc.Len())
+	varTypes := make(map[string]types.Map, b.ctx.sc.Len())
 	defCounts := make(map[string]int, b.ctx.sc.Len())
 
 	for _, ctx := range contexts {
@@ -660,7 +661,7 @@ func (b *blockWalker) handleTry(s *ir.TryStmt) bool {
 			continue
 		}
 
-		ctx.sc.Iterate(func(nm string, typ meta.TypesMap, flags meta.VarFlags) {
+		ctx.sc.Iterate(func(nm string, typ types.Map, flags meta.VarFlags) {
 			varTypes[nm] = varTypes[nm].Append(typ)
 			if flags.IsAlwaysDefined() {
 				defCounts[nm]++
@@ -675,7 +676,7 @@ func (b *blockWalker) handleTry(s *ir.TryStmt) bool {
 	}
 
 	if finallyCtx != nil {
-		finallyCtx.sc.Iterate(func(nm string, typ meta.TypesMap, flags meta.VarFlags) {
+		finallyCtx.sc.Iterate(func(nm string, typ types.Map, flags meta.VarFlags) {
 			flags.SetAlwaysDefined(finallyCtx.exitFlags == 0)
 			b.ctx.sc.AddVarName(nm, typ, "finally", flags)
 		})
@@ -692,15 +693,15 @@ func (b *blockWalker) handleTry(s *ir.TryStmt) bool {
 }
 
 func (b *blockWalker) handleCatch(s *ir.CatchStmt) {
-	types := make([]meta.Type, 0, len(s.Types))
+	typeList := make([]types.Type, 0, len(s.Types))
 	for _, t := range s.Types {
 		typ, ok := solver.GetClassName(b.r.ctx.st, t)
 		if !ok {
 			continue
 		}
-		types = append(types, meta.Type{Elem: typ})
+		typeList = append(typeList, types.Type{Elem: typ})
 	}
-	m := meta.NewTypesMapFromTypes(types)
+	m := types.NewMapFromTypes(typeList)
 
 	b.handleVariableNode(s.Variable, m, "catch")
 
@@ -752,19 +753,19 @@ func (b *blockWalker) handleCallArgs(args []ir.Node, fn meta.FuncInfo) {
 			a.Walk(b)
 		case *ir.ArrayDimFetchExpr:
 			if ref {
-				b.handleAndCheckDimFetchLValue(a, "call_with_ref", meta.MixedType)
+				b.handleAndCheckDimFetchLValue(a, "call_with_ref", types.MixedType)
 				break
 			}
 			a.Walk(b)
 		case *ir.ClosureExpr:
-			var typ meta.TypesMap
+			var typ types.Map
 			isInstance := b.ctx.sc.IsInInstanceMethod()
 			if isInstance {
 				typ, _ = b.ctx.sc.GetVarNameType("this")
 			}
 
 			// find the types for the arguments of the function that contains this closure
-			var funcArgTypes []meta.TypesMap
+			var funcArgTypes []types.Map
 			for _, arg := range args {
 				tp := solver.ExprTypeLocal(b.ctx.sc, b.r.ctx.st, arg.(*ir.Argument).Expr)
 				funcArgTypes = append(funcArgTypes, tp)
@@ -923,16 +924,16 @@ func (b *blockWalker) handleForeach(s *ir.ForeachStmt) bool {
 	if s.Stmt != nil {
 		ctx := b.withNewContext(func() {
 			solver.ExprTypeLocalCustom(b.ctx.sc, b.r.ctx.st, s.Expr, b.ctx.customTypes).Iterate(func(typ string) {
-				b.handleVariableNode(s.Variable, meta.NewTypesMap(meta.WrapElemOf(typ)), "foreach_value")
+				b.handleVariableNode(s.Variable, types.NewMap(types.WrapElemOf(typ)), "foreach_value")
 			})
 
 			b.handleVariableNode(s.Key, arrayKeyType, "foreach_key")
 			if list, ok := s.Variable.(*ir.ListExpr); ok {
 				for _, item := range list.Items {
-					b.handleVariableNode(item.Val, meta.TypesMap{}, "foreach_value")
+					b.handleVariableNode(item.Val, types.Map{}, "foreach_value")
 				}
 			} else {
-				b.handleVariableNode(s.Variable, meta.TypesMap{}, "foreach_value")
+				b.handleVariableNode(s.Variable, types.Map{}, "foreach_value")
 			}
 
 			b.ctx.innermostLoop = loopFor
@@ -1013,14 +1014,14 @@ func (b *blockWalker) enterArrowFunction(fun *ir.ArrowFunctionExpr) bool {
 	return false
 }
 
-func (b *blockWalker) enterClosure(fun *ir.ClosureExpr, haveThis bool, thisType meta.TypesMap, closureSolver *solver.ClosureCallerInfo) bool {
+func (b *blockWalker) enterClosure(fun *ir.ClosureExpr, haveThis bool, thisType types.Map, closureSolver *solver.ClosureCallerInfo) bool {
 	sc := meta.NewScope()
 	sc.SetInClosure(true)
 
 	if haveThis {
 		sc.AddVarName("this", thisType, "closure inside instance method", meta.VarAlwaysDefined)
 	} else {
-		sc.AddVarName("this", meta.NewTypesMap("possibly_late_bound"), "possibly late bound $this", meta.VarAlwaysDefined)
+		sc.AddVarName("this", types.NewMap("possibly_late_bound"), "possibly late bound $this", meta.VarAlwaysDefined)
 	}
 
 	doc := b.r.parsePHPDoc(fun, fun.Doc, fun.Params)
@@ -1064,7 +1065,7 @@ func (b *blockWalker) enterClosure(fun *ir.ClosureExpr, haveThis bool, thisType
 }
 
 func (b *blockWalker) maybeAddAllVars(sc *meta.Scope, reason string) {
-	sc.Iterate(func(varName string, typ meta.TypesMap, flags meta.VarFlags) {
+	sc.Iterate(func(varName string, typ types.Map, flags meta.VarFlags) {
 		flags &^= meta.VarAlwaysDefined
 		b.ctx.sc.AddVarName(varName, typ, reason, flags)
 	})
@@ -1170,7 +1171,7 @@ func (b *blockWalker) handleVariable(v ir.Node) bool {
 
 	if !have && !b.inArrowFunction {
 		b.r.reportUndefinedVariable(v, b.ctx.sc.MaybeHaveVar(v))
-		b.ctx.sc.AddVar(v, meta.NewTypesMap("undefined"), "undefined", meta.VarAlwaysDefined)
+		b.ctx.sc.AddVar(v, types.NewMap("undefined"), "undefined", meta.VarAlwaysDefined)
 	}
 
 	// In case the required variable was not found in the current scope,
@@ -1207,7 +1208,7 @@ func (b *blockWalker) handleVariable(v ir.Node) bool {
 
 		if varNotFound {
 			b.r.reportUndefinedVariable(v, varMaybeNotDefined)
-			b.ctx.sc.AddVar(v, meta.NewTypesMap("undefined"), "undefined", meta.VarAlwaysDefined)
+			b.ctx.sc.AddVar(v, types.NewMap("undefined"), "undefined", meta.VarAlwaysDefined)
 		}
 	}
 
@@ -1305,7 +1306,7 @@ func (b *blockWalker) handleIf(s *ir.IfStmt) bool {
 
 	b.propagateFlagsFromBranches(contexts, linksCount)
 
-	varTypes := make(map[string]meta.TypesMap, b.ctx.sc.Len())
+	varTypes := make(map[string]types.Map, b.ctx.sc.Len())
 	defCounts := make(map[string]int, b.ctx.sc.Len())
 
 	for _, ctx := range contexts {
@@ -1313,7 +1314,7 @@ func (b *blockWalker) handleIf(s *ir.IfStmt) bool {
 			continue
 		}
 
-		ctx.sc.Iterate(func(nm string, typ meta.TypesMap, flags meta.VarFlags) {
+		ctx.sc.Iterate(func(nm string, typ types.Map, flags meta.VarFlags) {
 			varTypes[nm] = varTypes[nm].Append(typ)
 			if flags.IsAlwaysDefined() {
 				defCounts[nm]++
@@ -1441,7 +1442,7 @@ func (b *blockWalker) handleSwitch(s *ir.SwitchStmt) bool {
 		b.ctx.exitFlags |= prematureExitFlags
 	}
 
-	varTypes := make(map[string]meta.TypesMap, b.ctx.sc.Len())
+	varTypes := make(map[string]types.Map, b.ctx.sc.Len())
 	defCounts := make(map[string]int, b.ctx.sc.Len())
 
 	for _, ctx := range contexts {
@@ -1452,7 +1453,7 @@ func (b *blockWalker) handleSwitch(s *ir.SwitchStmt) bool {
 			continue
 		}
 
-		ctx.sc.Iterate(func(nm string, typ meta.TypesMap, flags meta.VarFlags) {
+		ctx.sc.Iterate(func(nm string, typ types.Map, flags meta.VarFlags) {
 			varTypes[nm] = varTypes[nm].Append(typ)
 			if flags.IsAlwaysDefined() {
 				defCounts[nm]++
@@ -1472,16 +1473,16 @@ func (b *blockWalker) handleSwitch(s *ir.SwitchStmt) bool {
 // if $a was previously undefined,
 // handle case when doing assignment like '$a[] = 4;'
 // or call to function that accepts like exec("command", $a)
-func (b *blockWalker) handleAndCheckDimFetchLValue(e *ir.ArrayDimFetchExpr, reason string, typ meta.TypesMap) {
+func (b *blockWalker) handleAndCheckDimFetchLValue(e *ir.ArrayDimFetchExpr, reason string, typ types.Map) {
 	b.checkArrayDimFetch(e)
 
 	switch v := e.Variable.(type) {
 	case *ir.Var, *ir.SimpleVar:
-		arrTyp := typ.Map(meta.WrapArrayOf)
+		arrTyp := typ.Map(types.WrapArrayOf)
 		b.addVar(v, arrTyp, reason, meta.VarAlwaysDefined)
 		b.handleVariable(v)
 	case *ir.ArrayDimFetchExpr:
-		b.handleAndCheckDimFetchLValue(v, reason, meta.MixedType)
+		b.handleAndCheckDimFetchLValue(v, reason, types.MixedType)
 	default:
 		// probably not assignable?
 		v.Walk(b)
@@ -1506,7 +1507,7 @@ func (b *blockWalker) checkArrayDimFetch(s *ir.ArrayDimFetchExpr) {
 
 	typ.Iterate(func(t string) {
 		// FullyQualified class name will have "\" in the beginning
-		if meta.IsClassType(t) {
+		if types.IsClassType(t) {
 			maybeHaveClasses = true
 
 			if !haveArrayAccess && solver.Implements(b.r.ctx.st.Info, t, `\ArrayAccess`) {
@@ -1524,7 +1525,7 @@ func (b *blockWalker) checkArrayDimFetch(s *ir.ArrayDimFetchExpr) {
 func (b *blockWalker) handleAssignReference(a *ir.AssignReference) bool {
 	switch v := a.Variable.(type) {
 	case *ir.ArrayDimFetchExpr:
-		b.handleAndCheckDimFetchLValue(v, "assign_array", meta.MixedType)
+		b.handleAndCheckDimFetchLValue(v, "assign_array", types.MixedType)
 		a.Expr.Walk(b)
 		return false
 	case *ir.Var, *ir.SimpleVar:
@@ -1533,7 +1534,7 @@ func (b *blockWalker) handleAssignReference(a *ir.AssignReference) bool {
 	case *ir.ListExpr:
 		// TODO: figure out whether this case is reachable.
 		for _, item := range v.Items {
-			b.handleVariableNode(item.Val, meta.NewTypesMap("unknown_from_list"), "assign")
+			b.handleVariableNode(item.Val, types.NewMap("unknown_from_list"), "assign")
 		}
 	default:
 		a.Variable.Walk(b)
@@ -1566,9 +1567,9 @@ func (b *blockWalker) handleAssignShapeToList(items []*ir.ArrayItemExpr, info me
 			prop, ok = info.Properties[fmt.Sprint(i)]
 		}
 
-		var tp meta.TypesMap
+		var tp types.Map
 		if !ok {
-			tp = meta.NewTypesMap("unknown_from_list")
+			tp = types.NewMap("unknown_from_list")
 		} else {
 			tp = prop.Typ
 		}
@@ -1584,17 +1585,17 @@ func (b *blockWalker) handleAssignList(list *ir.ListExpr, rhs ir.Node) {
 	// Hint: only const (literal) size hints work for this.
 	// Hint: check the compiler output to see whether elemTypes "escape" or not.
 	//
-	// We store meta.Type instead of string to avoid the need to do strings.Join
+	// We store types.Type instead of string to avoid the need to do strings.Join
 	// when we want to create a TypesMap.
-	var elemTypes []meta.Type
+	var elemTypes []types.Type
 	var shapeType string
 	typ.Iterate(func(typ string) {
 		switch {
-		case meta.IsShapeType(typ):
+		case types.IsShapeType(typ):
 			shapeType = typ
-		case meta.IsArrayType(typ):
+		case types.IsArrayType(typ):
 			elemType := strings.TrimSuffix(typ, "[]")
-			elemTypes = append(elemTypes, meta.Type{Elem: elemType})
+			elemTypes = append(elemTypes, types.Type{Elem: elemType})
 		}
 	})
 
@@ -1609,7 +1610,7 @@ func (b *blockWalker) handleAssignList(list *ir.ListExpr, rhs ir.Node) {
 
 	// Try to handle it as an array assignment.
 	if len(elemTypes) != 0 {
-		elemTypeMap := meta.NewTypesMapFromTypes(elemTypes).Immutable()
+		elemTypeMap := types.NewMapFromTypes(elemTypes).Immutable()
 		for _, item := range list.Items {
 			b.handleVariableNode(item.Val, elemTypeMap, "list-assign")
 		}
@@ -1621,7 +1622,7 @@ func (b *blockWalker) handleAssignList(list *ir.ListExpr, rhs ir.Node) {
 	// TODO: shouldn't it be a mixed type? I would prefer a "mixed" type here
 	// and "unknown from list" reason.
 	for _, item := range list.Items {
-		b.handleVariableNode(item.Val, meta.NewTypesMap("unknown_from_list"), "list-assign")
+		b.handleVariableNode(item.Val, types.NewMap("unknown_from_list"), "list-assign")
 	}
 }
 
@@ -1713,7 +1714,7 @@ func (b *blockWalker) handleAssign(a *ir.Assign) bool {
 }
 
 func (b *blockWalker) handleAssignOp(assign ir.Node) {
-	var typ meta.TypesMap
+	var typ types.Map
 	var v ir.Node
 
 	switch assign := assign.(type) {
@@ -1748,13 +1749,13 @@ func (b *blockWalker) handleAssignOp(assign ir.Node) {
 
 	case *ir.AssignConcat:
 		v = assign.Variable
-		typ = meta.PreciseStringType
+		typ = types.PreciseStringType
 	case *ir.AssignShiftLeft:
 		v = assign.Variable
-		typ = meta.PreciseIntType
+		typ = types.PreciseIntType
 	case *ir.AssignShiftRight:
 		v = assign.Variable
-		typ = meta.PreciseIntType
+		typ = types.PreciseIntType
 	case *ir.AssignCoalesce:
 		e := &ir.CoalesceExpr{
 			Left:  assign.Variable,
@@ -1797,7 +1798,7 @@ func (b *blockWalker) flushUnused() {
 	}
 }
 
-func (b *blockWalker) handleVariableNode(n ir.Node, typ meta.TypesMap, what string) {
+func (b *blockWalker) handleVariableNode(n ir.Node, typ types.Map, what string) {
 	if n == nil {
 		return
 	}
@@ -1878,6 +1879,6 @@ func (b *blockWalker) sideEffectFree(n ir.Node) bool {
 	return solver.SideEffectFree(b.ctx.sc, b.r.ctx.st, b.ctx.customTypes, n)
 }
 
-func (b *blockWalker) exprType(n ir.Node) meta.TypesMap {
+func (b *blockWalker) exprType(n ir.Node) types.Map {
 	return solver.ExprTypeCustom(b.ctx.sc, b.r.ctx.st, n, b.ctx.customTypes)
 }
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index 3372458..53d1771 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -11,6 +11,7 @@ import (
 	"github.com/VKCOM/noverify/src/meta"
 	"github.com/VKCOM/noverify/src/quickfix"
 	"github.com/VKCOM/noverify/src/solver"
+	"github.com/VKCOM/noverify/src/types"
 )
 
 type blockLinter struct {
@@ -818,7 +819,7 @@ func (b *blockLinter) checkArrayKeyExistsCall(e *ir.FunctionCallExpr) {
 	typ := solver.ExprType(b.walker.ctx.sc, b.walker.r.ctx.st, e.Arg(1).Expr)
 
 	onlyObjects := !typ.Find(func(typ string) bool {
-		return !meta.IsClassType(typ)
+		return !types.IsClassType(typ)
 	})
 
 	if onlyObjects {
@@ -1144,8 +1145,8 @@ func (b *blockLinter) checkFormatString(e *ir.FunctionCallExpr, arg *ir.Argument
 	}
 }
 
-func (b *blockLinter) isArrayType(typ meta.TypesMap) bool {
-	return typ.Len() == 1 && typ.Find(meta.IsArrayType)
+func (b *blockLinter) isArrayType(typ types.Map) bool {
+	return typ.Len() == 1 && typ.Find(types.IsArrayType)
 }
 
 func (b *blockLinter) classParseState() *meta.ClassParseState {
diff --git a/src/linter/cache.go b/src/linter/cache.go
index b29c8f5..20a8857 100644
--- a/src/linter/cache.go
+++ b/src/linter/cache.go
@@ -43,6 +43,7 @@ import (
 //     45 - added Mixins field to meta.ClassInfo
 //     46 - changed the way of inferring the return type of functions and methods
 //     47 - forced cache version invalidation due to the #921
+//     48 - renamed meta.TypesMap to types.Map; this affects gob encoding
 const cacheVersion = 47
 
 var (
diff --git a/src/linter/custom.go b/src/linter/custom.go
index 29655f2..d884f12 100644
--- a/src/linter/custom.go
+++ b/src/linter/custom.go
@@ -12,6 +12,7 @@ import (
 	"github.com/VKCOM/noverify/src/phpdoc"
 	"github.com/VKCOM/noverify/src/quickfix"
 	"github.com/VKCOM/noverify/src/rules"
+	"github.com/VKCOM/noverify/src/types"
 	"github.com/VKCOM/noverify/src/workspace"
 )
 
@@ -187,7 +188,7 @@ func (ctx *BlockContext) NodePath() irutil.NodePath {
 }
 
 // ExprType resolves the type of e expression node.
-func (ctx *BlockContext) ExprType(e ir.Node) meta.TypesMap {
+func (ctx *BlockContext) ExprType(e ir.Node) types.Map {
 	return ctx.w.exprType(e)
 }
 
diff --git a/src/linter/root.go b/src/linter/root.go
index 4803d4b..293c7e3 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -23,6 +23,7 @@ import (
 	"github.com/VKCOM/noverify/src/rules"
 	"github.com/VKCOM/noverify/src/solver"
 	"github.com/VKCOM/noverify/src/state"
+	"github.com/VKCOM/noverify/src/types"
 	"github.com/VKCOM/noverify/src/workspace"
 )
 
@@ -71,7 +72,7 @@ type rootWalker struct {
 
 type phpDocParamEl struct {
 	optional bool
-	typ      meta.TypesMap
+	typ      types.Map
 }
 
 type phpDocParamsMap map[string]phpDocParamEl
@@ -510,7 +511,7 @@ func (d *rootWalker) reportUndefinedVariable(v ir.Node, maybeHave bool) {
 }
 
 type handleFuncResult struct {
-	returnTypes            meta.TypesMap
+	returnTypes            types.Map
 	prematureExitFlags     int
 	callsParentConstructor bool
 }
@@ -558,7 +559,7 @@ func (d *rootWalker) handleFuncStmts(params []meta.FuncParam, uses, stmts []ir.N
 
 		typ, ok := sc.GetVarNameType(v.Name)
 		if !ok {
-			typ = meta.NewTypesMap("TODO_use_var")
+			typ = types.NewMap("TODO_use_var")
 		}
 
 		sc.AddVar(v, typ, "use", meta.VarAlwaysDefined)
@@ -601,9 +602,9 @@ func (d *rootWalker) handleFuncStmts(params []meta.FuncParam, uses, stmts []ir.N
 
 	switch {
 	case b.bareReturn && b.returnsValue:
-		b.returnTypes = meta.MergeTypeMaps(b.returnTypes, meta.NullType)
+		b.returnTypes = types.MergeMaps(b.returnTypes, types.NullType)
 	case b.returnTypes.IsEmpty() && b.returnsValue:
-		b.returnTypes = meta.MixedType
+		b.returnTypes = types.MixedType
 	}
 
 	return handleFuncResult{
@@ -819,7 +820,7 @@ func (d *rootWalker) enterPropertyList(pl *ir.PropertyListStmt) bool {
 		}
 	}
 
-	var specifiedType meta.TypesMap
+	var specifiedType types.Map
 	if typ, ok := d.parseTypeNode(pl.Type); ok {
 		specifiedType = typ
 	}
@@ -916,11 +917,11 @@ func (d *rootWalker) enterClassMethod(meth *ir.ClassMethodStmt) bool {
 
 	sc := meta.NewScope()
 	if !modif.static {
-		sc.AddVarName("this", meta.NewTypesMap(d.ctx.st.CurrentClass).Immutable(), "instance method", meta.VarAlwaysDefined)
+		sc.AddVarName("this", types.NewMap(d.ctx.st.CurrentClass).Immutable(), "instance method", meta.VarAlwaysDefined)
 		sc.SetInInstanceMethod(true)
 	}
 
-	var hintReturnType meta.TypesMap
+	var hintReturnType types.Map
 	if typ, ok := d.parseTypeNode(meth.ReturnType); ok {
 		hintReturnType = typ
 	}
@@ -976,8 +977,8 @@ func (d *rootWalker) enterClassMethod(meth *ir.ClassMethodStmt) bool {
 			}
 
 			res := make(map[string]struct{})
-			res[meta.WrapBaseMethodParam(i, d.ctx.st.CurrentClass, nm)] = struct{}{}
-			params[i].Typ = meta.NewTypesMapFromMap(res)
+			res[types.WrapBaseMethodParam(i, d.ctx.st.CurrentClass, nm)] = struct{}{}
+			params[i].Typ = types.NewMapFromMap(res)
 			sc.AddVarName(p.Name, params[i].Typ, "param", meta.VarAlwaysDefined)
 		}
 	}
@@ -1043,7 +1044,7 @@ func (d *rootWalker) reportPhpdocErrors(n ir.Node, errs phpdocErrors) {
 	}
 }
 
-func (d *rootWalker) parsePHPDocVar(n ir.Node, doc phpdoc.Comment) (m meta.TypesMap) {
+func (d *rootWalker) parsePHPDocVar(n ir.Node, doc phpdoc.Comment) (m types.Map) {
 	if phpdoc.IsSuspicious([]byte(doc.Raw)) {
 		d.Report(n, LevelWarning, "phpdocLint", "multiline phpdoc comment should start with /**, not /*")
 	}
@@ -1052,11 +1053,11 @@ func (d *rootWalker) parsePHPDocVar(n ir.Node, doc phpdoc.Comment) (m meta.Types
 		d.checkPHPDocRef(n, part)
 		part, ok := part.(*phpdoc.TypeVarCommentPart)
 		if ok && part.Name() == "var" {
-			types, warning := typesFromPHPDoc(&d.ctx, part.Type)
+			typeList, warning := typesFromPHPDoc(&d.ctx, part.Type)
 			if warning != "" {
 				d.Report(n, LevelNotice, "phpdocType", "%s on line %d", warning, part.Line())
 			}
-			m = newTypesMap(&d.ctx, types)
+			m = newTypesMap(&d.ctx, typeList)
 		}
 	}
 
@@ -1064,7 +1065,7 @@ func (d *rootWalker) parsePHPDocVar(n ir.Node, doc phpdoc.Comment) (m meta.Types
 }
 
 type phpDocParseResult struct {
-	returnType meta.TypesMap
+	returnType types.Map
 	types      phpDocParamsMap
 	info       meta.PhpDocInfo
 	errs       phpdocErrors
@@ -1259,11 +1260,11 @@ func (d *rootWalker) parsePHPDoc(n ir.Node, doc phpdoc.Comment, actualParams []i
 
 		if part.Name() == "return" {
 			part := part.(*phpdoc.TypeCommentPart)
-			types, warning := typesFromPHPDoc(&d.ctx, part.Type)
+			typeList, warning := typesFromPHPDoc(&d.ctx, part.Type)
 			if warning != "" {
 				result.errs.pushType("%s on line %d", warning, part.Line())
 			}
-			result.returnType = newTypesMap(&d.ctx, types)
+			result.returnType = newTypesMap(&d.ctx, typeList)
 			continue
 		}
 
@@ -1303,11 +1304,11 @@ func (d *rootWalker) parsePHPDoc(n ir.Node, doc phpdoc.Comment, actualParams []i
 		curParam++
 
 		var param phpDocParamEl
-		types, warning := typesFromPHPDoc(&d.ctx, part.Type)
+		typeList, warning := typesFromPHPDoc(&d.ctx, part.Type)
 		if warning != "" {
 			result.errs.pushType("%s on line %d", warning, part.Line())
 		}
-		param.typ = newTypesMap(&d.ctx, types)
+		param.typ = newTypesMap(&d.ctx, typeList)
 		param.typ.Iterate(func(t string) {
 			if t == "void" {
 				result.errs.pushType("void is not a valid type for input parameter")
@@ -1324,27 +1325,27 @@ func (d *rootWalker) parsePHPDoc(n ir.Node, doc phpdoc.Comment, actualParams []i
 }
 
 // parse type info, e.g. "string" in "someFunc() : string { ... }"
-func (d *rootWalker) parseTypeNode(n ir.Node) (typ meta.TypesMap, ok bool) {
+func (d *rootWalker) parseTypeNode(n ir.Node) (typ types.Map, ok bool) {
 	if n == nil {
-		return meta.TypesMap{}, false
+		return types.Map{}, false
 	}
 
-	types := typesFromNode(n)
-	tm := newTypesMap(&d.ctx, types)
+	typeList := typesFromNode(n)
+	tm := newTypesMap(&d.ctx, typeList)
 	return tm, !tm.IsEmpty()
 }
 
 // callbackParamByIndex returns the description of the parameter for the function by its index.
-func (d *rootWalker) callbackParamByIndex(param ir.Node, argType meta.TypesMap) meta.FuncParam {
+func (d *rootWalker) callbackParamByIndex(param ir.Node, argType types.Map) meta.FuncParam {
 	p := param.(*ir.Parameter)
 	v := p.Variable
 
-	var typ meta.TypesMap
+	var typ types.Map
 	tp, ok := d.parseTypeNode(p.VariableType)
 	if ok {
 		typ = tp
 	} else {
-		typ = argType.Map(meta.WrapElemOf)
+		typ = argType.Map(types.WrapElemOf)
 	}
 
 	arg := meta.FuncParam{
@@ -1378,11 +1379,11 @@ func (d *rootWalker) parseFuncArgsForCallback(params []ir.Node, sc *meta.Scope,
 	for i, param := range params {
 		p := param.(*ir.Parameter)
 		v := p.Variable
-		var typ meta.TypesMap
+		var typ types.Map
 		if i < len(args) {
 			typ = args[i].Typ
 		} else {
-			typ = meta.MixedType
+			typ = types.MixedType
 		}
 
 		sc.AddVarName(v.Name, typ, "param", meta.VarAlwaysDefined)
@@ -1432,7 +1433,7 @@ func (d *rootWalker) parseFuncArgs(params []ir.Node, parTypes phpDocParamsMap, s
 		}
 
 		if p.Variadic {
-			typ = typ.Map(meta.WrapArrayOf)
+			typ = typ.Map(types.WrapArrayOf)
 		}
 
 		sc.AddVarName(v.Name, typ, "param", meta.VarAlwaysDefined)
@@ -1493,7 +1494,7 @@ func (d *rootWalker) enterFunction(fun *ir.FunctionStmt) bool {
 		d.Report(fun.FunctionName, LevelNotice, "complexity", "Too big function: more than %d lines", maxFunctionLines)
 	}
 
-	var hintReturnType meta.TypesMap
+	var hintReturnType types.Map
 	if typ, ok := d.parseTypeNode(fun.ReturnType); ok {
 		hintReturnType = typ
 	}
@@ -1568,7 +1569,7 @@ func (d *rootWalker) checkTypeHintClassCaseFunctionParam(p *ir.Parameter) {
 	}
 
 	typ.Iterate(func(typ string) {
-		if meta.IsClassType(typ) {
+		if types.IsClassType(typ) {
 			className := typ
 
 			class, ok := d.metaInfo().GetClass(className)
diff --git a/src/linter/root_context.go b/src/linter/root_context.go
index 88191d5..1581069 100644
--- a/src/linter/root_context.go
+++ b/src/linter/root_context.go
@@ -7,6 +7,7 @@ import (
 	"github.com/VKCOM/noverify/src/baseline"
 	"github.com/VKCOM/noverify/src/meta"
 	"github.com/VKCOM/noverify/src/quickfix"
+	"github.com/VKCOM/noverify/src/types"
 )
 
 type rootContext struct {
@@ -48,7 +49,7 @@ func (ctx *rootContext) generateShapeName() string {
 	return fmt.Sprintf(`\shape$%s$%d$`, ctx.st.CurrentFile, len(ctx.shapes))
 }
 
-func newTypesMap(ctx *rootContext, types []meta.Type) meta.TypesMap {
-	ctx.typeNormalizer.NormalizeTypes(types)
-	return meta.NewTypesMapFromTypes(types)
+func newTypesMap(ctx *rootContext, typeList []types.Type) types.Map {
+	ctx.typeNormalizer.NormalizeTypes(typeList)
+	return types.NewMapFromTypes(typeList)
 }
diff --git a/src/linter/types.go b/src/linter/types.go
index b7706e7..04100a9 100644
--- a/src/linter/types.go
+++ b/src/linter/types.go
@@ -7,6 +7,7 @@ import (
 	"github.com/VKCOM/noverify/src/meta"
 	"github.com/VKCOM/noverify/src/phpdoc"
 	"github.com/VKCOM/noverify/src/solver"
+	"github.com/VKCOM/noverify/src/types"
 )
 
 // TODO: reflect source line in shape names.
@@ -15,7 +16,7 @@ type warningString string
 
 type shapeTypeProp struct {
 	key   string
-	types []meta.Type
+	types []types.Type
 }
 
 type shapeTypeInfo struct {
@@ -27,13 +28,13 @@ type shapeTypeInfo struct {
 //
 // No normalization is performed, but some PHPDoc-specific types
 // are simplified to be compatible with our type system.
-func typesFromPHPDoc(ctx *rootContext, typ phpdoc.Type) ([]meta.Type, warningString) {
+func typesFromPHPDoc(ctx *rootContext, typ phpdoc.Type) ([]types.Type, warningString) {
 	conv := phpdocTypeConverter{ctx: ctx}
-	types := conv.mapType(typ.Expr)
+	result := conv.mapType(typ.Expr)
 	if conv.nullable {
-		types = append(types, meta.Type{Elem: "null"})
+		result = append(result, types.Type{Elem: "null"})
 	}
-	return types, conv.warning
+	return result, conv.warning
 }
 
 type phpdocTypeConverter struct {
@@ -42,12 +43,12 @@ type phpdocTypeConverter struct {
 	nullable bool
 }
 
-func (conv *phpdocTypeConverter) mapType(e phpdoc.TypeExpr) []meta.Type {
+func (conv *phpdocTypeConverter) mapType(e phpdoc.TypeExpr) []types.Type {
 	switch e.Kind {
 	case phpdoc.ExprInvalid, phpdoc.ExprUnknown:
 		if e.Value == "-" {
 			conv.warn(`expected a type, found '-'; if you want to express 'any' type, use 'mixed'`)
-			return []meta.Type{{Elem: "mixed"}}
+			return []types.Type{{Elem: "mixed"}}
 		}
 
 	case phpdoc.ExprParen:
@@ -57,10 +58,10 @@ func (conv *phpdocTypeConverter) mapType(e phpdoc.TypeExpr) []meta.Type {
 		if suggest, ok := typeAliases[e.Value]; ok {
 			conv.warn(fmt.Sprintf("use %s type instead of %s", suggest, e.Value))
 		}
-		return []meta.Type{{Elem: e.Value}}
+		return []types.Type{{Elem: e.Value}}
 
 	case phpdoc.ExprMemberType:
-		return []meta.Type{{Elem: "mixed"}}
+		return []types.Type{{Elem: "mixed"}}
 
 	case phpdoc.ExprGeneric:
 		typ := e.Args[0]
@@ -96,11 +97,11 @@ func (conv *phpdocTypeConverter) mapType(e phpdoc.TypeExpr) []meta.Type {
 		return conv.mapArrayType(e.Args[0])
 
 	case phpdoc.ExprUnion:
-		types := make([]meta.Type, 0, len(e.Args))
+		typeList := make([]types.Type, 0, len(e.Args))
 		for _, a := range e.Args {
-			types = append(types, conv.mapType(a)...)
+			typeList = append(typeList, conv.mapType(a)...)
 		}
-		return types
+		return typeList
 
 	case phpdoc.ExprOptional:
 		// Due to the fact that the optional keys for shape are processed in the mapShapeType
@@ -113,18 +114,18 @@ func (conv *phpdocTypeConverter) mapType(e phpdoc.TypeExpr) []meta.Type {
 	return nil
 }
 
-func (conv *phpdocTypeConverter) mapArrayType(elem phpdoc.TypeExpr) []meta.Type {
-	types := conv.mapType(elem)
-	if len(types) == 0 {
-		return []meta.Type{{Elem: "mixed", Dims: 1}}
+func (conv *phpdocTypeConverter) mapArrayType(elem phpdoc.TypeExpr) []types.Type {
+	typeList := conv.mapType(elem)
+	if len(typeList) == 0 {
+		return []types.Type{{Elem: "mixed", Dims: 1}}
 	}
-	for i := range types {
-		types[i].Dims++
+	for i := range typeList {
+		typeList[i].Dims++
 	}
-	return types
+	return typeList
 }
 
-func (conv *phpdocTypeConverter) mapShapeType(params []phpdoc.TypeExpr) []meta.Type {
+func (conv *phpdocTypeConverter) mapShapeType(params []phpdoc.TypeExpr) []types.Type {
 	props := make([]shapeTypeProp, 0, len(params))
 	for i, p := range params {
 		if p.Value == "*" || p.Value == "..." {
@@ -146,11 +147,11 @@ func (conv *phpdocTypeConverter) mapShapeType(params []phpdoc.TypeExpr) []meta.T
 			conv.warn(fmt.Sprintf("invalid shape key: %s", key.Value))
 			continue
 		}
-		types := conv.mapType(typeExpr)
+		typeList := conv.mapType(typeExpr)
 
 		// We need to resolve the class names as well as static,
 		// self and $this here for it to work properly.
-		for i, typ := range types {
+		for i, typ := range typeList {
 			if _, ok := typeAliases[typ.Elem]; ok {
 				continue
 			}
@@ -168,10 +169,10 @@ func (conv *phpdocTypeConverter) mapShapeType(params []phpdoc.TypeExpr) []meta.T
 				continue
 			}
 
-			types[i].Elem = className
+			typeList[i].Elem = className
 		}
 		if conv.nullable {
-			types = append(types, meta.Type{
+			typeList = append(typeList, types.Type{
 				Elem: "null",
 				Dims: 0,
 			})
@@ -179,7 +180,7 @@ func (conv *phpdocTypeConverter) mapShapeType(params []phpdoc.TypeExpr) []meta.T
 
 		props = append(props, shapeTypeProp{
 			key:   key.Value,
-			types: types,
+			types: typeList,
 		})
 	}
 
@@ -189,11 +190,11 @@ func (conv *phpdocTypeConverter) mapShapeType(params []phpdoc.TypeExpr) []meta.T
 	}
 	conv.ctx.shapes = append(conv.ctx.shapes, shape)
 
-	return []meta.Type{{Elem: shape.name}}
+	return []types.Type{{Elem: shape.name}}
 }
 
-func (conv *phpdocTypeConverter) mapTupleType(params []phpdoc.TypeExpr) []meta.Type {
-	types := make([]phpdoc.TypeExpr, 0, len(params))
+func (conv *phpdocTypeConverter) mapTupleType(params []phpdoc.TypeExpr) []types.Type {
+	typeList := make([]phpdoc.TypeExpr, 0, len(params))
 	for i, p := range params {
 		if p.Value == "*" || p.Value == "..." {
 			continue
@@ -210,10 +211,10 @@ func (conv *phpdocTypeConverter) mapTupleType(params []phpdoc.TypeExpr) []meta.T
 			Kind: phpdoc.ExprKeyVal,
 			Args: args,
 		}
-		types = append(types, typeExpr)
+		typeList = append(typeList, typeExpr)
 	}
 
-	return conv.mapShapeType(types)
+	return conv.mapShapeType(typeList)
 }
 
 func (conv *phpdocTypeConverter) warn(msg string) {
@@ -225,23 +226,23 @@ func (conv *phpdocTypeConverter) warn(msg string) {
 // typesFromNode converts type hint node to meta types.
 //
 // No normalization is performed.
-func typesFromNode(typeNode ir.Node) []meta.Type {
+func typesFromNode(typeNode ir.Node) []types.Type {
 	n := typeNode
 
-	var results []meta.Type
+	var results []types.Type
 	if nullable, ok := typeNode.(*ir.Nullable); ok {
 		n = nullable.Expr
-		results = make([]meta.Type, 0, 2)
-		results = append(results, meta.Type{Elem: "null"})
+		results = make([]types.Type, 0, 2)
+		results = append(results, types.Type{Elem: "null"})
 	} else {
-		results = make([]meta.Type, 0, 1)
+		results = make([]types.Type, 0, 1)
 	}
 
 	// There is a trick here.
 	// Unlike with phpdoc types, having `integer` here
 	// means that we need to force it to be interpreted as
 	// `\integer`, not as `int`. This is why we prepend `\`.
-	typ := meta.Type{Elem: meta.NameNodeToString(n)}
+	typ := types.Type{Elem: meta.NameNodeToString(n)}
 	if _, isAlias := typeAliases[typ.Elem]; isAlias {
 		typ.Elem = `\` + typ.Elem
 	}
@@ -256,9 +257,9 @@ type typeNormalizer struct {
 	kphp bool
 }
 
-func (n typeNormalizer) NormalizeTypes(types []meta.Type) {
-	for i := range types {
-		n.normalizeType(&types[i])
+func (n typeNormalizer) NormalizeTypes(typeList []types.Type) {
+	for i := range typeList {
+		n.normalizeType(&typeList[i])
 	}
 }
 
@@ -268,7 +269,7 @@ func (n typeNormalizer) string2name(s string) *ir.Name {
 	return &ir.Name{Value: s}
 }
 
-func (n typeNormalizer) normalizeType(typ *meta.Type) {
+func (n typeNormalizer) normalizeType(typ *types.Type) {
 	if trivialTypes[typ.Elem] {
 		return
 	}
diff --git a/src/linter/utils.go b/src/linter/utils.go
index 8293a1d..d7c46cb 100644
--- a/src/linter/utils.go
+++ b/src/linter/utils.go
@@ -12,6 +12,7 @@ import (
 	"github.com/VKCOM/noverify/src/phpdoc"
 	"github.com/VKCOM/noverify/src/rules"
 	"github.com/VKCOM/noverify/src/solver"
+	"github.com/VKCOM/noverify/src/types"
 )
 
 // FlagsToString is designed for debugging flags.
@@ -86,21 +87,21 @@ func varToString(v ir.Node) string {
 	}
 }
 
-func typesMapToTypeExpr(p *phpdoc.TypeParser, m meta.TypesMap) phpdoc.Type {
+func typesMapToTypeExpr(p *phpdoc.TypeParser, m types.Map) phpdoc.Type {
 	typeString := m.String()
 	return p.Parse(typeString)
 }
 
 // mergeTypeMaps merges two typesmaps without losing information.
 // So merging int[] and array will give int[], and Foo and object will give Foo.
-func mergeTypeMaps(left meta.TypesMap, right meta.TypesMap) meta.TypesMap {
+func mergeTypeMaps(left types.Map, right types.Map) types.Map {
 	var hasAtLeastOneArray bool
 	var hasAtLeastOneClass bool
 
 	merged := make(map[string]struct{}, left.Len()+right.Len())
 
 	left.Iterate(func(typ string) {
-		if typ[0] == meta.WArrayOf {
+		if typ[0] == types.WArrayOf {
 			hasAtLeastOneArray = true
 		}
 		if typ[0] == '\\' {
@@ -110,7 +111,7 @@ func mergeTypeMaps(left meta.TypesMap, right meta.TypesMap) meta.TypesMap {
 	})
 
 	right.Iterate(func(typ string) {
-		if typ[0] == meta.WArrayOf && meta.UnwrapArrayOf(typ) == "mixed" && hasAtLeastOneArray {
+		if typ[0] == types.WArrayOf && types.UnwrapArrayOf(typ) == "mixed" && hasAtLeastOneArray {
 			return
 		}
 		if typ == "object" && hasAtLeastOneClass {
@@ -119,7 +120,7 @@ func mergeTypeMaps(left meta.TypesMap, right meta.TypesMap) meta.TypesMap {
 		merged[typ] = struct{}{}
 	})
 
-	return meta.NewTypesMapFromMap(merged)
+	return types.NewMapFromMap(merged)
 }
 
 // functionReturnType returns the return type of a function over computed types
@@ -133,8 +134,8 @@ func mergeTypeMaps(left meta.TypesMap, right meta.TypesMap) meta.TypesMap {
 //
 // 3. If there is no @return annotation and type hint, then the return type is equal to
 //    the union of the types that are returned from the function by return.
-func functionReturnType(phpdocReturnType meta.TypesMap, hintReturnType meta.TypesMap, actualReturnTypes meta.TypesMap) meta.TypesMap {
-	var returnTypes meta.TypesMap
+func functionReturnType(phpdocReturnType types.Map, hintReturnType types.Map, actualReturnTypes types.Map) types.Map {
+	var returnTypes types.Map
 	if !phpdocReturnType.IsEmpty() || !hintReturnType.IsEmpty() {
 		returnTypes = mergeTypeMaps(phpdocReturnType, hintReturnType)
 	} else {
@@ -142,7 +143,7 @@ func functionReturnType(phpdocReturnType meta.TypesMap, hintReturnType meta.Type
 	}
 
 	if returnTypes.IsEmpty() {
-		returnTypes = meta.VoidType
+		returnTypes = types.VoidType
 	}
 
 	return returnTypes
@@ -189,7 +190,7 @@ type methodCallInfo struct {
 	methodName       string
 	className        string
 	info             meta.FuncInfo
-	methodCallerType meta.TypesMap
+	methodCallerType types.Map
 	isFound          bool
 	isMagic          bool
 	canAnalyze       bool
@@ -300,7 +301,7 @@ func resolveStaticMethodCall(st *meta.ClassParseState, e *ir.StaticCallExpr) sta
 type propertyFetchInfo struct {
 	className         string
 	info              meta.PropertyInfo
-	propertyFetchType meta.TypesMap
+	propertyFetchType types.Map
 	propertyNode      *ir.Identifier
 	isFound           bool
 	isMagic           bool
diff --git a/src/linter/worker.go b/src/linter/worker.go
index 4c2e0e8..8fec45c 100644
--- a/src/linter/worker.go
+++ b/src/linter/worker.go
@@ -26,6 +26,7 @@ import (
 	"github.com/VKCOM/noverify/src/lintdebug"
 	"github.com/VKCOM/noverify/src/meta"
 	"github.com/VKCOM/noverify/src/quickfix"
+	"github.com/VKCOM/noverify/src/types"
 	"github.com/VKCOM/noverify/src/workspace"
 )
 
@@ -297,8 +298,8 @@ func (w *Worker) analyzeFile(file *workspace.File, rootNode *ir.Root) (*rootWalk
 // do not need to call it yourself.
 func analyzeFileRootLevel(rootNode ir.Node, d *rootWalker) {
 	sc := meta.NewScope()
-	sc.AddVarName("argv", meta.NewTypesMap("string[]"), "predefined", meta.VarAlwaysDefined)
-	sc.AddVarName("argc", meta.NewTypesMap("int"), "predefined", meta.VarAlwaysDefined)
+	sc.AddVarName("argv", types.NewMap("string[]"), "predefined", meta.VarAlwaysDefined)
+	sc.AddVarName("argc", types.NewMap("int"), "predefined", meta.VarAlwaysDefined)
 
 	b := newBlockWalker(d, sc)
 	b.ignoreFunctionBodies = true
diff --git a/src/meta/info.go b/src/meta/info.go
index 7dd4520..28c2aee 100644
--- a/src/meta/info.go
+++ b/src/meta/info.go
@@ -3,6 +3,8 @@ package meta
 import (
 	"strings"
 	"sync"
+
+	"github.com/VKCOM/noverify/src/types"
 )
 
 // Info contains meta information for all classes, functions, etc.
@@ -149,19 +151,19 @@ func (i *Info) InitKphpStubs() {
 		Name:         `\array_first_element`,
 		Params:       []FuncParam{{Name: "el"}},
 		MinParamsCnt: 1,
-		Typ:          NewTypesMap("mixed"),
+		Typ:          types.NewMap("mixed"),
 	}
 	i.internalFunctions.H[`\array_last_element`] = FuncInfo{
 		Name:         `\array_last_element`,
 		Params:       []FuncParam{{Name: "el"}},
 		MinParamsCnt: 1,
-		Typ:          NewTypesMap("mixed"),
+		Typ:          types.NewMap("mixed"),
 	}
 	i.internalFunctions.H[`\instance_deserialize`] = FuncInfo{
 		Name:         `\instance_deserialize`,
 		Params:       []FuncParam{{Name: "packed_str"}, {Name: "type_of_instance"}},
 		MinParamsCnt: 2,
-		Typ:          NewTypesMap("object|null"),
+		Typ:          types.NewMap("object|null"),
 	}
 
 	i.internalFunctionOverrides[`\array_first_element`] = FuncInfoOverride{
@@ -329,7 +331,7 @@ func (i *Info) AddConstantsNonLocked(filename string, m ConstantsMap) {
 }
 
 func (i *Info) AddToGlobalScopeNonLocked(filename string, sc *Scope) {
-	sc.Iterate(func(nm string, typ TypesMap, flags VarFlags) {
+	sc.Iterate(func(nm string, typ types.Map, flags VarFlags) {
 		i.AddVarName(nm, typ, "global", flags)
 	})
 }
diff --git a/src/meta/metainfo.go b/src/meta/metainfo.go
index 9f7bc69..ea8655b 100644
--- a/src/meta/metainfo.go
+++ b/src/meta/metainfo.go
@@ -1,11 +1,45 @@
 package meta
 
 import (
-	"strings"
-
 	"github.com/VKCOM/noverify/src/ir"
+	"github.com/VKCOM/noverify/src/types"
+)
+
+type FuncFlags uint8
+
+const (
+	FuncStatic FuncFlags = 1 << iota
+	FuncPure
+	FuncAbstract
+	FuncFinal
+)
+
+type PhpDocInfo struct {
+	Deprecated      bool
+	DeprecationNote string
+}
+
+type AccessLevel int
+
+const (
+	Public AccessLevel = iota
+	Protected
+	Private
 )
 
+func (l AccessLevel) String() string {
+	switch l {
+	case Public:
+		return "public"
+	case Protected:
+		return "protected"
+	case Private:
+		return "private"
+	}
+
+	panic("Invalid access level")
+}
+
 // PerFile contains all meta information about the specified file
 type PerFile struct {
 	Traits    ClassesMap
@@ -17,29 +51,15 @@ type PerFile struct {
 type FuncParam struct {
 	IsRef bool
 	Name  string
-	Typ   TypesMap
+	Typ   types.Map
 }
 
-type PhpDocInfo struct {
-	Deprecated      bool
-	DeprecationNote string
-}
-
-type FuncFlags uint8
-
-const (
-	FuncStatic FuncFlags = 1 << iota
-	FuncPure
-	FuncAbstract
-	FuncFinal
-)
-
 type FuncInfo struct {
 	Pos          ElementPosition
 	Name         string
 	Params       []FuncParam
 	MinParamsCnt int
-	Typ          TypesMap
+	Typ          types.Map
 	AccessLevel  AccessLevel
 	Flags        FuncFlags
 	ExitFlags    int // if function has exit/die/throw, then ExitFlags will be <> 0
@@ -61,27 +81,6 @@ const (
 	OverrideClassType
 )
 
-type AccessLevel int
-
-const (
-	Public AccessLevel = iota
-	Protected
-	Private
-)
-
-func (l AccessLevel) String() string {
-	switch l {
-	case Public:
-		return "public"
-	case Protected:
-		return "protected"
-	case Private:
-		return "private"
-	}
-
-	panic("Invalid access level")
-}
-
 // FuncInfoOverride defines return type overrides based on their parameter types.
 // For example, \array_slice($arr) returns type of element (OverrideElementType) of the ArgNum=0
 type FuncInfoOverride struct {
@@ -91,13 +90,13 @@ type FuncInfoOverride struct {
 
 type PropertyInfo struct {
 	Pos         ElementPosition
-	Typ         TypesMap
+	Typ         types.Map
 	AccessLevel AccessLevel
 }
 
 type ConstInfo struct {
 	Pos         ElementPosition
-	Typ         TypesMap
+	Typ         types.Map
 	AccessLevel AccessLevel
 	Value       ConstValue
 }
@@ -182,11 +181,3 @@ func NameNodeEquals(n ir.Node, s string) bool {
 		return false
 	}
 }
-
-func IsClassType(s string) bool {
-	return strings.HasPrefix(s, `\`) && !IsShapeType(s) && !IsArrayType(s)
-}
-
-func IsShapeType(s string) bool { return strings.HasPrefix(s, `\shape$`) }
-
-func IsArrayType(s string) bool { return strings.HasSuffix(s, `[]`) }
diff --git a/src/meta/scope.go b/src/meta/scope.go
index 795058b..76f7a8b 100644
--- a/src/meta/scope.go
+++ b/src/meta/scope.go
@@ -4,9 +4,11 @@ import (
 	"bytes"
 	"encoding/gob"
 	"fmt"
+	"io"
 	"strings"
 
 	"github.com/VKCOM/noverify/src/ir"
+	"github.com/VKCOM/noverify/src/types"
 )
 
 var debugScope = false
@@ -20,7 +22,7 @@ const (
 )
 
 type scopeVar struct {
-	typesMap TypesMap
+	typesMap types.Map
 	flags    VarFlags
 }
 
@@ -47,20 +49,27 @@ func NewScope() *Scope {
 	return &Scope{vars: make(map[string]*scopeVar)}
 }
 
-// GobEncode is a custom gob marshaller
-func (s *Scope) GobEncode() ([]byte, error) {
-	w := new(bytes.Buffer)
+func (s *Scope) GobWrite(w io.Writer) error {
 	encoder := gob.NewEncoder(w)
 	err := encoder.Encode(s.vars)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	err = encoder.Encode(s.inInstanceMethod)
 	if err != nil {
-		return nil, err
+		return err
 	}
 	err = encoder.Encode(s.inClosure)
 	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// GobEncode is a custom gob marshaller
+func (s *Scope) GobEncode() ([]byte, error) {
+	w := new(bytes.Buffer)
+	if err := s.GobWrite(w); err != nil {
 		return nil, err
 	}
 	return w.Bytes(), nil
@@ -129,7 +138,7 @@ func (s *Scope) SetInClosure(v bool) {
 	s.inClosure = v
 }
 
-func (s *Scope) Iterate(cb func(varName string, typ TypesMap, flags VarFlags)) {
+func (s *Scope) Iterate(cb func(varName string, typ types.Map, flags VarFlags)) {
 	for varName, v := range s.vars {
 		cb(varName, v.typesMap, v.flags)
 	}
@@ -140,7 +149,7 @@ func (s *Scope) Len() int {
 }
 
 // AddVar adds variable with specified types to scope
-func (s *Scope) AddVar(v ir.Node, typ TypesMap, reason string, flags VarFlags) {
+func (s *Scope) AddVar(v ir.Node, typ types.Map, reason string, flags VarFlags) {
 	name, ok := scopeVarName(v)
 	if !ok {
 		return
@@ -149,7 +158,7 @@ func (s *Scope) AddVar(v ir.Node, typ TypesMap, reason string, flags VarFlags) {
 }
 
 // ReplaceVar replaces variable with specified types to scope
-func (s *Scope) ReplaceVar(v ir.Node, typ TypesMap, reason string, flags VarFlags) {
+func (s *Scope) ReplaceVar(v ir.Node, typ types.Map, reason string, flags VarFlags) {
 	name, ok := scopeVarName(v)
 	if !ok {
 		return
@@ -177,7 +186,7 @@ func (s *Scope) DelVarName(name, reason string) {
 }
 
 // ReplaceVarName replaces variable with specified types to the scope
-func (s *Scope) ReplaceVarName(name string, typ TypesMap, reason string, flags VarFlags) {
+func (s *Scope) ReplaceVarName(name string, typ types.Map, reason string, flags VarFlags) {
 	oldVar, ok := s.vars[name]
 	if ok && oldVar.flags.IsNoReplace() {
 		oldVar.typesMap = oldVar.typesMap.Append(typ)
@@ -191,7 +200,7 @@ func (s *Scope) ReplaceVarName(name string, typ TypesMap, reason string, flags V
 }
 
 // AddVarName adds variable with specified types to the scope
-func (s *Scope) addVarName(name string, typ TypesMap, reason string, flags VarFlags) {
+func (s *Scope) addVarName(name string, typ types.Map, reason string, flags VarFlags) {
 	v, ok := s.vars[name]
 
 	if !ok {
@@ -215,12 +224,12 @@ func (s *Scope) addVarName(name string, typ TypesMap, reason string, flags VarFl
 }
 
 // AddVarName adds variable with specified types to the scope
-func (s *Scope) AddVarName(name string, typ TypesMap, reason string, flags VarFlags) {
+func (s *Scope) AddVarName(name string, typ types.Map, reason string, flags VarFlags) {
 	s.addVarName(name, typ, reason, flags)
 }
 
 // AddVarFromPHPDoc adds variable with specified types to the scope
-func (s *Scope) AddVarFromPHPDoc(name string, typ TypesMap, reason string) {
+func (s *Scope) AddVarFromPHPDoc(name string, typ types.Map, reason string) {
 	s.addVarName(name, typ, reason, varNoReplace|VarAlwaysDefined)
 }
 
@@ -254,10 +263,10 @@ func (s *Scope) HaveVarName(name string) bool {
 }
 
 // GetVarNameType returns type map for variable if it exists
-func (s *Scope) GetVarNameType(name string) (m TypesMap, ok bool) {
+func (s *Scope) GetVarNameType(name string) (m types.Map, ok bool) {
 	res, ok := s.vars[name]
 	if !ok {
-		return TypesMap{}, false
+		return types.Map{}, false
 	}
 	return res.typesMap, ok
 }
diff --git a/src/solver/closure.go b/src/solver/closure.go
index 6b5f280..a816eb9 100644
--- a/src/solver/closure.go
+++ b/src/solver/closure.go
@@ -1,8 +1,6 @@
 package solver
 
-import (
-	"github.com/VKCOM/noverify/src/meta"
-)
+import "github.com/VKCOM/noverify/src/types"
 
 var supportedFunctions = map[string]struct{}{
 	`\array_map`:            {},
@@ -21,6 +19,6 @@ func IsSupportedFunction(name string) bool {
 
 // Struct containing information about the function that called the closure.
 type ClosureCallerInfo struct {
-	Name     string          // caller function name
-	ArgTypes []meta.TypesMap // types for each arg for call caller function
+	Name     string      // caller function name
+	ArgTypes []types.Map // types for each arg for call caller function
 }
diff --git a/src/solver/exprtype.go b/src/solver/exprtype.go
index f1b072a..c71383c 100644
--- a/src/solver/exprtype.go
+++ b/src/solver/exprtype.go
@@ -7,23 +7,24 @@ import (
 	"github.com/VKCOM/noverify/src/ir"
 	"github.com/VKCOM/noverify/src/ir/irutil"
 	"github.com/VKCOM/noverify/src/meta"
+	"github.com/VKCOM/noverify/src/types"
 )
 
 // CustomType specifies a mapping between some AST structure
 // and concrete type (e.g. for <expr> instanceof <something>).
 type CustomType struct {
 	Node ir.Node
-	Typ  meta.TypesMap
+	Typ  types.Map
 }
 
 // ExprType returns type of expression. Depending on whether or not is it "full mode",
 // it will also recursively resolve all nested types.
-func ExprType(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node) meta.TypesMap {
+func ExprType(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node) types.Map {
 	return ExprTypeCustom(sc, cs, n, nil)
 }
 
 // ExprTypeCustom is ExprType that allows to specify custom types overrides.
-func ExprTypeCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, custom []CustomType) meta.TypesMap {
+func ExprTypeCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, custom []CustomType) types.Map {
 	m := ExprTypeLocalCustom(sc, cs, n, custom)
 
 	if !cs.Info.IsIndexingComplete() {
@@ -35,26 +36,26 @@ func ExprTypeCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, custom
 
 	visitedMap := make(ResolverMap)
 	resolvedTypes := ResolveTypes(cs.Info, cs.CurrentClass, m, visitedMap)
-	return meta.NewTypesMapFromMap(resolvedTypes)
+	return types.NewMapFromMap(resolvedTypes)
 }
 
 // ExprTypeLocal is basic expression type that does not resolve cross-file function calls and such.
-func ExprTypeLocal(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node) meta.TypesMap {
+func ExprTypeLocal(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node) types.Map {
 	return ExprTypeLocalCustom(sc, cs, n, nil)
 }
 
 // ExprTypeLocalCustom is ExprTypeLocal that allows to specify custom types.
-func ExprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, custom []CustomType) meta.TypesMap {
+func ExprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, custom []CustomType) types.Map {
 	res := exprTypeLocalCustom(sc, cs, n, custom)
 	if res.Len() == 0 {
-		return meta.MixedType
+		return types.MixedType
 	}
 	return res
 }
 
-func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, custom []CustomType) meta.TypesMap {
+func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, custom []CustomType) types.Map {
 	if n == nil || sc == nil {
-		return meta.TypesMap{}
+		return types.Map{}
 	}
 
 	for _, c := range custom {
@@ -87,7 +88,7 @@ func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 	case *ir.BitwiseXorExpr:
 		return bitwiseOpType(sc, cs, n.Left, n.Right, custom)
 	case *ir.ConcatExpr:
-		return meta.PreciseStringType
+		return types.PreciseStringType
 	case *ir.ArrayExpr:
 		return arrayType(sc, cs, n.Items)
 	case *ir.ArrayItemExpr:
@@ -97,7 +98,7 @@ func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 		*ir.GreaterExpr, *ir.GreaterOrEqualExpr,
 		*ir.SmallerExpr, *ir.SmallerOrEqualExpr,
 		*ir.EmptyExpr, *ir.IssetExpr:
-		return meta.PreciseBoolType
+		return types.PreciseBoolType
 	case *ir.UnaryMinusExpr:
 		return unaryMathOpType(sc, cs, n.Expr, custom)
 	case *ir.UnaryPlusExpr:
@@ -123,17 +124,17 @@ func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 	case *ir.TypeCastExpr:
 		return typeCastType(n)
 	case *ir.ShiftLeftExpr, *ir.ShiftRightExpr:
-		return meta.PreciseIntType
+		return types.PreciseIntType
 	case *ir.ClassConstFetchExpr:
 		return classConstFetchType(n, cs)
 	case *ir.ConstFetchExpr:
 		return constFetchType(n)
 	case *ir.String, *ir.Encapsed, *ir.Heredoc:
-		return meta.PreciseStringType
+		return types.PreciseStringType
 	case *ir.Lnumber:
-		return meta.PreciseIntType
+		return types.PreciseIntType
 	case *ir.Dnumber:
-		return meta.PreciseFloatType
+		return types.PreciseFloatType
 	case *ir.TernaryExpr:
 		return ternaryExprType(n, sc, cs, custom)
 	case *ir.CoalesceExpr:
@@ -145,59 +146,59 @@ func exprTypeLocalCustom(sc *meta.Scope, cs *meta.ClassParseState, n ir.Node, cu
 	case *ir.Assign:
 		return ExprTypeLocalCustom(sc, cs, n.Expr, custom)
 	case *ir.AssignConcat:
-		return meta.PreciseStringType
+		return types.PreciseStringType
 	case *ir.AssignShiftLeft, *ir.AssignShiftRight:
-		return meta.PreciseIntType
+		return types.PreciseIntType
 	case *ir.CloneExpr:
 		return ExprTypeLocalCustom(sc, cs, n.Expr, custom)
 	case *ir.ClosureExpr:
-		return meta.NewTypesMap(`\Closure`)
+		return types.NewMap(`\Closure`)
 	case *ir.MagicConstant:
 		return magicConstantType(n)
 	}
 
-	return meta.TypesMap{}
+	return types.Map{}
 }
 
 // unaryBitwiseOpType is used for unary bitwise operations.
-func unaryBitwiseOpType(sc *meta.Scope, cs *meta.ClassParseState, x ir.Node, custom []CustomType) meta.TypesMap {
+func unaryBitwiseOpType(sc *meta.Scope, cs *meta.ClassParseState, x ir.Node, custom []CustomType) types.Map {
 	if ExprTypeLocalCustom(sc, cs, x, custom).Is("string") {
-		return meta.NewTypesMap("string")
+		return types.NewMap("string")
 	}
-	return meta.NewTypesMap("int")
+	return types.NewMap("int")
 }
 
 // bitwiseOpType is used for binary bitwise operations.
-func bitwiseOpType(sc *meta.Scope, cs *meta.ClassParseState, left, right ir.Node, custom []CustomType) meta.TypesMap {
+func bitwiseOpType(sc *meta.Scope, cs *meta.ClassParseState, left, right ir.Node, custom []CustomType) types.Map {
 	if ExprTypeLocalCustom(sc, cs, left, custom).Is("string") && ExprTypeLocalCustom(sc, cs, right, custom).Is("string") {
-		return meta.NewTypesMap("string")
+		return types.NewMap("string")
 	}
-	return meta.NewTypesMap("int")
+	return types.NewMap("int")
 }
 
 // unaryMathOpType is used for unary arithmetic operations.
-func unaryMathOpType(sc *meta.Scope, cs *meta.ClassParseState, x ir.Node, custom []CustomType) meta.TypesMap {
+func unaryMathOpType(sc *meta.Scope, cs *meta.ClassParseState, x ir.Node, custom []CustomType) types.Map {
 	if ExprTypeLocalCustom(sc, cs, x, custom).Is("int") {
-		return meta.NewTypesMap("int")
+		return types.NewMap("int")
 	}
-	return meta.NewTypesMap("float")
+	return types.NewMap("float")
 }
 
 // binaryMathOpType is used for binary arithmetic operations.
-func binaryMathOpType(sc *meta.Scope, cs *meta.ClassParseState, left, right ir.Node, custom []CustomType) meta.TypesMap {
+func binaryMathOpType(sc *meta.Scope, cs *meta.ClassParseState, left, right ir.Node, custom []CustomType) types.Map {
 	if ExprTypeLocalCustom(sc, cs, left, custom).Is("int") && ExprTypeLocalCustom(sc, cs, right, custom).Is("int") {
-		return meta.NewTypesMap("int")
+		return types.NewMap("int")
 	}
-	return meta.NewTypesMap("float")
+	return types.NewMap("float")
 }
 
 // binaryPlusOpType is a special case as "plus" is also used for array union operation.
-func binaryPlusOpType(sc *meta.Scope, cs *meta.ClassParseState, left, right ir.Node, custom []CustomType) meta.TypesMap {
+func binaryPlusOpType(sc *meta.Scope, cs *meta.ClassParseState, left, right ir.Node, custom []CustomType) types.Map {
 	// TODO: PHP will raise fatal error if one operand is array and other is not, so we may check it too
 	leftType := ExprTypeLocalCustom(sc, cs, left, custom)
 	rightType := ExprTypeLocalCustom(sc, cs, right, custom)
 	if leftType.IsArray() && rightType.IsArray() {
-		return meta.MergeTypeMaps(leftType, rightType)
+		return types.MergeMaps(leftType, rightType)
 	}
 	return binaryMathOpType(sc, cs, left, right, custom)
 }
@@ -235,10 +236,10 @@ func classNameToString(cs *meta.ClassParseState, n ir.Node) (string, bool) {
 	return className, true
 }
 
-func internalFuncType(nm string, sc *meta.Scope, cs *meta.ClassParseState, c *ir.FunctionCallExpr, custom []CustomType) (typ meta.TypesMap, ok bool) {
+func internalFuncType(nm string, sc *meta.Scope, cs *meta.ClassParseState, c *ir.FunctionCallExpr, custom []CustomType) (typ types.Map, ok bool) {
 	fn, ok := cs.Info.GetInternalFunctionInfo(nm)
 	if !ok || fn.Typ.IsEmpty() {
-		return meta.TypesMap{}, false
+		return types.Map{}, false
 	}
 
 	override, ok := cs.Info.GetInternalFunctionOverrideInfo(nm)
@@ -254,7 +255,7 @@ func internalFuncType(nm string, sc *meta.Scope, cs *meta.ClassParseState, c *ir
 		return typ, true
 
 	case meta.OverrideElementType:
-		newTyp := typ.Map(meta.WrapElemOf)
+		newTyp := typ.Map(types.WrapElemOf)
 		return newTyp, true
 
 	case meta.OverrideClassType:
@@ -263,16 +264,16 @@ func internalFuncType(nm string, sc *meta.Scope, cs *meta.ClassParseState, c *ir
 		// although the most popular ones.
 		className, ok := classNameToString(cs, arg.Expr)
 		if !ok {
-			return meta.NewTypesMap("mixed"), true
+			return types.NewMap("mixed"), true
 		}
-		return meta.NewTypesMap(className + "|null"), true
+		return types.NewMap(className + "|null"), true
 	}
 
 	log.Printf("Internal error: unexpected override type %d for function %s", override.OverrideType, nm)
-	return meta.TypesMap{}, false
+	return types.Map{}, false
 }
 
-func arrayType(sc *meta.Scope, cs *meta.ClassParseState, items []*ir.ArrayItemExpr) meta.TypesMap {
+func arrayType(sc *meta.Scope, cs *meta.ClassParseState, items []*ir.ArrayItemExpr) types.Map {
 	if len(items) == 0 {
 		// Used as a placeholder until more specific type is discovered.
 		//
@@ -283,7 +284,7 @@ func arrayType(sc *meta.Scope, cs *meta.ClassParseState, items []*ir.ArrayItemEx
 		// for any mono-typed array, so we can just throw away "empty_array"
 		// in that case. If element type is unknown, "empty_array" is
 		// resolved into "mixed[]".
-		return meta.NewTypesMap("empty_array")
+		return types.NewMap("empty_array")
 	}
 
 	firstElementType := ExprTypeLocal(sc, cs, items[0])
@@ -298,83 +299,83 @@ func arrayType(sc *meta.Scope, cs *meta.ClassParseState, items []*ir.ArrayItemEx
 		}
 
 		if !firstElementType.Equals(itemType) {
-			return meta.NewTypesMap("mixed[]")
+			return types.NewMap("mixed[]")
 		}
 	}
 
-	return firstElementType.Map(meta.WrapArrayOf)
+	return firstElementType.Map(types.WrapArrayOf)
 }
 
-func newExprType(n *ir.NewExpr, cs *meta.ClassParseState) meta.TypesMap {
+func newExprType(n *ir.NewExpr, cs *meta.ClassParseState) types.Map {
 	if meta.NameNodeToString(n.Class) == "static" {
-		return meta.NewTypesMap("static")
+		return types.NewMap("static")
 	}
 	nm, ok := GetClassName(cs, n.Class)
 	if ok {
-		return meta.NewPreciseTypesMap(nm)
+		return types.NewPreciseMap(nm)
 	}
-	return meta.TypesMap{}
+	return types.Map{}
 }
 
-func ternaryExprType(n *ir.TernaryExpr, sc *meta.Scope, cs *meta.ClassParseState, custom []CustomType) meta.TypesMap {
+func ternaryExprType(n *ir.TernaryExpr, sc *meta.Scope, cs *meta.ClassParseState, custom []CustomType) types.Map {
 	t := ExprTypeLocalCustom(sc, cs, n.IfTrue, custom)
 	f := ExprTypeLocalCustom(sc, cs, n.IfFalse, custom)
-	return meta.NewEmptyTypesMap(t.Len() + f.Len()).Append(t).Append(f)
+	return types.NewEmptyMap(t.Len() + f.Len()).Append(t).Append(f)
 }
 
-func coalesceExprType(n *ir.CoalesceExpr, sc *meta.Scope, cs *meta.ClassParseState, custom []CustomType) meta.TypesMap {
+func coalesceExprType(n *ir.CoalesceExpr, sc *meta.Scope, cs *meta.ClassParseState, custom []CustomType) types.Map {
 	l := ExprTypeLocalCustom(sc, cs, n.Left, custom)
 	r := ExprTypeLocalCustom(sc, cs, n.Right, custom)
-	return meta.NewEmptyTypesMap(l.Len() + r.Len()).Append(l).Append(r)
+	return types.NewEmptyMap(l.Len() + r.Len()).Append(l).Append(r)
 }
 
-func constFetchType(n *ir.ConstFetchExpr) meta.TypesMap {
+func constFetchType(n *ir.ConstFetchExpr) types.Map {
 	// TODO: handle namespaces
 	nm := n.Constant
 	switch nm.Value {
 	case "false", "true":
-		return meta.PreciseBoolType
+		return types.PreciseBoolType
 	case "null":
-		return meta.NewTypesMap("null")
+		return types.NewMap("null")
 	default:
 		if nm.NumParts() == 0 {
-			return meta.NewTypesMap(meta.WrapConstant(nm.Value))
+			return types.NewMap(types.WrapConstant(nm.Value))
 		}
 	}
-	return meta.TypesMap{}
+	return types.Map{}
 }
 
-func classConstFetchType(n *ir.ClassConstFetchExpr, cs *meta.ClassParseState) meta.TypesMap {
+func classConstFetchType(n *ir.ClassConstFetchExpr, cs *meta.ClassParseState) types.Map {
 	if n.ConstantName.Value == "class" {
-		return meta.PreciseStringType
+		return types.PreciseStringType
 	}
 	className, ok := GetClassName(cs, n.Class)
 	if !ok {
-		return meta.TypesMap{}
+		return types.Map{}
 	}
-	return meta.NewTypesMap(meta.WrapClassConstFetch(className, n.ConstantName.Value))
+	return types.NewMap(types.WrapClassConstFetch(className, n.ConstantName.Value))
 }
 
-func typeCastType(n *ir.TypeCastExpr) meta.TypesMap {
+func typeCastType(n *ir.TypeCastExpr) types.Map {
 	switch n.Type {
 	case "array":
-		return meta.NewTypesMap("mixed[]")
+		return types.NewMap("mixed[]")
 	case "int":
-		return meta.PreciseIntType
+		return types.PreciseIntType
 	case "string":
-		return meta.PreciseStringType
+		return types.PreciseStringType
 	case "float":
-		return meta.PreciseFloatType
+		return types.PreciseFloatType
 	case "bool":
-		return meta.PreciseBoolType
+		return types.PreciseBoolType
 	}
-	return meta.TypesMap{}
+	return types.Map{}
 }
 
-func arrayDimFetchType(n *ir.ArrayDimFetchExpr, sc *meta.Scope, cs *meta.ClassParseState, custom []CustomType) meta.TypesMap {
+func arrayDimFetchType(n *ir.ArrayDimFetchExpr, sc *meta.Scope, cs *meta.ClassParseState, custom []CustomType) types.Map {
 	m := ExprTypeLocalCustom(sc, cs, n.Variable, custom)
 	if m.IsEmpty() {
-		return meta.TypesMap{}
+		return types.Map{}
 	}
 
 	res := make(map[string]struct{}, m.Len())
@@ -382,98 +383,98 @@ func arrayDimFetchType(n *ir.ArrayDimFetchExpr, sc *meta.Scope, cs *meta.ClassPa
 	m.Iterate(func(className string) {
 		switch dim := n.Dim.(type) {
 		case *ir.String:
-			res[meta.WrapElemOfKey(className, dim.Value)] = struct{}{}
+			res[types.WrapElemOfKey(className, dim.Value)] = struct{}{}
 		case *ir.Lnumber:
-			res[meta.WrapElemOfKey(className, dim.Value)] = struct{}{}
+			res[types.WrapElemOfKey(className, dim.Value)] = struct{}{}
 		default:
-			res[meta.WrapElemOf(className)] = struct{}{}
+			res[types.WrapElemOf(className)] = struct{}{}
 		}
 	})
 
-	return meta.NewTypesMapFromMap(res)
+	return types.NewMapFromMap(res)
 }
 
-func propertyFetchType(n *ir.PropertyFetchExpr, sc *meta.Scope, cs *meta.ClassParseState, custom []CustomType) meta.TypesMap {
+func propertyFetchType(n *ir.PropertyFetchExpr, sc *meta.Scope, cs *meta.ClassParseState, custom []CustomType) types.Map {
 	// Support only $obj->some_prop.
 	// Do not support $obj->$some_prop.
 	id, ok := n.Property.(*ir.Identifier)
 	if !ok {
-		return meta.TypesMap{}
+		return types.Map{}
 	}
 
 	m := ExprTypeLocalCustom(sc, cs, n.Variable, custom)
 	if m.IsEmpty() {
-		return meta.TypesMap{}
+		return types.Map{}
 	}
 
 	res := make(map[string]struct{}, m.Len())
 
 	m.Iterate(func(className string) {
-		res[meta.WrapInstancePropertyFetch(className, id.Value)] = struct{}{}
+		res[types.WrapInstancePropertyFetch(className, id.Value)] = struct{}{}
 	})
 
-	return meta.NewTypesMapFromMap(res)
+	return types.NewMapFromMap(res)
 }
 
-func methodCallType(n *ir.MethodCallExpr, sc *meta.Scope, cs *meta.ClassParseState, custom []CustomType) meta.TypesMap {
+func methodCallType(n *ir.MethodCallExpr, sc *meta.Scope, cs *meta.ClassParseState, custom []CustomType) types.Map {
 	// Support only $obj->callSomething().
 	// Do not support $obj->$method().
 	id, ok := n.Method.(*ir.Identifier)
 	if !ok {
-		return meta.TypesMap{}
+		return types.Map{}
 	}
 
 	m := ExprTypeLocalCustom(sc, cs, n.Variable, custom)
 	if m.IsEmpty() {
-		return meta.TypesMap{}
+		return types.Map{}
 	}
 
 	res := make(map[string]struct{}, m.Len())
 
 	m.Iterate(func(className string) {
-		res[meta.WrapInstanceMethodCall(className, id.Value)] = struct{}{}
+		res[types.WrapInstanceMethodCall(className, id.Value)] = struct{}{}
 	})
 
-	return meta.NewTypesMapFromMap(res)
+	return types.NewMapFromMap(res)
 }
 
-func simpleVarType(n *ir.SimpleVar, sc *meta.Scope) meta.TypesMap {
+func simpleVarType(n *ir.SimpleVar, sc *meta.Scope) types.Map {
 	typ, _ := sc.GetVarNameType(n.Name)
 	return typ
 }
 
-func staticPropertyFetchType(n *ir.StaticPropertyFetchExpr, cs *meta.ClassParseState) meta.TypesMap {
+func staticPropertyFetchType(n *ir.StaticPropertyFetchExpr, cs *meta.ClassParseState) types.Map {
 	v, ok := n.Property.(*ir.SimpleVar)
 	if !ok {
-		return meta.TypesMap{}
+		return types.Map{}
 	}
 
 	nm, ok := GetClassName(cs, n.Class)
 	if !ok {
-		return meta.TypesMap{}
+		return types.Map{}
 	}
 
-	return meta.NewTypesMap(meta.WrapStaticPropertyFetch(nm, "$"+v.Name))
+	return types.NewMap(types.WrapStaticPropertyFetch(nm, "$"+v.Name))
 }
 
-func staticFunctionCallType(n *ir.StaticCallExpr, cs *meta.ClassParseState) meta.TypesMap {
+func staticFunctionCallType(n *ir.StaticCallExpr, cs *meta.ClassParseState) types.Map {
 	id, ok := n.Call.(*ir.Identifier)
 	if !ok {
-		return meta.TypesMap{}
+		return types.Map{}
 	}
 
 	nm, ok := GetClassName(cs, n.Class)
 	if !ok {
-		return meta.TypesMap{}
+		return types.Map{}
 	}
 
-	return meta.NewTypesMap(meta.WrapStaticMethodCall(nm, id.Value))
+	return types.NewMap(types.WrapStaticMethodCall(nm, id.Value))
 }
 
-func functionCallType(n *ir.FunctionCallExpr, sc *meta.Scope, cs *meta.ClassParseState, custom []CustomType) meta.TypesMap {
+func functionCallType(n *ir.FunctionCallExpr, sc *meta.Scope, cs *meta.ClassParseState, custom []CustomType) types.Map {
 	nm, ok := n.Function.(*ir.Name)
 	if !ok {
-		return meta.TypesMap{}
+		return types.Map{}
 	}
 	if nm.IsFullyQualified() {
 		if nm.NumParts() == 1 {
@@ -482,18 +483,18 @@ func functionCallType(n *ir.FunctionCallExpr, sc *meta.Scope, cs *meta.ClassPars
 				return typ
 			}
 		}
-		return meta.NewTypesMap(meta.WrapFunctionCall(nm.Value))
+		return types.NewMap(types.WrapFunctionCall(nm.Value))
 	}
 	typ, ok := internalFuncType(`\`+nm.Value, sc, cs, n, custom)
 	if ok {
 		return typ
 	}
-	return meta.NewTypesMap(meta.WrapFunctionCall(cs.Namespace + `\` + nm.Value))
+	return types.NewMap(types.WrapFunctionCall(cs.Namespace + `\` + nm.Value))
 }
 
-func magicConstantType(n *ir.MagicConstant) meta.TypesMap {
+func magicConstantType(n *ir.MagicConstant) types.Map {
 	if n.Value == "__LINE__" {
-		return meta.PreciseIntType
+		return types.PreciseIntType
 	}
-	return meta.PreciseStringType
+	return types.PreciseStringType
 }
diff --git a/src/solver/solver.go b/src/solver/solver.go
index 62393eb..c6156c6 100644
--- a/src/solver/solver.go
+++ b/src/solver/solver.go
@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"github.com/VKCOM/noverify/src/meta"
+	"github.com/VKCOM/noverify/src/types"
 )
 
 var sharedMixedType = map[string]struct{}{"mixed": {}}
@@ -27,7 +28,7 @@ func resolveType(info *meta.Info, curStaticClass, typ string, visitedMap Resolve
 
 // ResolveTypes resolves function calls, method calls and global variables.
 //   curStaticClass is current class name (if inside the class, otherwise "")
-func ResolveTypes(info *meta.Info, curStaticClass string, m meta.TypesMap, visitedMap ResolverMap) map[string]struct{} {
+func ResolveTypes(info *meta.Info, curStaticClass string, m types.Map, visitedMap ResolverMap) map[string]struct{} {
 	r := resolver{info: info, visited: visitedMap}
 	return r.resolveTypes(curStaticClass, m)
 }
@@ -75,7 +76,7 @@ func (r *resolver) resolveTypeNoLateStaticBinding(class, typ string) map[string]
 		return result
 	}
 
-	if typ == "" || typ[0] >= meta.WMax {
+	if typ == "" || typ[0] >= types.WMax {
 		return identityType(typ)
 	}
 
@@ -83,39 +84,39 @@ func (r *resolver) resolveTypeNoLateStaticBinding(class, typ string) map[string]
 	visitedMap[typ] = nil // Nil guards against unbound recursion
 
 	switch typ[0] {
-	case meta.WGlobal:
-		varTyp, ok := r.info.GetVarNameType(meta.UnwrapGlobal(typ))
+	case types.WGlobal:
+		varTyp, ok := r.info.GetVarNameType(types.UnwrapGlobal(typ))
 		if ok {
 			for tt := range r.resolveTypes(class, varTyp) {
 				res[tt] = struct{}{}
 			}
 		}
-	case meta.WConstant:
-		ci, ok := r.info.GetConstant(meta.UnwrapConstant(typ))
+	case types.WConstant:
+		ci, ok := r.info.GetConstant(types.UnwrapConstant(typ))
 		if ok {
 			for tt := range r.resolveTypes(class, ci.Typ) {
 				res[tt] = struct{}{}
 			}
 		}
-	case meta.WArrayOf:
-		for tt := range r.resolveType(class, meta.UnwrapArrayOf(typ)) {
+	case types.WArrayOf:
+		for tt := range r.resolveType(class, types.UnwrapArrayOf(typ)) {
 			res[tt+"[]"] = struct{}{}
 		}
-	case meta.WElemOfKey:
-		arrayType, key := meta.UnwrapElemOfKey(typ)
+	case types.WElemOfKey:
+		arrayType, key := types.UnwrapElemOfKey(typ)
 		for tt := range r.resolveType(class, arrayType) {
-			if meta.IsShapeType(tt) {
+			if types.IsShapeType(tt) {
 				res = r.solveElemOfShape(class, tt, key, res)
 			} else {
 				res = r.solveElemOf(tt, res)
 			}
 		}
-	case meta.WElemOf:
-		for tt := range r.resolveType(class, meta.UnwrapElemOf(typ)) {
+	case types.WElemOf:
+		for tt := range r.resolveType(class, types.UnwrapElemOf(typ)) {
 			res = r.solveElemOf(tt, res)
 		}
-	case meta.WFunctionCall:
-		nm := meta.UnwrapFunctionCall(typ)
+	case types.WFunctionCall:
+		nm := types.UnwrapFunctionCall(typ)
 		fn, ok := r.info.GetFunction(nm)
 		// functions can fall back to root namespace
 		if !ok && strings.Count(nm, `\`) > 1 {
@@ -125,8 +126,8 @@ func (r *resolver) resolveTypeNoLateStaticBinding(class, typ string) map[string]
 		if ok {
 			return r.resolveTypes(class, fn.Typ)
 		}
-	case meta.WInstanceMethodCall:
-		expr, methodName := meta.UnwrapInstanceMethodCall(typ)
+	case types.WInstanceMethodCall:
+		expr, methodName := types.UnwrapInstanceMethodCall(typ)
 
 		instanceTypes := r.resolveType(class, expr)
 		res = r.collectMethodCallTypes(res, instanceTypes, methodName)
@@ -134,8 +135,8 @@ func (r *resolver) resolveTypeNoLateStaticBinding(class, typ string) map[string]
 			res = r.collectMethodCallTypes(res, instanceTypes, "__call")
 		}
 
-	case meta.WInstancePropertyFetch:
-		expr, propertyName := meta.UnwrapInstancePropertyFetch(typ)
+	case types.WInstancePropertyFetch:
+		expr, propertyName := types.UnwrapInstancePropertyFetch(typ)
 
 		for className := range r.resolveType(class, expr) {
 			p, ok := FindProperty(r.info, className, propertyName)
@@ -153,10 +154,10 @@ func (r *resolver) resolveTypeNoLateStaticBinding(class, typ string) map[string]
 				}
 			}
 		}
-	case meta.WBaseMethodParam:
+	case types.WBaseMethodParam:
 		return r.solveBaseMethodParam(class, typ, visitedMap, res)
-	case meta.WStaticMethodCall:
-		className, methodName := meta.UnwrapStaticMethodCall(typ)
+	case types.WStaticMethodCall:
+		className, methodName := types.UnwrapStaticMethodCall(typ)
 		m, ok := FindMethod(r.info, className, methodName)
 		if ok {
 			res = r.resolveTypes(className, m.Info.Typ)
@@ -173,8 +174,8 @@ func (r *resolver) resolveTypeNoLateStaticBinding(class, typ string) map[string]
 			return r.resolveTypes(className, m.Info.Typ)
 		}
 
-	case meta.WStaticPropertyFetch:
-		className, propertyName := meta.UnwrapStaticPropertyFetch(typ)
+	case types.WStaticPropertyFetch:
+		className, propertyName := types.UnwrapStaticPropertyFetch(typ)
 		p, ok := FindProperty(r.info, className, propertyName)
 		if ok {
 			res = r.resolveTypes(className, p.Info.Typ)
@@ -183,8 +184,8 @@ func (r *resolver) resolveTypeNoLateStaticBinding(class, typ string) map[string]
 			}
 			return res
 		}
-	case meta.WClassConstFetch:
-		className, constName := meta.UnwrapClassConstFetch(typ)
+	case types.WClassConstFetch:
+		className, constName := types.UnwrapClassConstFetch(typ)
 		info, _, ok := FindConstant(r.info, className, constName)
 		if ok {
 			return r.resolveTypes(class, info.Typ)
@@ -197,7 +198,7 @@ func (r *resolver) resolveTypeNoLateStaticBinding(class, typ string) map[string]
 }
 
 func (r *resolver) solveBaseMethodParam(curStaticClass, typ string, visitedMap ResolverMap, res map[string]struct{}) map[string]struct{} {
-	index, className, methodName := meta.UnwrapBaseMethodParam(typ)
+	index, className, methodName := types.UnwrapBaseMethodParam(typ)
 	class, ok := r.info.GetClass(className)
 	if ok {
 		// TODO(quasilyte): walk parent interfaces as well?
@@ -234,7 +235,7 @@ func (r *resolver) solveElemOfShape(class, shapeName, key string, res map[string
 
 func (r *resolver) solveElemOf(tt string, res map[string]struct{}) map[string]struct{} {
 	switch {
-	case meta.IsArrayType(tt):
+	case types.IsArrayType(tt):
 		res[strings.TrimSuffix(tt, "[]")] = struct{}{}
 	case tt == "mixed":
 		res["mixed"] = struct{}{}
@@ -256,13 +257,13 @@ func (r *resolver) solveElemOf(tt string, res map[string]struct{}) map[string]st
 	return res
 }
 
-func (r *resolver) resolveTypes(class string, m meta.TypesMap) map[string]struct{} {
+func (r *resolver) resolveTypes(class string, m types.Map) map[string]struct{} {
 	res := make(map[string]struct{}, m.Len())
 
 	m.Iterate(func(t string) {
 		defer func() {
 			if r := recover(); r != nil {
-				log.Printf("Panic during parsing '%s'", meta.NewTypesMap(t))
+				log.Printf("Panic during parsing '%s'", types.NewMap(t))
 				panic(r)
 			}
 		}()
@@ -280,7 +281,7 @@ func (r *resolver) resolveTypes(class string, m meta.TypesMap) map[string]struct
 		delete(res, "empty_array")
 		specialized := false
 		for tt := range res {
-			if meta.IsArrayType(tt) {
+			if types.IsArrayType(tt) {
 				specialized = true
 				break
 			}
diff --git a/src/meta/typesmap.go b/src/types/typesmap.go
similarity index 68%
rename from src/meta/typesmap.go
rename to src/types/typesmap.go
index 2f5fab6..c9e2a8d 100644
--- a/src/meta/typesmap.go
+++ b/src/types/typesmap.go
@@ -1,4 +1,4 @@
-package meta
+package types
 
 import (
 	"bytes"
@@ -9,14 +9,14 @@ import (
 
 // Preallocated and shared immutable type maps.
 var (
-	MixedType = NewTypesMap("mixed").Immutable()
-	VoidType  = NewTypesMap("void").Immutable()
-	NullType  = NewTypesMap("null").Immutable()
-
-	PreciseIntType    = NewPreciseTypesMap("int").Immutable()
-	PreciseFloatType  = NewPreciseTypesMap("float").Immutable()
-	PreciseBoolType   = NewPreciseTypesMap("bool").Immutable()
-	PreciseStringType = NewPreciseTypesMap("string").Immutable()
+	MixedType = NewMap("mixed").Immutable()
+	VoidType  = NewMap("void").Immutable()
+	NullType  = NewMap("null").Immutable()
+
+	PreciseIntType    = NewPreciseMap("int").Immutable()
+	PreciseFloatType  = NewPreciseMap("float").Immutable()
+	PreciseBoolType   = NewPreciseMap("bool").Immutable()
+	PreciseStringType = NewPreciseMap("string").Immutable()
 )
 
 type Type struct {
@@ -31,8 +31,8 @@ const (
 	mapPrecise
 )
 
-// TypesMap holds a set of types and can be made immutable to prevent unexpected changes.
-type TypesMap struct {
+// Map holds a set of types and can be made immutable to prevent unexpected changes.
+type Map struct {
 	flags mapFlags
 	m     map[string]struct{}
 }
@@ -47,20 +47,20 @@ type TypesMap struct {
 // Adding an imprecise type to a types map makes the entire type map imprecise.
 //
 // Important invariant: a precise map contains no lazy types.
-func (m TypesMap) IsPrecise() bool { return m.flags&mapPrecise != 0 }
+func (m Map) IsPrecise() bool { return m.flags&mapPrecise != 0 }
 
-func (m *TypesMap) MarkAsImprecise() {
+func (m *Map) MarkAsImprecise() {
 	m.flags &^= mapPrecise
 }
 
-func (m TypesMap) isImmutable() bool { return m.flags&mapImmutable != 0 }
+func (m Map) isImmutable() bool { return m.flags&mapImmutable != 0 }
 
 // IsResolved reports whether all types inside types map are resolved.
 //
 // Users should not depend on the "false" result meaning.
-// If "true" is returned, TypesMap is guaranteed to be free of lazy types.
-func (m TypesMap) IsResolved() bool {
-	// TODO: could do a `s[0] >= meta.WMax` check over map keys
+// If "true" is returned, Map is guaranteed to be free of lazy types.
+func (m Map) IsResolved() bool {
+	// TODO: could do a `s[0] >= types.WMax` check over map keys
 	// to check if it contains any lazy types.
 	// It can be safe to start with maps of size 1.
 	//
@@ -73,20 +73,20 @@ func (m TypesMap) IsResolved() bool {
 
 // Map returns a new types map with the results of calling fn for every type contained inside m.
 // The result type map is never marked as precise.
-func (m TypesMap) Map(fn func(string) string) TypesMap {
+func (m Map) Map(fn func(string) string) Map {
 	mapped := make(map[string]struct{}, len(m.m))
 	for typ := range m.m {
 		mapped[fn(typ)] = struct{}{}
 	}
-	return NewTypesMapFromMap(mapped)
+	return NewMapFromMap(mapped)
 }
 
-// NewEmptyTypesMap creates new type map that has no types in it
-func NewEmptyTypesMap(cap int) TypesMap {
-	return TypesMap{m: make(map[string]struct{}, cap)}
+// NewEmptyMap creates new type map that has no types in it
+func NewEmptyMap(cap int) Map {
+	return Map{m: make(map[string]struct{}, cap)}
 }
 
-func NewTypesMapFromTypes(types []Type) TypesMap {
+func NewMapFromTypes(types []Type) Map {
 	m := make(map[string]struct{}, len(types))
 	for _, typ := range types {
 		s := typ.Elem
@@ -95,11 +95,11 @@ func NewTypesMapFromTypes(types []Type) TypesMap {
 		}
 		m[s] = struct{}{}
 	}
-	return TypesMap{m: m}
+	return Map{m: m}
 }
 
-// NewTypesMap returns new TypesMap that is initialized with the provided types (separated by "|" symbol)
-func NewTypesMap(str string) TypesMap {
+// NewMap returns new Map that is initialized with the provided types (separated by "|" symbol)
+func NewMap(str string) Map {
 	m := make(map[string]struct{}, strings.Count(str, "|")+1)
 	for _, s := range strings.Split(str, "|") {
 		if IsArrayType(s) {
@@ -107,23 +107,23 @@ func NewTypesMap(str string) TypesMap {
 		}
 		m[s] = struct{}{}
 	}
-	return TypesMap{m: m}
+	return Map{m: m}
 }
 
-func NewPreciseTypesMap(str string) TypesMap {
-	m := NewTypesMap(str)
+func NewPreciseMap(str string) Map {
+	m := NewMap(str)
 	m.flags |= mapPrecise
 	return m
 }
 
-// MergeTypeMaps creates a new types map from union of specified type maps
-func MergeTypeMaps(maps ...TypesMap) TypesMap {
+// MergeMaps creates a new types map from union of specified type maps
+func MergeMaps(maps ...Map) Map {
 	totalLen := 0
 	for _, m := range maps {
 		totalLen += m.Len()
 	}
 
-	t := NewEmptyTypesMap(totalLen)
+	t := NewEmptyMap(totalLen)
 	for _, m := range maps {
 		t = t.Append(m)
 	}
@@ -131,26 +131,26 @@ func MergeTypeMaps(maps ...TypesMap) TypesMap {
 	return t
 }
 
-// NewTypesMapFromMap creates TypesMap from provided map[string]struct{}
-func NewTypesMapFromMap(m map[string]struct{}) TypesMap {
-	return TypesMap{m: m}
+// NewMapFromMap creates Map from provided map[string]struct{}
+func NewMapFromMap(m map[string]struct{}) Map {
+	return Map{m: m}
 }
 
-// Immutable returns immutable view of TypesMap
-func (m TypesMap) Immutable() TypesMap {
-	return TypesMap{
+// Immutable returns immutable view of Map
+func (m Map) Immutable() Map {
+	return Map{
 		flags: m.flags | mapImmutable,
 		m:     m.m,
 	}
 }
 
 // IsEmpty checks if map has no types at all
-func (m TypesMap) IsEmpty() bool {
+func (m Map) IsEmpty() bool {
 	return len(m.m) == 0
 }
 
 // Equals check if two typesmaps are the same
-func (m TypesMap) Equals(m2 TypesMap) bool {
+func (m Map) Equals(m2 Map) bool {
 	if len(m.m) != len(m2.m) {
 		return false
 	}
@@ -164,14 +164,14 @@ func (m TypesMap) Equals(m2 TypesMap) bool {
 }
 
 // Len returns number of different types in map
-func (m TypesMap) Len() int {
+func (m Map) Len() int {
 	return len(m.m)
 }
 
 // IsArray checks if map contains only array of any type
 //
 // Warning: use only for *lazy* types!
-func (m TypesMap) IsArray() bool {
+func (m Map) IsArray() bool {
 	if len(m.m) != 1 {
 		return false
 	}
@@ -187,7 +187,7 @@ func (m TypesMap) IsArray() bool {
 // IsArrayOf checks if map contains only array of given type
 //
 // Warning: use only for *lazy* types!
-func (m TypesMap) IsArrayOf(typ string) bool {
+func (m Map) IsArrayOf(typ string) bool {
 	if len(m.m) != 1 {
 		return false
 	}
@@ -199,7 +199,7 @@ func (m TypesMap) IsArrayOf(typ string) bool {
 // Is reports whether m contains exactly one specified type.
 //
 // Warning: typ must be a proper *lazy* or *solved* type.
-func (m TypesMap) Is(typ string) bool {
+func (m Map) Is(typ string) bool {
 	if m.Len() != 1 {
 		return false
 	}
@@ -208,7 +208,7 @@ func (m TypesMap) Is(typ string) bool {
 	return ok
 }
 
-func (m TypesMap) Clone() TypesMap {
+func (m Map) Clone() Map {
 	if m.Len() == 0 || m.isImmutable() {
 		return m
 	}
@@ -217,11 +217,11 @@ func (m TypesMap) Clone() TypesMap {
 	for typ := range m.m {
 		mm[typ] = struct{}{}
 	}
-	return TypesMap{m: mm, flags: m.flags}
+	return Map{m: mm, flags: m.flags}
 }
 
 // Append adds provided types to current map and returns new one (immutable maps are always copied)
-func (m TypesMap) Append(n TypesMap) TypesMap {
+func (m Map) Append(n Map) Map {
 	if m.Len() == 0 {
 		return n
 	}
@@ -259,11 +259,11 @@ func (m TypesMap) Append(n TypesMap) TypesMap {
 		flags |= mapPrecise
 	}
 
-	return TypesMap{m: mm, flags: flags}
+	return Map{m: mm, flags: flags}
 }
 
 // String returns string representation of a map
-func (m TypesMap) String() string {
+func (m Map) String() string {
 	if len(m.m) == 1 {
 		for k := range m.m {
 			return k
@@ -272,14 +272,14 @@ func (m TypesMap) String() string {
 
 	types := make([]string, 0, len(m.m))
 	for k := range m.m {
-		types = append(types, formatType(k))
+		types = append(types, FormatType(k))
 	}
 	sort.Strings(types)
 	return strings.Join(types, "|")
 }
 
 // GobEncode is a custom gob marshaller
-func (m TypesMap) GobEncode() ([]byte, error) {
+func (m Map) GobEncode() ([]byte, error) {
 	w := new(bytes.Buffer)
 	encoder := gob.NewEncoder(w)
 	err := encoder.Encode(m.flags)
@@ -294,7 +294,7 @@ func (m TypesMap) GobEncode() ([]byte, error) {
 }
 
 // GobDecode is custom gob unmarshaller
-func (m *TypesMap) GobDecode(buf []byte) error {
+func (m *Map) GobDecode(buf []byte) error {
 	r := bytes.NewBuffer(buf)
 	decoder := gob.NewDecoder(r)
 	err := decoder.Decode(&m.flags)
@@ -304,7 +304,7 @@ func (m *TypesMap) GobDecode(buf []byte) error {
 	return decoder.Decode(&m.m)
 }
 
-func (m TypesMap) Contains(typ string) bool {
+func (m Map) Contains(typ string) bool {
 	if m.Len() == 0 {
 		return false
 	}
@@ -319,7 +319,7 @@ func (m TypesMap) Contains(typ string) bool {
 // Find applies a predicate function to every contained type.
 // If callback returns true for any of them, this is a result of Find call.
 // False is returned if none of the contained types made pred function return true.
-func (m TypesMap) Find(pred func(typ string) bool) bool {
+func (m Map) Find(pred func(typ string) bool) bool {
 	if m.Len() == 0 {
 		return false
 	}
@@ -339,7 +339,7 @@ func (m TypesMap) Find(pred func(typ string) bool) bool {
 }
 
 // Iterate applies cb to all contained types
-func (m TypesMap) Iterate(cb func(typ string)) {
+func (m Map) Iterate(cb func(typ string)) {
 	if m.Len() == 0 {
 		return
 	}
@@ -359,7 +359,7 @@ func (m TypesMap) Iterate(cb func(typ string)) {
 
 // ArrayElemLazyType returns type of array element. T[] -> T, T[][] -> T[].
 // For *Lazy* type.
-func (m TypesMap) ArrayElemLazyType() TypesMap {
+func (m Map) ArrayElemLazyType() Map {
 	if m.Len() == 0 {
 		return MixedType
 	}
@@ -368,5 +368,5 @@ func (m TypesMap) ArrayElemLazyType() TypesMap {
 	for typ := range m.m {
 		mm[UnwrapArrayOf(typ)] = struct{}{}
 	}
-	return TypesMap{m: mm, flags: m.flags}
+	return Map{m: mm, flags: m.flags}
 }
diff --git a/src/meta/typestring.go b/src/types/typestring.go
similarity index 93%
rename from src/meta/typestring.go
rename to src/types/typestring.go
index 6f3e0ad..2a89a95 100644
--- a/src/meta/typestring.go
+++ b/src/types/typestring.go
@@ -1,4 +1,4 @@
-package meta
+package types
 
 import (
 	"encoding/binary"
@@ -281,7 +281,7 @@ func UnwrapConstant(s string) (constName string) {
 	return unwrap1(s)
 }
 
-func formatType(s string) (res string) {
+func FormatType(s string) (res string) {
 	if s == "" || s[0] >= WMax {
 		return s
 	}
@@ -294,24 +294,24 @@ func formatType(s string) (res string) {
 
 	switch s[0] {
 	case WGlobal:
-		return "global_$" + formatType(UnwrapGlobal(s))
+		return "global_$" + FormatType(UnwrapGlobal(s))
 	case WConstant:
 		return "constant(" + UnwrapConstant(s) + ")"
 	case WArrayOf:
-		return formatType(UnwrapArrayOf(s)) + "[]"
+		return FormatType(UnwrapArrayOf(s)) + "[]"
 	case WElemOf:
-		return "elem(" + formatType(UnwrapElemOf(s)) + ")"
+		return "elem(" + FormatType(UnwrapElemOf(s)) + ")"
 	case WElemOfKey:
 		typ, key := UnwrapElemOfKey(s)
-		return fmt.Sprintf("elem(%s)[%s]", formatType(typ), key)
+		return fmt.Sprintf("elem(%s)[%s]", FormatType(typ), key)
 	case WFunctionCall:
 		return UnwrapFunctionCall(s) + "()"
 	case WInstanceMethodCall:
 		expr, methodName := UnwrapInstanceMethodCall(s)
-		return "(" + formatType(expr) + ")->" + methodName + "()"
+		return "(" + FormatType(expr) + ")->" + methodName + "()"
 	case WInstancePropertyFetch:
 		expr, propertyName := UnwrapInstancePropertyFetch(s)
-		return "(" + formatType(expr) + ")->" + propertyName
+		return "(" + FormatType(expr) + ")->" + propertyName
 	case WBaseMethodParam:
 		index, className, methodName := unwrap3(s)
 		return fmt.Sprintf("param(%s)::%s[%d]", className, methodName, index)
@@ -328,3 +328,11 @@ func formatType(s string) (res string) {
 
 	return "unknown(" + s + ")"
 }
+
+func IsClassType(s string) bool {
+	return strings.HasPrefix(s, `\`) && !IsShapeType(s) && !IsArrayType(s)
+}
+
+func IsShapeType(s string) bool { return strings.HasPrefix(s, `\shape$`) }
+
+func IsArrayType(s string) bool { return strings.HasSuffix(s, `[]`) }


Test patch:
diff --git a/src/linter/cache_test.go b/src/linter/cache_test.go
index 4a80fc9..d2cdc6a 100644
--- a/src/linter/cache_test.go
+++ b/src/linter/cache_test.go
@@ -141,7 +141,7 @@ main();
 		//
 		// If cache encoding changes, there is a very high chance that
 		// encoded data lengh will change as well.
-		wantLen := 5332
+		wantLen := 5316
 		haveLen := buf.Len()
 		if haveLen != wantLen {
 			t.Errorf("cache len mismatch:\nhave: %d\nwant: %d", haveLen, wantLen)
@@ -150,7 +150,7 @@ main();
 		// 2. Check cache "strings" hash.
 		//
 		// It catches new fields in cached types, field renames and encoding of additional named attributes.
-		wantStrings := "cfbe2074534e9b4116dc9e4a629e5fcfba2344c363314643dce1f90156c9b20006a006cb4e7d724d9d5fca6c6034d078d95127a9cbb002f79fb68debc2a59def"
+		wantStrings := "63e0138101dfef86e5fa6c4ba977e34846d92999f6f4248050f3f84bca820970dcbd447a0f044ede1f32ef1f4a04b4acb5c2fb4f13caf50de6220c05e7c1e1ef"
 		haveStrings := collectCacheStrings(buf.String())
 		if haveStrings != wantStrings {
 			t.Errorf("cache strings mismatch:\nhave: %q\nwant: %q", haveStrings, wantStrings)
diff --git a/src/meta/const_value_test.go b/src/meta/const_value_test.go
new file mode 100644
index 0000000..71efacd
--- /dev/null
+++ b/src/meta/const_value_test.go
@@ -0,0 +1,34 @@
+package meta
+
+import "testing"
+
+func TestConstantValueDecodeEncode(t *testing.T) {
+	testCases := []ConstValue{
+		{Type: String, Value: "world"},
+		{Type: Integer, Value: int64(5)},
+		{Type: Float, Value: 5.56},
+		{Type: String, Value: "hello"},
+		{Type: Float, Value: 124.67},
+		{Type: Integer, Value: int64(50000000)},
+	}
+
+	for _, testCase := range testCases {
+		// encode this
+		encoded, err := testCase.GobEncode()
+		if err != nil {
+			t.Errorf("unexpected error \"%s\"", err)
+		}
+
+		// decode this
+		decoded := ConstValue{}
+		err = decoded.GobDecode(encoded)
+		if err != nil {
+			t.Errorf("unexpected error \"%s\"", err)
+		}
+
+		// compare
+		if decoded.Type != testCase.Type || decoded.Value != testCase.Value {
+			t.Error("error decode, objects not equal")
+		}
+	}
+}
diff --git a/src/solver/solver_test.go b/src/solver/solver_test.go
index 8ee669e..b8b84f3 100644
--- a/src/solver/solver_test.go
+++ b/src/solver/solver_test.go
@@ -6,6 +6,7 @@ import (
 	"testing"
 
 	"github.com/VKCOM/noverify/src/meta"
+	"github.com/VKCOM/noverify/src/types"
 )
 
 func resolve(info *meta.Info, typ string) map[string]struct{} {
@@ -25,14 +26,14 @@ func typesEqual(a map[string]struct{}, b string) bool {
 }
 
 func TestSolver(t *testing.T) {
-	tm := meta.NewTypesMap
+	tm := types.NewMap
 
 	sc := meta.NewScope()
 	sc.AddVarName("MC", tm("Memcache"), "global", meta.VarAlwaysDefined)
 
 	fm := meta.NewFunctionsMap()
-	fm.Set(`\array_map`, meta.FuncInfo{Typ: tm(`array|bool|` + meta.WrapFunctionCall(`\my_func`))})
-	fm.Set(`\my_func`, meta.FuncInfo{Typ: tm(meta.WrapFunctionCall(`\array_map`) + `|float`)})
+	fm.Set(`\array_map`, meta.FuncInfo{Typ: tm(`array|bool|` + types.WrapFunctionCall(`\my_func`))})
+	fm.Set(`\my_func`, meta.FuncInfo{Typ: tm(types.WrapFunctionCall(`\array_map`) + `|float`)})
 
 	cmfm := meta.NewFunctionsMap()
 	cmfm.Set(`do_something`, meta.FuncInfo{Typ: tm(`string`)})
@@ -50,19 +51,19 @@ func TestSolver(t *testing.T) {
 	metainfo.AddFunctionsNonLocked("test", fm)
 	metainfo.AddClassesNonLocked("test", cm)
 
-	if typ := resolve(metainfo, meta.WrapFunctionCall(`\my_func`)); !typesEqual(typ, `array|bool|float`) {
+	if typ := resolve(metainfo, types.WrapFunctionCall(`\my_func`)); !typesEqual(typ, `array|bool|float`) {
 		t.Errorf("My func wrong type: %+v", typ)
 	}
 
-	if typ := resolve(metainfo, meta.WrapGlobal(`MC`)); !typesEqual(typ, `Memcache`) {
+	if typ := resolve(metainfo, types.WrapGlobal(`MC`)); !typesEqual(typ, `Memcache`) {
 		t.Errorf("Global $MC wrong: %+v", typ)
 	}
 
-	if typ := resolve(metainfo, meta.WrapStaticPropertyFetch(`\Test`, `$instance`)); !typesEqual(typ, `\Test`) {
+	if typ := resolve(metainfo, types.WrapStaticPropertyFetch(`\Test`, `$instance`)); !typesEqual(typ, `\Test`) {
 		t.Errorf(`\Test::$instance wrong: %+v`, typ)
 	}
 
-	if typ := resolve(metainfo, meta.WrapInstanceMethodCall(meta.WrapStaticPropertyFetch(`\Test`, `$instance`), `do_something`)); !typesEqual(typ, `string`) {
+	if typ := resolve(metainfo, types.WrapInstanceMethodCall(types.WrapStaticPropertyFetch(`\Test`, `$instance`), `do_something`)); !typesEqual(typ, `string`) {
 		t.Errorf(`\Test::$instance::do_something() wrong: %+v`, typ)
 	}
 }
diff --git a/src/tests/bench/bench_test.go b/src/tests/bench/bench_test.go
index 2005cff..75b8178 100644
--- a/src/tests/bench/bench_test.go
+++ b/src/tests/bench/bench_test.go
@@ -8,6 +8,7 @@ import (
 	"github.com/VKCOM/noverify/src/linttest"
 	"github.com/VKCOM/noverify/src/meta"
 	"github.com/VKCOM/noverify/src/solver"
+	"github.com/VKCOM/noverify/src/types"
 )
 
 func BenchmarkExprType(b *testing.B) {
@@ -53,7 +54,7 @@ func BenchmarkExprType(b *testing.B) {
 	st := &meta.ClassParseState{Info: l.MetaInfo()}
 	sc := meta.NewScope()
 
-	sc.AddVarName("foo", meta.NewTypesMap(`\Foo|int|null`), "test", meta.VarAlwaysDefined)
+	sc.AddVarName("foo", types.NewMap(`\Foo|int|null`), "test", meta.VarAlwaysDefined)
 
 	b.Run("simplevar", func(b *testing.B) {
 		for i := 0; i < b.N; i++ {
diff --git a/src/tests/checkers/basic_test.go b/src/tests/checkers/basic_test.go
index 2d6c8cf..6a984fe 100644
--- a/src/tests/checkers/basic_test.go
+++ b/src/tests/checkers/basic_test.go
@@ -9,7 +9,7 @@ import (
 	"github.com/VKCOM/noverify/src/cmd"
 	"github.com/VKCOM/noverify/src/linter"
 	"github.com/VKCOM/noverify/src/linttest"
-	"github.com/VKCOM/noverify/src/meta"
+	"github.com/VKCOM/noverify/src/types"
 )
 
 func TestBadString(t *testing.T) {
@@ -2020,7 +2020,7 @@ func TestArrayUnion(t *testing.T) {
 		t.Errorf("Unexpected number of types: %d, excepted 2", l)
 	}
 
-	if !fnMixedArr.Typ.Equals(meta.NewTypesMap("int[]|string[]")) {
+	if !fnMixedArr.Typ.Equals(types.NewMap("int[]|string[]")) {
 		// NOTE: this is how code works right now. It currently treat a[]|b[] as (a|b)[]
 		t.Errorf("Wrong type: %s, expected int[]|string[]", fnMixedArr.Typ)
 	}
diff --git a/src/tests/exprtype/exprtype_test.go b/src/tests/exprtype/exprtype_test.go
index 3724fe5..12f18d2 100644
--- a/src/tests/exprtype/exprtype_test.go
+++ b/src/tests/exprtype/exprtype_test.go
@@ -12,6 +12,7 @@ import (
 	"github.com/VKCOM/noverify/src/linter"
 	"github.com/VKCOM/noverify/src/linttest"
 	"github.com/VKCOM/noverify/src/meta"
+	"github.com/VKCOM/noverify/src/types"
 	"github.com/VKCOM/noverify/src/workspace"
 )
 
@@ -37,7 +38,7 @@ import (
 
 var (
 	exprTypeResultMu sync.Mutex
-	exprTypeResult   map[ir.Node]meta.TypesMap
+	exprTypeResult   map[ir.Node]types.Map
 )
 
 func TestExprTypeListOverArray(t *testing.T) {
@@ -2874,7 +2875,7 @@ func exprTypeTestImpl(t *testing.T, params *exprTypeTestParams, kphp bool) {
 	l.MetaInfo().SetIndexingComplete(true)
 
 	// Reset results map and run expr type collector.
-	exprTypeResult = map[ir.Node]meta.TypesMap{}
+	exprTypeResult = map[ir.Node]types.Map{}
 	result := linttest.ParseTestFile(t, l, "exprtype.php", params.code)
 
 	// Check that collected types are identical to the expected types.
diff --git a/src/tests/checkers/typesmap_test.go b/src/types/typesmap_test.go
similarity index 55%
rename from src/tests/checkers/typesmap_test.go
rename to src/types/typesmap_test.go
index 39a3760..6762a62 100644
--- a/src/tests/checkers/typesmap_test.go
+++ b/src/types/typesmap_test.go
@@ -1,20 +1,18 @@
-package checkers_test
+package types
 
 import (
 	"testing"
-
-	"github.com/VKCOM/noverify/src/meta"
 )
 
 func TestEqualsMatching(t *testing.T) {
-	testCases := [][]meta.TypesMap{
+	testCases := [][]Map{
 		{
-			meta.NewEmptyTypesMap(1),
-			meta.NewEmptyTypesMap(1),
+			NewEmptyMap(1),
+			NewEmptyMap(1),
 		},
 		{
-			meta.NewTypesMapFromMap(map[string]struct{}{"string": {}, "int": {}}),
-			meta.NewTypesMapFromMap(map[string]struct{}{"string": {}, "int": {}}),
+			NewMapFromMap(map[string]struct{}{"string": {}, "int": {}}),
+			NewMapFromMap(map[string]struct{}{"string": {}, "int": {}}),
 		},
 	}
 
@@ -29,14 +27,14 @@ func TestEqualsMatching(t *testing.T) {
 }
 
 func TestEqualNonMatching(t *testing.T) {
-	testCases := [][]meta.TypesMap{
+	testCases := [][]Map{
 		{
-			meta.NewEmptyTypesMap(1),
-			meta.NewTypesMapFromMap(map[string]struct{}{"int": {}}),
+			NewEmptyMap(1),
+			NewMapFromMap(map[string]struct{}{"int": {}}),
 		},
 		{
-			meta.NewTypesMapFromMap(map[string]struct{}{"string": {}}),
-			meta.NewTypesMapFromMap(map[string]struct{}{"int": {}}),
+			NewMapFromMap(map[string]struct{}{"string": {}}),
+			NewMapFromMap(map[string]struct{}{"int": {}}),
 		},
 	}
 
diff --git a/src/meta/encoding_test.go b/src/types/typestring_test.go
similarity index 74%
rename from src/meta/encoding_test.go
rename to src/types/typestring_test.go
index ab7e64f..60b10e3 100644
--- a/src/meta/encoding_test.go
+++ b/src/types/typestring_test.go
@@ -1,4 +1,4 @@
-package meta
+package types
 
 import (
 	"strings"
@@ -47,7 +47,7 @@ func TestTypeEncoding(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		stringified := formatType(test.wrapped)
+		stringified := FormatType(test.wrapped)
 		if stringified != test.stringified {
 			t.Errorf("formatType %q:\nhave: %q\nwant: %q",
 				test.stringified, stringified, test.stringified)
@@ -60,7 +60,7 @@ func TestTypeEncoding(t *testing.T) {
 		}
 
 		const typeSuffix = "|zzz|zzz[]"
-		m := NewTypesMap(test.stringified + typeSuffix)
+		m := NewMap(test.stringified + typeSuffix)
 		if m.String() != test.stringified+typeSuffix {
 			t.Errorf("type map string mismatched for %q:\nhave: %q\nwant: %q",
 				test.stringified, m.String(), test.stringified+typeSuffix)
@@ -72,7 +72,7 @@ func TestTypeEncoding(t *testing.T) {
 			t.Errorf("failed to gob-encode %q: %v", test.stringified, err)
 			continue
 		}
-		decoded := NewEmptyTypesMap(m.Len())
+		decoded := NewEmptyMap(m.Len())
 		if err := decoded.GobDecode(encoded); err != nil {
 			t.Errorf("failed to gob-decode %q: %v", test.stringified, err)
 			continue
@@ -94,34 +94,3 @@ func TestTypeEncoding(t *testing.T) {
 		}
 	}
 }
-
-func TestConstantValueDecodeEncode(t *testing.T) {
-	testCases := []ConstValue{
-		{Type: String, Value: "world"},
-		{Type: Integer, Value: int64(5)},
-		{Type: Float, Value: 5.56},
-		{Type: String, Value: "hello"},
-		{Type: Float, Value: 124.67},
-		{Type: Integer, Value: int64(50000000)},
-	}
-
-	for _, testCase := range testCases {
-		// encode this
-		encoded, err := testCase.GobEncode()
-		if err != nil {
-			t.Errorf("unexpected error \"%s\"", err)
-		}
-
-		// decode this
-		decoded := ConstValue{}
-		err = decoded.GobDecode(encoded)
-		if err != nil {
-			t.Errorf("unexpected error \"%s\"", err)
-		}
-
-		// compare
-		if decoded.Type != testCase.Type || decoded.Value != testCase.Value {
-			t.Error("error decode, objects not equal")
-		}
-	}
-}