package sema import ( "fmt" "github.com/marzeq/qk/parser" "github.com/marzeq/qk/shared" "github.com/marzeq/qk/symbols" "github.com/marzeq/qk/types" ) type Validator struct { analyser *Analyser currentFunction *symbols.Symbol errors []error } func (a *Analyser) NewValidator() *Validator { return &Validator{analyser: a} } func (v *Validator) errorf(node parser.Node, format string, args ...any) { v.errors = append(v.errors, shared.NewError(node.GetLoc(), format, args...)) } func (v *Validator) ValidateModule(root *parser.RootNode) { v.validateNode(root) } func (v *Validator) Errors() []error { return v.errors } func (v *Validator) validateNode(node parser.Node) { switch n := node.(type) { case *parser.RootNode: for _, stmt := range n.Body { v.validateNode(stmt) } case *parser.ImportNode, *parser.ModuleNode: case *parser.FunctionDefNode: prev := v.currentFunction v.currentFunction = n.Symbol if n.Body != nil { v.validateNode(n.Body) } else { if n.ExternFrom == "" { v.errorf(n, "function declaration missing body or extern") return } } v.currentFunction = prev case *parser.BlockNode: for _, stmt := range n.Body { switch s := stmt.(type) { case *parser.DeclarationNode: v.finaliseDeclaration(s) default: v.validateNode(s) } } case *parser.DeclarationNode: v.finaliseDeclaration(n) case *parser.AssignmentNode: v.validateAssignment(n) case *parser.ArrayAssignmentNode: v.validateArrayAssignment(n) case *parser.IfNode: v.validateIf(n) case *parser.ForNode: v.validateNode(n.Body) case *parser.ControlKeywordNode: if n.ReturnValue != nil { v.validateReturn(n) } case parser.ExpressionNode: v.validateExpr(n) case *parser.TypeAliasNode: // pass default: panic(fmt.Sprintf("unhandled node type %T", n)) } } func (v *Validator) finaliseDeclaration(n *parser.DeclarationNode) { if n.Value == nil { return } v.validateExpr(n.Value) valueType := n.Value.GetType() if n.TypeNode != nil { declared := v.analyser.resolveTypeNode(n.TypeNode) if !valueType.CanCoerceTo(declared) { v.errorf(n, "cannot assign %v to %v", valueType, declared) return } if !valueType.Equals(declared) { n.Value = v.createCast(n.Value, declared) } n.Symbol.Type = declared return } if types.IsUntyped(valueType) { valueType = types.DefaultUntyped(valueType) } n.Symbol.Type = valueType } func (v *Validator) validateAssignment(n *parser.AssignmentNode) { if !v.validateLValue(n.Assignee) { return } v.validateExpr(n.Assignee) v.validateExpr(n.Value) lhsType := n.Assignee.GetType() rhsType := n.Value.GetType() if !rhsType.CanCoerceTo(lhsType) { v.errorf(n, "cannot assign %v to %v", rhsType, lhsType) return } if !rhsType.Equals(lhsType) { n.Value = v.createCast(n.Value, lhsType) } } func (v *Validator) validateLValue(expr parser.ExpressionNode) bool { switch e := expr.(type) { case *parser.IdentifierNode: if !e.Symbol.Mutable { v.errorf(e, "cannot assign to immutable symbol") return false } case *parser.UnaryOpNode: if e.Op == parser.UnaryOpDereference { v.validateExpr(e.Operand) return true } v.errorf(expr, "invalid assignment target") return false default: v.errorf(expr, "invalid assignment target!") return false } return true } func (v *Validator) validateArrayAssignment(n *parser.ArrayAssignmentNode) { v.validateExpr(n.Assignee) v.validateExpr(n.Index) v.validateExpr(n.Value) containerType := n.Assignee.GetType() var base types.Type switch t := containerType.(type) { case types.ArrayType: base = t.Base case types.PointerType: base = t.Base default: v.errorf(n, "cannot index into non-array type") return } valueType := n.Value.GetType() if !valueType.CanCoerceTo(base) { v.errorf(n, "cannot assign %v to %v", valueType, base) return } if !valueType.Equals(base) { n.Value = v.createCast(n.Value, base) } } func (v *Validator) validateIf(n *parser.IfNode) { v.validateExpr(n.IfBranch.Condition) if !n.IfBranch.Condition.GetType().Equals(types.PrimitiveBool) { v.errorf(n, "if condition must be bool") } v.validateNode(n.IfBranch.Node) for _, elif := range n.ElseIfBranches { v.validateExpr(elif.Condition) if !elif.Condition.GetType().Equals(types.PrimitiveBool) { v.errorf(n, "elseif condition must be bool") } v.validateNode(elif.Node) } if n.ElseBranch != nil { v.validateNode(n.ElseBranch) } } func (v *Validator) validateReturn(n *parser.ControlKeywordNode) { if v.currentFunction == nil { v.errorf(n, "return outside of function") return } expected := v.currentFunction.Signature.ReturnType valueType := n.ReturnValue.GetType() if !valueType.CanCoerceTo(expected) { v.errorf(n, "invalid return type") return } if !valueType.Equals(expected) { n.ReturnValue = v.createCast(n.ReturnValue, expected) } } func (v *Validator) validateExpr(node parser.ExpressionNode) { if _, ok := node.GetType().(types.ErrorType); ok { return } switch n := node.(type) { case *parser.FunctionCallNode: if n.Symbol == nil || n.Symbol.Kind != symbols.SymbolKindFunction { v.errorf(n, "not callable") return } sig := n.Symbol.Signature if !sig.Variadic && len(n.Args) != len(sig.Parameters) { v.errorf(n, "wrong number of arguments") return } for i, arg := range n.Args { v.validateExpr(arg) if i >= len(sig.Parameters) { continue } paramType := sig.Parameters[i] argType := arg.GetType() if !argType.CanCoerceTo(paramType) { v.errorf(n, "cannot pass %v to %v", argType, paramType) continue } if !argType.Equals(paramType) { n.Args[i] = v.createCast(arg, paramType) } } case *parser.CastNode: v.validateExpr(n.Operand) if !n.Operand.GetType().CanCastTo(n.Type) { v.errorf(n, "invalid cast") } case *parser.UnaryOpNode: v.validateExpr(n.Operand) operandType := n.Operand.GetType() switch n.Op { case parser.UnaryOpLogicalNot: if !operandType.Equals(types.PrimitiveBool) { v.errorf(n, "operator not requires bool") } case parser.UnaryOpNegate: if !types.IsNumeric(operandType) { v.errorf(n, "operator - requires numeric type") } case parser.UnaryOpReference: case parser.UnaryOpDereference: if _, ok := operandType.(types.PointerType); !ok { v.errorf(n, "cannot dereference non-pointer type") } case parser.UnaryOpArrayLen: switch operandType.(type) { case types.ArrayType: // OK default: v.errorf(n, "[] operator requires array or pointer") } default: v.errorf(n, "unknown unary operator") } case *parser.BinaryOpNode: v.validateExpr(n.Operand1) v.validateExpr(n.Operand2) t1 := n.Operand1.GetType() t2 := n.Operand2.GetType() switch n.Op { case parser.BinaryOpAdd, parser.BinaryOpSubtract, parser.BinaryOpMultiply, parser.BinaryOpDivide, parser.BinaryOpModulo: if !types.IsNumeric(t1) || !types.IsNumeric(t2) { v.errorf(n, "arithmetic operators require numeric operands") } case parser.BinaryOpLogicalAnd, parser.BinaryOpLogicalOr: if !t1.Equals(types.PrimitiveBool) || !t2.Equals(types.PrimitiveBool) { v.errorf(n, "logical operators require bool operands") } case parser.BinaryOpEqual, parser.BinaryOpNotEqual: if !t1.CanCoerceTo(t2) && !t2.CanCoerceTo(t1) { v.errorf(n, "incompatible types for comparison: %v and %v", t1, t2) } case parser.BinaryOpLess, parser.BinaryOpLessEqual, parser.BinaryOpGreater, parser.BinaryOpGreaterEqual: if !types.IsNumeric(t1) || !types.IsNumeric(t2) { v.errorf(n, "ordering operators require numeric operands") } default: v.errorf(n, "unknown binary operator") } case *parser.IndexExprNode: v.validateExpr(n.Subject) v.validateExpr(n.Index) indexType := n.Index.GetType() if !types.IsInteger(indexType) { v.errorf(n, "index must be integer") } switch n.Subject.GetType().(type) { case types.ArrayType, types.PointerType: // OK default: v.errorf(n, "cannot index into non-array type") } case *parser.IfExprNode: v.validateExpr(n.IfBranch.Condition) if !n.IfBranch.Condition.GetType().Equals(types.PrimitiveBool) { v.errorf(n, "if expression condition must be bool") } v.validateExpr(n.IfBranch.Node) for _, elif := range n.ElseIfBranches { v.validateExpr(elif.Condition) if !elif.Condition.GetType().Equals(types.PrimitiveBool) { v.errorf(n, "elseif condition must be bool") } v.validateExpr(elif.Node) } if n.ElseBranch != nil { v.validateExpr(n.ElseBranch) } case *parser.GivenExprNode: v.validateNode(n.Block) v.validateExpr(n.FinalExpr) case *parser.ArrayLiteralNode: var common types.Type = nil for _, el := range n.Elements { v.validateExpr(el) if common == nil { common = el.GetType() } else { common = types.PromoteNumeric(common, el.GetType()) if _, isErr := common.(types.ErrorType); isErr { v.errorf(n, "array element type mismatch") return } } } if types.IsUntyped(common) { common = types.DefaultUntyped(common) } n.Type = types.ArrayType{ Base: common, Size: len(n.Elements), } for i, el := range n.Elements { if !el.GetType().Equals(common) { n.Elements[i] = v.createCast(el, common) } } case *parser.IntegerLiteralNode, *parser.FloatLiteralNode, *parser.BoolLiteralNode, *parser.StringLiteralNode, *parser.CharLiteralNode, *parser.NilLiteralNode, *parser.IdentifierNode, *parser.ModuleAccessNode, *parser.SizeOfNode: // nothing to validate default: panic(fmt.Sprintf("unhandled expression type %T", n)) } if types.IsUntyped(node.GetType()) { node.SetType(types.DefaultUntyped(node.GetType())) } } func (v *Validator) createCast(node parser.ExpressionNode, target types.Type) parser.ExpressionNode { if node.GetType().Equals(target) { return node } switch n := node.(type) { case *parser.IntegerLiteralNode: def := types.DefaultUntyped(n.GetType()) if types.IsUntyped(n.GetType()) && target.Equals(def) { n.SetType(def) } return n case *parser.FloatLiteralNode: def := types.DefaultUntyped(n.GetType()) if types.IsUntyped(n.GetType()) && target.Equals(def) { n.SetType(def) } return n } return &parser.CastNode{ Operand: node, Type: target, } }