func stringToType(s string) *typeObj {
	primitive := &typeObj{}
	primitive.fullName = s
	primitive.name = s
	primitive.isPrimitive = prim.IsPrimitiveType(s)
	primitive.isNumber = prim.IsNumberType(s)
	return primitive
}
Is this still needed?
package typechecker
import (
	"fmt"
	"knox/ast"
	"knox/lexer"
	"knox/token"
)
var prim primitives  
var currentFunc *ast.Node   
var currentClass *ast.Node  
func Analyze(node *ast.Node) {
	prim.Init()
	typecheck(node)
}
func typecheck(node *ast.Node) {
	for _, child := range node.Children {
		if child.Type == ast.EXPRESSION {
			exprType := getType(&child.Children[0])
			if node.Type == ast.VARDECL {
				leftType := declType(node)
				if !compareTypes(leftType, exprType) {  
					abortMsgf(node, "Mismatched types: %s and %s", leftType.fullName, exprType.fullName)
				}
				if leftType.isClass && !child.Symbols.IsDeclared(leftType.name) {
					abortMsgf(node, "Undeclared type: %s", leftType.name)
				}
			} else if node.Type == ast.VARASSIGN {
				leftType := getType(&node.Children[0])
				if !compareTypes(leftType, exprType) {  
					abortMsgf(node, "Mismatched types: %s and %s", leftType.fullName, exprType.fullName)
				}
			} else if node.Type == ast.IFSTATEMENT || node.Type == ast.WHILESTATEMENT {
				if !compareTypes(exprType, prim.typeBOOL) {
					abortMsg(node, "Conditionals require boolean expressions.")
				}
			}
		} else if child.Type == ast.JUMPSTATEMENT {
			if child.TokenStart.Literal == "return" {
				returnType := buildTypeList(&child)
				funcReturnType := buildReturnList(&currentFunc.Children[2])
				if compareTypes(funcReturnType, prim.typeVOID) && returnType.fullName == "" {  
				} else if !compareTypes(&returnType.inner[0], &funcReturnType.inner[0]) {
					abortMsgf(node, "Incorrect return type: %v when expecting %v.", returnType.inner[0].fullName, funcReturnType.inner[0].fullName)
				}
			}
		} else if node.Type == ast.FORSTATEMENT {
			left := declType(&node.Children[0])
			right := getType(&node.Children[1])
			fmt.Println("Debugging...", right.fullName, right.isList, right.isClass, right.isPrimitive)
			if !right.isList && !right.isMap {
				abortMsg(node, "For loop requires a list or map")
			}
			if !compareTypes(left, &right.inner[0]) {
				abortMsg(node, "For loop element is incorrect type")
			}
		} else if child.Type == ast.FUNCDECL {
			currentFunc = &child
			typecheck(&child)
		} else if child.Type == ast.CLASS {
			currentClass = &child
			typecheck(&child)
		} else if child.Type == ast.LEFTEXPR {
			only := getType(&child.Children[0])
			if !compareTypes(only, prim.typeVOID) {
				abortMsg(node, "Expression must be of void type, not "+only.fullName)
			}
		} else {
			typecheck(&child)
		}
	}
}
func abortMsg(node *ast.Node, msg string) {
	fmt.Printf("Type error: %v. Line %v.\n", msg, node.TokenStart.Line)
	panic("Aborted.\n")
}
func abortMsgf(node *ast.Node, msg string, args ...interface{}) {
	s := fmt.Sprintf(". Line %v.\n", node.TokenStart.Line)
	fmt.Printf("Type error: "+msg+s, args...)
	panic("Aborted.\n")
}
func compareTypes(a *typeObj, b *typeObj) bool {
	if a.isLiteral || b.isLiteral {
		if a.isNumber && b.isNumber {
			return true
		}
	}
	if (a.isClass || a.isContainer) && b.fullName == "nil" {
		return true
	}
	if a.isContainer && b.isContainer {
		if len(a.inner) != len(b.inner) {
			return false
		}
		for i := range a.inner {
			if compareTypes(&a.inner[i], &b.inner[i]) == false {
				return false
			}
		}
		return true  
	}
	return a.fullName == b.fullName
}