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.IsGeneric() {
break
}
if n.Symbol == nil || n.Symbol.Signature == nil {
w.errors = append(w.errors, "function symbol has nil type")
}
for _, arg := range n.Args {
if arg.Default != nil {
w.walkExpr(arg.Default)
}
}
if n.Body != nil {
w.walkNode(n.Body)
}
case *parser.BlockNode:
for _, stmt := range n.Body {
w.walkNode(stmt)
}
case *parser.DeferNode:
w.walkNode(n.Action)
case *parser.DeclarationNode:
if len(n.GenericParameters) != 0 {
break
}
if n.Name == "_" {
if n.Value != nil {
w.walkExpr(n.Value)
}
break
}
if n.Symbol != nil && n.Symbol.InlineComptime {
break
}
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.MultiDeclarationNode:
for _, sym := range n.Symbols {
if sym != nil {
w.checkType(sym.Type)
}
}
w.walkExpr(n.Value)
case *parser.AssignmentNode:
if len(n.Assignees) > 0 {
for _, target := range n.Assignees {
if id, ok := target.(*parser.IdentifierNode); !ok || id.Name != "_" {
w.walkExpr(target)
}
}
} else if id, ok := n.Assignee.(*parser.IdentifierNode); !ok || id.Name != "_" {
w.walkExpr(n.Assignee)
}
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.MatchNode:
for _, subject := range n.Subjects {
w.walkExpr(subject)
}
for _, arm := range n.Arms {
if arm.Guard != nil {
w.walkExpr(arm.Guard)
}
w.walkExpr(arm.Body)
}
case *parser.ForNode:
w.walkNode(n.Body)
case *parser.ControlKeywordNode:
for _, value := range n.ReturnValues {
w.walkExpr(value)
}
case parser.ExpressionNode:
w.walkExpr(n)
}
}
func (w *debugWalker) walkExpr(expr parser.ExpressionNode) {
if expr.GetType() == nil {
w.errors = append(w.errors, fmt.Sprintf("expression %T at %v has nil type", expr, expr.GetLoc()))
return
}
w.checkType(expr.GetType())
switch n := expr.(type) {
case *parser.LambdaNode:
if n.Function == nil {
w.errors = append(w.errors, "lambda has no generated function")
} else {
w.walkNode(n.Function)
}
case *parser.IdentifierNode:
case *parser.IntegerLiteralNode,
*parser.FloatLiteralNode,
*parser.BoolLiteralNode,
*parser.StringLiteralNode,
*parser.EmbedNode,
*parser.CStringLiteralNode,
*parser.CharLiteralNode,
*parser.NilLiteralNode,
*parser.EnumLiteralNode,
*parser.NoInitializerNode:
case *parser.CastNode:
w.walkExpr(n.Operand)
w.checkType(n.Type)
case *parser.ReprNode:
w.walkExpr(n.Operand)
case *parser.UnaryOpNode:
w.walkExpr(n.Operand)
case *parser.BinaryOpNode:
w.walkExpr(n.Operand1)
w.walkExpr(n.Operand2)
case *parser.FunctionCallNode:
// Instance method calls are resolved directly to their function symbol;
// the member-shaped callee is syntax for receiver dispatch, not a bound
// method expression with its own type.
if n.Symbol == nil && n.TaggedUnionType == nil {
w.walkExpr(n.Callee)
}
for _, arg := range n.Args {
w.walkExpr(arg)
}
case *parser.InlineAsmNode:
for _, output := range n.Outputs {
w.checkType(output.Type)
}
for _, input := range n.Inputs {
w.walkExpr(input.Value)
}
case *parser.IndexExprNode:
w.walkExpr(n.Subject)
w.walkExpr(n.Index)
case *parser.SliceExprNode:
w.walkExpr(n.Subject)
if n.Start != nil {
w.walkExpr(n.Start)
}
if n.End != nil {
w.walkExpr(n.End)
}
case *parser.FieldAccessNode:
if n.MethodSymbol == nil && n.TaggedUnionType == nil {
w.walkExpr(n.Subject)
}
case *parser.SliceLiteralNode:
if n.RepeatValue != nil {
w.walkExpr(n.RepeatValue)
w.walkExpr(n.RepeatAmount)
break
}
for _, el := range n.Elements {
w.walkExpr(el)
}
case *parser.BlockNode:
for _, child := range n.Body {
w.walkNode(child)
}
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.MatchNode:
for _, subject := range n.Subjects {
w.walkExpr(subject)
}
for _, arm := range n.Arms {
if arm.Guard != nil {
w.walkExpr(arm.Guard)
}
w.walkExpr(arm.Body)
}
case *parser.SizeOfNode:
if n.Expression != nil {
w.walkExpr(n.Expression)
}
w.checkType(n.Type)
case *parser.SizeOfExprNode:
w.walkExpr(n.Operand)
w.checkType(n.Operand.GetType())
w.checkType(n.Type)
case *parser.AlignOfNode:
if n.Expression != nil {
w.walkExpr(n.Expression)
}
w.checkType(n.OperandType)
w.checkType(n.Type)
case *parser.OffsetOfNode:
w.checkType(n.OperandType)
w.checkType(n.Type)
case *parser.StructLiteralNode:
for _, field := range n.Fields {
w.walkExpr(field.R)
}
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, types.UnresolvedEnum:
w.errors = append(w.errors, "untyped type remains after validation")
case types.ErrorType:
w.errors = append(w.errors, "ErrorType remains after validation")
case types.SequenceType:
w.errors = append(w.errors, "unresolved sequence literal type remains after validation")
case types.SliceType:
w.checkType(tt.Base)
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.EnumType:
case types.UnionType:
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
}
}