repos / gbc

GBC - Go B Compiler
git clone https://github.com/xplshn/gbc.git

gbc / pkg / typeChecker
xplshn  ·  2025-08-16

typeChecker.go

Go
  1package typeChecker
  2
  3import (
  4	"fmt"
  5	"strings"
  6
  7	"github.com/xplshn/gbc/pkg/ast"
  8	"github.com/xplshn/gbc/pkg/config"
  9	"github.com/xplshn/gbc/pkg/token"
 10	"github.com/xplshn/gbc/pkg/util"
 11)
 12
 13type Symbol struct {
 14	Name   string
 15	Type   *ast.BxType
 16	IsFunc bool
 17	IsType bool
 18	Node   *ast.Node
 19	Next   *Symbol
 20}
 21
 22type Scope struct {
 23	Symbols *Symbol
 24	Parent  *Scope
 25}
 26
 27type TypeChecker struct {
 28	currentScope *Scope
 29	currentFunc  *ast.FuncDeclNode
 30	globalScope  *Scope
 31	cfg          *config.Config
 32	resolving    map[*ast.BxType]bool
 33	wordSize     int
 34}
 35
 36func NewTypeChecker(cfg *config.Config) *TypeChecker {
 37	globalScope := newScope(nil)
 38	return &TypeChecker{
 39		currentScope: globalScope,
 40		globalScope:  globalScope,
 41		cfg:          cfg,
 42		resolving:    make(map[*ast.BxType]bool),
 43		wordSize:     cfg.WordSize,
 44	}
 45}
 46
 47func newScope(parent *Scope) *Scope { return &Scope{Parent: parent} }
 48func (tc *TypeChecker) enterScope()    { tc.currentScope = newScope(tc.currentScope) }
 49func (tc *TypeChecker) exitScope() {
 50	if tc.currentScope.Parent != nil {
 51		tc.currentScope = tc.currentScope.Parent
 52	}
 53}
 54
 55func (tc *TypeChecker) addSymbol(node *ast.Node) *Symbol {
 56	var name string
 57	var typ *ast.BxType
 58	isFunc, isType := false, false
 59
 60	switch d := node.Data.(type) {
 61	case ast.VarDeclNode:
 62		name, typ = d.Name, d.Type
 63	case ast.FuncDeclNode:
 64		name, typ, isFunc = d.Name, d.ReturnType, true
 65	case ast.TypeDeclNode:
 66		name, typ, isType = d.Name, d.Type, true
 67	case ast.ExtrnDeclNode:
 68		for _, nameNode := range d.Names {
 69			ident := nameNode.Data.(ast.IdentNode)
 70			if tc.findSymbol(ident.Name, false) == nil {
 71				sym := &Symbol{Name: ident.Name, Type: ast.TypeUntyped, IsFunc: true, Node: node, Next: tc.currentScope.Symbols}
 72				tc.currentScope.Symbols = sym
 73			}
 74		}
 75		return nil
 76	case ast.IdentNode: // untyped function parameters
 77		name, typ = d.Name, ast.TypeUntyped
 78	default:
 79		return nil
 80	}
 81
 82	if typ == nil {
 83		typ = ast.TypeUntyped
 84	}
 85
 86	if existing := tc.findSymbol(name, isType); existing != nil && tc.currentScope == tc.globalScope {
 87		isExistingExtrn := existing.Node != nil && existing.Node.Type == ast.ExtrnDecl
 88		if isExistingExtrn || (existing.IsFunc && !isFunc && existing.Type.Kind == ast.TYPE_UNTYPED) {
 89			existing.Type, existing.IsFunc, existing.IsType, existing.Node = typ, isFunc, isType, node
 90			return existing
 91		}
 92		util.Warn(tc.cfg, config.WarnExtra, node.Tok, "Redefinition of '%s'", name)
 93		existing.Type, existing.IsFunc, existing.IsType, existing.Node = typ, isFunc, isType, node
 94		return existing
 95	}
 96
 97	sym := &Symbol{Name: name, Type: typ, IsFunc: isFunc, IsType: isType, Node: node, Next: tc.currentScope.Symbols}
 98	tc.currentScope.Symbols = sym
 99	return sym
100}
101
102func (tc *TypeChecker) findSymbol(name string, findTypes bool) *Symbol {
103	for s := tc.currentScope; s != nil; s = s.Parent {
104		for sym := s.Symbols; sym != nil; sym = sym.Next {
105			if sym.Name == name && sym.IsType == findTypes {
106				return sym
107			}
108		}
109	}
110	return nil
111}
112
113func (tc *TypeChecker) getSizeof(typ *ast.BxType) int64 {
114	if typ == nil || typ.Kind == ast.TYPE_UNTYPED {
115		return int64(tc.wordSize)
116	}
117	switch typ.Kind {
118	case ast.TYPE_VOID:
119		return 0
120	case ast.TYPE_POINTER:
121		return int64(tc.wordSize)
122	case ast.TYPE_ARRAY:
123		elemSize := tc.getSizeof(typ.Base)
124		var arrayLen int64 = 1
125		if typ.ArraySize != nil {
126			if folded := ast.FoldConstants(typ.ArraySize); folded.Type == ast.Number {
127				arrayLen = folded.Data.(ast.NumberNode).Value
128			} else {
129				util.Error(typ.ArraySize.Tok, "Array size must be a constant expression.")
130			}
131		}
132		return elemSize * arrayLen
133	case ast.TYPE_PRIMITIVE:
134		switch typ.Name {
135		case "int", "uint", "string":
136			return int64(tc.wordSize)
137		case "int64", "uint64":
138			return 8
139		case "int32", "uint32":
140			return 4
141		case "int16", "uint16":
142			return 2
143		case "byte", "bool", "int8", "uint8":
144			return 1
145		default:
146			if sym := tc.findSymbol(typ.Name, true); sym != nil {
147				return tc.getSizeof(sym.Type)
148			}
149			return int64(tc.wordSize)
150		}
151	case ast.TYPE_STRUCT:
152		var totalSize int64
153		for _, field := range typ.Fields {
154			totalSize += tc.getSizeof(field.Data.(ast.VarDeclNode).Type)
155		}
156		// NOTE: does not account for alignment/padding
157		return totalSize
158	}
159	return int64(tc.wordSize)
160}
161
162func (tc *TypeChecker) Check(root *ast.Node) {
163	if !tc.cfg.IsFeatureEnabled(config.FeatTyped) {
164		return
165	}
166	tc.collectGlobals(root)
167	tc.checkNode(root)
168	tc.annotateGlobalDecls(root)
169}
170
171func (tc *TypeChecker) collectGlobals(node *ast.Node) {
172	if node == nil || node.Type != ast.Block {
173		return
174	}
175	for _, stmt := range node.Data.(ast.BlockNode).Stmts {
176		switch stmt.Type {
177		case ast.VarDecl, ast.FuncDecl, ast.ExtrnDecl, ast.TypeDecl:
178			tc.addSymbol(stmt)
179		case ast.MultiVarDecl:
180			for _, subStmt := range stmt.Data.(ast.MultiVarDeclNode).Decls {
181				tc.addSymbol(subStmt)
182			}
183		}
184	}
185}
186
187func (tc *TypeChecker) annotateGlobalDecls(root *ast.Node) {
188	if root == nil || root.Type != ast.Block {
189		return
190	}
191	for _, stmt := range root.Data.(ast.BlockNode).Stmts {
192		if stmt.Type == ast.VarDecl {
193			d, ok := stmt.Data.(ast.VarDeclNode)
194			if !ok {
195				continue
196			}
197			if globalSym := tc.findSymbol(d.Name, false); globalSym != nil {
198				if (d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED) && (globalSym.Type != nil && globalSym.Type.Kind != ast.TYPE_UNTYPED) {
199					d.Type = globalSym.Type
200					stmt.Data, stmt.Typ = d, globalSym.Type
201				}
202			}
203		}
204	}
205}
206
207func (tc *TypeChecker) checkNode(node *ast.Node) {
208	if node == nil {
209		return
210	}
211	switch node.Type {
212	case ast.Block:
213		d := node.Data.(ast.BlockNode)
214		if !d.IsSynthetic {
215			tc.enterScope()
216		}
217		for _, stmt := range d.Stmts {
218			tc.checkNode(stmt)
219		}
220		if !d.IsSynthetic {
221			tc.exitScope()
222		}
223	case ast.FuncDecl:
224		tc.checkFuncDecl(node)
225	case ast.VarDecl:
226		tc.checkVarDecl(node)
227	case ast.MultiVarDecl:
228		for _, decl := range node.Data.(ast.MultiVarDeclNode).Decls {
229			tc.checkVarDecl(decl)
230		}
231	case ast.If:
232		d := node.Data.(ast.IfNode)
233		tc.checkExprAsCondition(d.Cond)
234		tc.checkNode(d.ThenBody)
235		tc.checkNode(d.ElseBody)
236	case ast.While:
237		d := node.Data.(ast.WhileNode)
238		tc.checkExprAsCondition(d.Cond)
239		tc.checkNode(d.Body)
240	case ast.Return:
241		tc.checkReturn(node)
242	case ast.Switch:
243		d := node.Data.(ast.SwitchNode)
244		tc.checkExprAsCondition(d.Expr)
245		tc.checkNode(d.Body)
246	case ast.Case:
247		tc.checkExpr(node.Data.(ast.CaseNode).Value)
248		tc.checkNode(node.Data.(ast.CaseNode).Body)
249	case ast.Default:
250		tc.checkNode(node.Data.(ast.DefaultNode).Body)
251	case ast.Label:
252		tc.checkNode(node.Data.(ast.LabelNode).Stmt)
253	case ast.ExtrnDecl:
254		tc.addSymbol(node)
255	case ast.TypeDecl, ast.Goto, ast.Break, ast.Continue, ast.AsmStmt, ast.Directive:
256	default:
257		if node.Type <= ast.TypeCast {
258			tc.checkExpr(node)
259		}
260	}
261}
262
263func (tc *TypeChecker) checkFuncDecl(node *ast.Node) {
264	d := node.Data.(ast.FuncDeclNode)
265	if d.Body == nil || d.Body.Type == ast.AsmStmt {
266		return
267	}
268	prevFunc := tc.currentFunc
269	tc.currentFunc = &d
270	defer func() { tc.currentFunc = prevFunc }()
271	tc.enterScope()
272	for _, pNode := range d.Params {
273		tc.addSymbol(pNode)
274	}
275	tc.checkNode(d.Body)
276	tc.exitScope()
277}
278
279func (tc *TypeChecker) checkVarDecl(node *ast.Node) {
280	d := node.Data.(ast.VarDeclNode)
281	if d.IsDefine && tc.findSymbol(d.Name, false) != nil {
282		util.Error(node.Tok, "No new variables on left side of :=")
283	}
284	if tc.currentFunc != nil {
285		tc.addSymbol(node)
286	}
287	if len(d.InitList) == 0 {
288		if (d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED) && !tc.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
289			util.Error(node.Tok, "Uninitialized variable '%s' is not allowed in this mode", d.Name)
290		}
291		node.Typ = d.Type
292		return
293	}
294
295	initExpr := d.InitList[0]
296	initType := tc.checkExpr(initExpr)
297	if initType == nil || initType.Kind == ast.TYPE_UNTYPED {
298		return
299	}
300
301	if d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED {
302		d.Type = initType
303		node.Data = d
304		if sym := tc.findSymbol(d.Name, false); sym != nil {
305			sym.Type = initType
306		}
307	}
308	if !tc.areTypesCompatible(d.Type, initType, initExpr) {
309		util.Warn(tc.cfg, config.WarnType, node.Tok, "Initializing variable of type '%s' with expression of incompatible type '%s'", typeToString(d.Type), typeToString(initType))
310	}
311	node.Typ = d.Type
312}
313
314func (tc *TypeChecker) checkReturn(node *ast.Node) {
315	d := node.Data.(ast.ReturnNode)
316	if tc.currentFunc == nil {
317		if d.Expr != nil {
318			util.Error(node.Tok, "Return with value used outside of a function.")
319		}
320		return
321	}
322	if !tc.currentFunc.IsTyped {
323		tc.checkExpr(d.Expr)
324		if d.Expr == nil {
325			tc.currentFunc.ReturnType = ast.TypeVoid
326			if sym := tc.findSymbol(tc.currentFunc.Name, false); sym != nil {
327				sym.Type = ast.TypeVoid
328			}
329		}
330		return
331	}
332
333	retType := tc.currentFunc.ReturnType
334	if d.Expr == nil {
335		if retType.Kind != ast.TYPE_VOID {
336			util.Error(node.Tok, "Return with no value in function returning non-void type ('%s')", typeToString(retType))
337		}
338	} else {
339		exprType := tc.checkExpr(d.Expr)
340		if retType.Kind == ast.TYPE_VOID {
341			util.Error(node.Tok, "Return with a value in function returning void")
342		} else if !tc.areTypesCompatible(retType, exprType, d.Expr) {
343			util.Warn(tc.cfg, config.WarnType, node.Tok, "Returning type '%s' is incompatible with function return type '%s'", typeToString(exprType), typeToString(retType))
344		}
345	}
346}
347
348func (tc *TypeChecker) checkExprAsCondition(node *ast.Node) {
349	typ := tc.checkExpr(node)
350	if !(tc.isScalarType(typ) || typ.Kind == ast.TYPE_UNTYPED) {
351		util.Warn(tc.cfg, config.WarnType, node.Tok, "Expression of type '%s' used as a condition", typeToString(typ))
352	}
353}
354
355func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
356	if node == nil {
357		return ast.TypeUntyped
358	}
359	if node.Typ != nil {
360		return node.Typ
361	}
362	var typ *ast.BxType
363	switch d := node.Data.(type) {
364	case ast.AssignNode:
365		lhsType, rhsType := tc.checkExpr(d.Lhs), tc.checkExpr(d.Rhs)
366		if d.Lhs.Type == ast.Subscript {
367			subscript := d.Lhs.Data.(ast.SubscriptNode)
368			arrayExpr := subscript.Array
369			if arrayExpr.Typ != nil && arrayExpr.Typ.Kind == ast.TYPE_POINTER && arrayExpr.Typ.Base.Kind == ast.TYPE_UNTYPED {
370				arrayExpr.Typ.Base = rhsType
371				lhsType = rhsType
372				if arrayExpr.Type == ast.Ident {
373					if sym := tc.findSymbol(arrayExpr.Data.(ast.IdentNode).Name, false); sym != nil {
374						sym.Type = arrayExpr.Typ
375					}
376				}
377			}
378		}
379		if lhsType.Kind == ast.TYPE_UNTYPED && d.Lhs.Type == ast.Ident {
380			if sym := tc.findSymbol(d.Lhs.Data.(ast.IdentNode).Name, false); sym != nil {
381				sym.Type, sym.IsFunc = rhsType, false
382				lhsType = rhsType
383			}
384		}
385		if !tc.areTypesCompatible(lhsType, rhsType, d.Rhs) {
386			util.Warn(tc.cfg, config.WarnType, node.Tok, "Assigning to type '%s' from incompatible type '%s'", typeToString(lhsType), typeToString(rhsType))
387		}
388		typ = lhsType
389	case ast.BinaryOpNode:
390		leftType, rightType := tc.checkExpr(d.Left), tc.checkExpr(d.Right)
391		typ = tc.getBinaryOpResultType(d.Op, leftType, rightType, node.Tok)
392	case ast.UnaryOpNode:
393		operandType := tc.checkExpr(d.Expr)
394		switch d.Op {
395		case token.Star: // Dereference
396			resolvedOpType := tc.resolveType(operandType)
397			if resolvedOpType.Kind == ast.TYPE_POINTER || resolvedOpType.Kind == ast.TYPE_ARRAY {
398				typ = resolvedOpType.Base
399			} else if resolvedOpType.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(resolvedOpType) {
400				// An untyped or integer variable is being dereferenced.
401				// Very common pattern in B. Promote it to a pointer to an untyped base
402				promotedType := &ast.BxType{Kind: ast.TYPE_POINTER, Base: ast.TypeUntyped}
403				d.Expr.Typ = promotedType
404				if d.Expr.Type == ast.Ident {
405					if sym := tc.findSymbol(d.Expr.Data.(ast.IdentNode).Name, false); sym != nil {
406						if sym.Type == nil || sym.Type.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(sym.Type) {
407							sym.Type = promotedType
408						}
409					}
410				}
411				typ = promotedType.Base
412			} else {
413				util.Error(node.Tok, "Cannot dereference non-pointer type '%s'", typeToString(operandType))
414				typ = ast.TypeUntyped
415			}
416		case token.And: // Address-of
417			typ = &ast.BxType{Kind: ast.TYPE_POINTER, Base: operandType}
418		default: // ++, --, -, +, !, ~
419			typ = operandType
420		}
421	case ast.PostfixOpNode:
422		typ = tc.checkExpr(d.Expr)
423	case ast.TernaryNode:
424		tc.checkExprAsCondition(d.Cond)
425		thenType, elseType := tc.checkExpr(d.ThenExpr), tc.checkExpr(d.ElseExpr)
426		if !tc.areTypesCompatible(thenType, elseType, nil) {
427			util.Warn(tc.cfg, config.WarnType, node.Tok, "Type mismatch in ternary expression branches ('%s' vs '%s')", typeToString(thenType), typeToString(elseType))
428		}
429		typ = thenType
430	case ast.SubscriptNode:
431		arrayType, indexType := tc.checkExpr(d.Array), tc.checkExpr(d.Index)
432		if !tc.isIntegerType(indexType) && indexType.Kind != ast.TYPE_UNTYPED {
433			util.Warn(tc.cfg, config.WarnType, d.Index.Tok, "Array subscript is not an integer type ('%s')", typeToString(indexType))
434		}
435		resolvedArrayType := tc.resolveType(arrayType)
436		if resolvedArrayType.Kind == ast.TYPE_ARRAY || resolvedArrayType.Kind == ast.TYPE_POINTER {
437			typ = resolvedArrayType.Base
438		} else if resolvedArrayType.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(resolvedArrayType) {
439			// An untyped or integer variable is being used as a pointer
440			// Another super common pattern in B. Promote it to a pointer to an untyped base
441			// The base type will be inferred from usage (e.g., assignment)
442			promotedType := &ast.BxType{Kind: ast.TYPE_POINTER, Base: ast.TypeUntyped}
443			d.Array.Typ = promotedType
444
445			if d.Array.Type == ast.Ident {
446				if sym := tc.findSymbol(d.Array.Data.(ast.IdentNode).Name, false); sym != nil {
447					if sym.Type == nil || sym.Type.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(sym.Type) {
448						sym.Type = promotedType
449					}
450				}
451			}
452			typ = promotedType.Base
453		} else {
454			util.Error(node.Tok, "Cannot subscript non-array/pointer type '%s'", typeToString(arrayType))
455			typ = ast.TypeUntyped
456		}
457	case ast.MemberAccessNode:
458		typ = tc.checkMemberAccess(node)
459	case ast.FuncCallNode:
460		typ = tc.checkFuncCall(node)
461	case ast.TypeCastNode:
462		tc.checkExpr(d.Expr)
463		typ = d.TargetType
464	case ast.NumberNode:
465		typ = ast.TypeInt
466	case ast.StringNode:
467		typ = ast.TypeString
468	case ast.IdentNode:
469		if sym := tc.findSymbol(d.Name, false); sym != nil {
470			if node.Parent != nil && node.Parent.Type == ast.FuncCall && node.Parent.Data.(ast.FuncCallNode).FuncExpr == node && !sym.IsFunc {
471				sym.IsFunc, sym.Type = true, ast.TypeInt
472			}
473			t := sym.Type
474			if t != nil && t.Kind == ast.TYPE_ARRAY {
475				typ = &ast.BxType{Kind: ast.TYPE_POINTER, Base: t.Base, IsConst: t.IsConst}
476			} else {
477				typ = t
478			}
479		} else {
480			util.Warn(tc.cfg, config.WarnImplicitDecl, node.Tok, "Implicit declaration of variable '%s'", d.Name)
481			sym := tc.addSymbol(ast.NewVarDecl(node.Tok, d.Name, ast.TypeUntyped, nil, nil, false, false, false))
482			typ = sym.Type
483		}
484	default:
485		typ = ast.TypeUntyped
486	}
487	node.Typ = typ
488	return typ
489}
490
491func (tc *TypeChecker) checkMemberAccess(node *ast.Node) *ast.BxType {
492	d := node.Data.(ast.MemberAccessNode)
493	exprType := tc.checkExpr(d.Expr)
494	baseType := exprType
495	if exprType.Kind == ast.TYPE_POINTER {
496		baseType = exprType.Base
497	}
498	resolvedBaseType := tc.resolveType(baseType)
499	if resolvedBaseType.Kind != ast.TYPE_STRUCT {
500		util.Error(node.Tok, "Request for member '%s' in non-struct type", d.Member.Data.(ast.IdentNode).Name)
501		return ast.TypeUntyped
502	}
503
504	var offset int64
505	var memberType *ast.BxType
506	found := false
507	memberName := d.Member.Data.(ast.IdentNode).Name
508	for _, fieldNode := range resolvedBaseType.Fields {
509		fieldData := fieldNode.Data.(ast.VarDeclNode)
510		if fieldData.Name == memberName {
511			memberType, found = fieldData.Type, true
512			break
513		}
514		offset += tc.getSizeof(fieldData.Type)
515	}
516	if !found {
517		util.Error(node.Tok, "No member named '%s' in struct '%s'", memberName, typeToString(resolvedBaseType))
518		return ast.TypeUntyped
519	}
520
521	var structAddrNode *ast.Node
522	if exprType.Kind == ast.TYPE_POINTER {
523		structAddrNode = d.Expr
524	} else {
525		structAddrNode = ast.NewAddressOf(d.Expr.Tok, d.Expr)
526		tc.checkExpr(structAddrNode)
527	}
528
529	offsetNode := ast.NewNumber(d.Member.Tok, offset)
530	offsetNode.Typ = ast.TypeInt
531	addNode := ast.NewBinaryOp(node.Tok, token.Plus, structAddrNode, offsetNode)
532	addNode.Typ = &ast.BxType{Kind: ast.TYPE_POINTER, Base: memberType}
533	node.Type, node.Data, node.Typ = ast.Indirection, ast.IndirectionNode{Expr: addNode}, memberType
534	return memberType
535}
536
537func (tc *TypeChecker) checkFuncCall(node *ast.Node) *ast.BxType {
538	d := node.Data.(ast.FuncCallNode)
539	if d.FuncExpr.Type == ast.Ident {
540		name := d.FuncExpr.Data.(ast.IdentNode).Name
541		if name == "sizeof" {
542			if len(d.Args) != 1 {
543				util.Error(node.Tok, "sizeof expects exactly one argument")
544				return ast.TypeUntyped
545			}
546			arg := d.Args[0]
547			var targetType *ast.BxType
548			if arg.Type == ast.Ident {
549				if sym := tc.findSymbol(arg.Data.(ast.IdentNode).Name, true); sym != nil && sym.IsType {
550					targetType = sym.Type
551				}
552			}
553			if targetType == nil {
554				targetType = tc.checkExpr(arg)
555			}
556			if targetType == nil {
557				util.Error(arg.Tok, "Cannot determine type for sizeof argument")
558				return ast.TypeUntyped
559			}
560			node.Type, node.Data, node.Typ = ast.Number, ast.NumberNode{Value: tc.getSizeof(targetType)}, ast.TypeInt
561			return ast.TypeInt
562		}
563	}
564
565	if len(d.Args) == 1 {
566		if sizeArgCall, ok := d.Args[0].Data.(ast.FuncCallNode); ok && sizeArgCall.FuncExpr.Type == ast.Ident && sizeArgCall.FuncExpr.Data.(ast.IdentNode).Name == "sizeof" {
567			if len(sizeArgCall.Args) == 1 {
568				sizeofArg := sizeArgCall.Args[0]
569				var targetType *ast.BxType
570				if sizeofArg.Type == ast.Ident {
571					if sym := tc.findSymbol(sizeofArg.Data.(ast.IdentNode).Name, true); sym != nil && sym.IsType {
572						targetType = sym.Type
573					}
574				} else {
575					targetType = tc.checkExpr(sizeofArg)
576				}
577				if targetType != nil {
578					node.Typ = &ast.BxType{Kind: ast.TYPE_POINTER, Base: targetType}
579					tc.checkExpr(d.FuncExpr)
580					for _, arg := range d.Args {
581						tc.checkExpr(arg)
582					}
583					return node.Typ
584				}
585			}
586		}
587	}
588
589	if d.FuncExpr.Type == ast.Ident {
590		name := d.FuncExpr.Data.(ast.IdentNode).Name
591		if sym := tc.findSymbol(name, false); sym == nil {
592			util.Warn(tc.cfg, config.WarnImplicitDecl, d.FuncExpr.Tok, "Implicit declaration of function '%s'", name)
593			tc.globalScope.Symbols = &Symbol{Name: name, Type: ast.TypeInt, IsFunc: true, Node: d.FuncExpr, Next: tc.globalScope.Symbols}
594		} else {
595			sym.IsFunc = true
596		}
597	}
598	funcExprType := tc.checkExpr(d.FuncExpr)
599	for _, arg := range d.Args {
600		tc.checkExpr(arg)
601	}
602	return funcExprType
603}
604
605func (tc *TypeChecker) getBinaryOpResultType(op token.Type, left, right *ast.BxType, tok token.Token) *ast.BxType {
606	resLeft, resRight := tc.resolveType(left), tc.resolveType(right)
607	if resLeft.Kind == ast.TYPE_UNTYPED {
608		return resRight
609	}
610	if resRight.Kind == ast.TYPE_UNTYPED {
611		return resLeft
612	}
613	if op >= token.EqEq && op <= token.OrOr {
614		return ast.TypeInt
615	}
616
617	switch op {
618	case token.Plus, token.Minus:
619		if resLeft.Kind == ast.TYPE_POINTER && tc.isIntegerType(resRight) {
620			return resLeft
621		}
622		if tc.isIntegerType(resLeft) && resRight.Kind == ast.TYPE_POINTER && op == token.Plus {
623			return resRight
624		}
625		if op == token.Minus && resLeft.Kind == ast.TYPE_POINTER && resRight.Kind == ast.TYPE_POINTER {
626			return ast.TypeInt
627		}
628	}
629
630	if tc.isNumericType(resLeft) && tc.isNumericType(resRight) {
631		if resLeft.Kind == ast.TYPE_FLOAT || resRight.Kind == ast.TYPE_FLOAT {
632			return ast.TypeFloat
633		}
634		return ast.TypeInt
635	}
636
637	util.Warn(tc.cfg, config.WarnType, tok, "Invalid binary operation between types '%s' and '%s'", typeToString(left), typeToString(right))
638	return ast.TypeInt
639}
640
641func (tc *TypeChecker) areTypesCompatible(a, b *ast.BxType, bNode *ast.Node) bool {
642	if a == nil || b == nil || a.Kind == ast.TYPE_UNTYPED || b.Kind == ast.TYPE_UNTYPED {
643		return true
644	}
645	resA, resB := tc.resolveType(a), tc.resolveType(b)
646	if resA.Kind == resB.Kind {
647		switch resA.Kind {
648		case ast.TYPE_POINTER:
649			if (resA.Base != nil && resA.Base.Kind == ast.TYPE_VOID) || (resB.Base != nil && resB.Base.Kind == ast.TYPE_VOID) {
650				return true
651			}
652			if (resA.Base != nil && resA.Base == ast.TypeByte) || (resB.Base != nil && resB.Base == ast.TypeByte) {
653				return true
654			}
655			return tc.areTypesCompatible(resA.Base, resB.Base, nil)
656		case ast.TYPE_ARRAY:
657			return tc.areTypesCompatible(resA.Base, resB.Base, nil)
658		case ast.TYPE_STRUCT:
659			return resA == resB || (resA.Name != "" && resA.Name == resB.Name)
660		default:
661			return true
662		}
663	}
664	if bNode != nil && bNode.Type == ast.Number && bNode.Data.(ast.NumberNode).Value == 0 && resA.Kind == ast.TYPE_POINTER && tc.isIntegerType(resB) {
665		return true
666	}
667	if resA.Kind == ast.TYPE_POINTER && resB.Kind == ast.TYPE_ARRAY {
668		return tc.areTypesCompatible(resA.Base, resB.Base, nil)
669	}
670	if tc.isNumericType(resA) && tc.isNumericType(resB) {
671		return true
672	}
673	if (resA.Kind == ast.TYPE_BOOL && tc.isScalarType(resB)) || (tc.isScalarType(resA) && resB.Kind == ast.TYPE_BOOL) {
674		return true
675	}
676	return false
677}
678
679func (tc *TypeChecker) resolveType(typ *ast.BxType) *ast.BxType {
680	if typ == nil {
681		return ast.TypeUntyped
682	}
683	if tc.resolving[typ] {
684		return typ
685	}
686	tc.resolving[typ] = true
687	defer func() { delete(tc.resolving, typ) }()
688	if (typ.Kind == ast.TYPE_PRIMITIVE || typ.Kind == ast.TYPE_STRUCT) && typ.Name != "" {
689		if sym := tc.findSymbol(typ.Name, true); sym != nil {
690			resolved := tc.resolveType(sym.Type)
691			if typ.IsConst {
692				newType := *resolved
693				newType.IsConst = true
694				return &newType
695			}
696			return resolved
697		}
698	}
699	return typ
700}
701
702func (tc *TypeChecker) isIntegerType(t *ast.BxType) bool {
703	if t == nil {
704		return false
705	}
706	resolved := tc.resolveType(t)
707	return resolved.Kind == ast.TYPE_PRIMITIVE && resolved.Name != "float" && resolved.Name != "float32" && resolved.Name != "float64"
708}
709
710func (tc *TypeChecker) isFloatType(t *ast.BxType) bool {
711	if t == nil {
712		return false
713	}
714	return tc.resolveType(t).Kind == ast.TYPE_FLOAT
715}
716
717func (tc *TypeChecker) isNumericType(t *ast.BxType) bool { return tc.isIntegerType(t) || tc.isFloatType(t) }
718func (tc *TypeChecker) isScalarType(t *ast.BxType) bool {
719	if t == nil {
720		return false
721	}
722	resolved := tc.resolveType(t)
723	return tc.isNumericType(resolved) || resolved.Kind == ast.TYPE_POINTER || resolved.Kind == ast.TYPE_BOOL
724}
725
726func typeToString(t *ast.BxType) string {
727	if t == nil {
728		return "<nil>"
729	}
730	var sb strings.Builder
731	if t.IsConst {
732		sb.WriteString("const ")
733	}
734	switch t.Kind {
735	case ast.TYPE_PRIMITIVE, ast.TYPE_BOOL, ast.TYPE_FLOAT:
736		sb.WriteString(t.Name)
737	case ast.TYPE_POINTER:
738		sb.WriteString(typeToString(t.Base))
739		sb.WriteString("*")
740	case ast.TYPE_ARRAY:
741		sb.WriteString("[]")
742		sb.WriteString(typeToString(t.Base))
743	case ast.TYPE_STRUCT:
744		sb.WriteString("struct ")
745		if t.Name != "" {
746			sb.WriteString(t.Name)
747		} else if t.StructTag != "" {
748			sb.WriteString(t.StructTag)
749		} else {
750			sb.WriteString("<anonymous>")
751		}
752	case ast.TYPE_VOID:
753		sb.WriteString("void")
754	case ast.TYPE_UNTYPED:
755		sb.WriteString("untyped")
756	default:
757		sb.WriteString(fmt.Sprintf("<unknown_type_kind_%d>", t.Kind))
758	}
759	return sb.String()
760}