package sema import ( "fmt" "github.com/marzeq/qk/parser" "github.com/marzeq/qk/types" ) func (a *Analyser) DebugCheck(root *parser.RootNode) { w := &debugWalker{} w.walkNode(root) if len(w.errors) > 0 { for _, err := range w.errors { fmt.Println("DEBUG SEMANTIC ERROR:", err) } panic("semantic invariants violated") } } type debugWalker struct { errors []string } func (w *debugWalker) walkNode(node parser.Node) { switch n := node.(type) { case *parser.RootNode: for _, stmt := range n.Body { w.walkNode(stmt) } case *parser.FunctionDefNode: if n.Symbol == nil || n.Symbol.Signature == nil { w.errors = append(w.errors, "function symbol has nil type") } if n.Body != nil { w.walkNode(n.Body) } case *parser.BlockNode: for _, stmt := range n.Body { w.walkNode(stmt) } case *parser.DeclarationNode: if n.Symbol == nil || n.Symbol.Type == nil { w.errors = append(w.errors, "declaration symbol has nil type") } else { w.checkType(n.Symbol.Type) } if n.Value != nil { w.walkExpr(n.Value) } case *parser.AssignmentNode: w.walkExpr(n.Assignee) w.walkExpr(n.Value) case *parser.ArrayAssignmentNode: w.walkExpr(n.Assignee) w.walkExpr(n.Index) w.walkExpr(n.Value) case *parser.IfNode: w.walkExpr(n.IfBranch.Condition) w.walkNode(n.IfBranch.Node) for _, elif := range n.ElseIfBranches { w.walkExpr(elif.Condition) w.walkNode(elif.Node) } if n.ElseBranch != nil { w.walkNode(n.ElseBranch) } case *parser.ForNode: w.walkNode(n.Body) case *parser.ControlKeywordNode: if n.ReturnValue != nil { w.walkExpr(n.ReturnValue) } case parser.ExpressionNode: w.walkExpr(n) } } func (w *debugWalker) walkExpr(expr parser.ExpressionNode) { if expr.GetType() == nil { w.errors = append(w.errors, "expression has nil type") return } w.checkType(expr.GetType()) switch n := expr.(type) { case *parser.IdentifierNode: case *parser.IntegerLiteralNode, *parser.FloatLiteralNode, *parser.BoolLiteralNode, *parser.StringLiteralNode, *parser.CharLiteralNode, *parser.NilLiteralNode: case *parser.CastNode: w.walkExpr(n.Operand) w.checkType(n.Type) case *parser.UnaryOpNode: w.walkExpr(n.Operand) case *parser.BinaryOpNode: w.walkExpr(n.Operand1) w.walkExpr(n.Operand2) case *parser.FunctionCallNode: for _, arg := range n.Args { w.walkExpr(arg) } case *parser.IndexExprNode: w.walkExpr(n.Subject) w.walkExpr(n.Index) case *parser.ArrayLiteralNode: for _, el := range n.Elements { w.walkExpr(el) } case *parser.IfExprNode: w.walkExpr(n.IfBranch.Condition) w.walkExpr(n.IfBranch.Node) for _, elif := range n.ElseIfBranches { w.walkExpr(elif.Condition) w.walkExpr(elif.Node) } if n.ElseBranch != nil { w.walkExpr(n.ElseBranch) } default: w.errors = append(w.errors, fmt.Sprintf("unhandled expression type %T", expr)) } } func (w *debugWalker) checkType(t types.Type) { switch tt := t.(type) { case types.UntypedInt, types.UntypedFloat: w.errors = append(w.errors, "untyped type remains after validation") case types.ErrorType: w.errors = append(w.errors, "ErrorType remains after validation") case types.ArrayType: w.checkType(tt.Base) case types.PointerType: w.checkType(tt.Base) case types.StructType: for _, field := range tt.Fields { w.checkType(field.R) } case types.FunctionType: for _, p := range tt.Parameters { w.checkType(p) } w.checkType(tt.ReturnType) default: // PrimitiveType is fine } }