xplshn
·
2025-09-10
typeChecker.go
Go
1package typeChecker
2
3import (
4 "fmt"
5
6 "github.com/xplshn/gbc/pkg/ast"
7 "github.com/xplshn/gbc/pkg/config"
8 "github.com/xplshn/gbc/pkg/ir"
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 { Symbols *Symbol; Parent *Scope }
23
24type TypeChecker struct {
25 currentScope *Scope
26 currentFunc *ast.FuncDeclNode
27 globalScope *Scope
28 cfg *config.Config
29 resolving map[*ast.BxType]bool
30 wordSize int
31}
32
33func NewTypeChecker(cfg *config.Config) *TypeChecker {
34 globalScope := newScope(nil)
35 return &TypeChecker{
36 currentScope: globalScope,
37 globalScope: globalScope,
38 cfg: cfg,
39 resolving: make(map[*ast.BxType]bool),
40 wordSize: cfg.WordSize,
41 }
42}
43
44func newScope(parent *Scope) *Scope { return &Scope{Parent: parent} }
45func (tc *TypeChecker) enterScope() { tc.currentScope = newScope(tc.currentScope) }
46func (tc *TypeChecker) exitScope() {
47 if tc.currentScope.Parent != nil { tc.currentScope = tc.currentScope.Parent }
48}
49
50func (tc *TypeChecker) typeErrorOrWarn(tok token.Token, format string, args ...interface{}) {
51 if !tc.cfg.IsFeatureEnabled(config.FeatPromTypes) {
52 util.Error(tok, format, args...)
53 } else {
54 util.Warn(tc.cfg, config.WarnPromTypes, tok, format, args...)
55 }
56}
57
58func (tc *TypeChecker) addSymbol(node *ast.Node) *Symbol {
59 var name string
60 var typ *ast.BxType
61 isFunc, isType := false, false
62
63 switch d := node.Data.(type) {
64 case ast.VarDeclNode: name, typ = d.Name, d.Type
65 case ast.FuncDeclNode: name, typ, isFunc = d.Name, d.ReturnType, true
66 case ast.TypeDeclNode: name, typ, isType = d.Name, d.Type, true
67 case ast.EnumDeclNode:
68 name, isType = d.Name, true
69 typ = &ast.BxType{Kind: ast.TYPE_ENUM, Name: d.Name, EnumMembers: d.Members, Base: ast.TypeInt}
70 for _, memberNode := range d.Members {
71 memberData := memberNode.Data.(ast.VarDeclNode)
72 if tc.findSymbol(memberData.Name, false) == nil {
73 memberSym := &Symbol{
74 Name: memberData.Name, Type: ast.TypeInt, Node: memberNode, Next: tc.currentScope.Symbols,
75 }
76 tc.currentScope.Symbols = memberSym
77 } else {
78 util.Warn(tc.cfg, config.WarnExtra, memberNode.Tok, "Redefinition of '%s' in enum", memberData.Name)
79 }
80 }
81 case ast.ExtrnDeclNode:
82 for _, nameNode := range d.Names {
83 ident := nameNode.Data.(ast.IdentNode)
84 if tc.findSymbol(ident.Name, false) == nil {
85 symbolType := ast.TypeUntyped
86 if d.ReturnType != nil { symbolType = d.ReturnType }
87 sym := &Symbol{Name: ident.Name, Type: symbolType, IsFunc: true, Node: node, Next: tc.currentScope.Symbols}
88 tc.currentScope.Symbols = sym
89 }
90 }
91 return nil
92 case ast.IdentNode: name, typ = d.Name, ast.TypeUntyped
93 default:
94 return nil
95 }
96
97 if typ == nil { typ = ast.TypeUntyped }
98
99 if existing := tc.findSymbol(name, isType); existing != nil && tc.currentScope == tc.globalScope {
100 isExistingExtrn := existing.Node != nil && existing.Node.Type == ast.ExtrnDecl
101 if !isExistingExtrn && !(existing.IsFunc && !isFunc && existing.Type.Kind == ast.TYPE_UNTYPED) {
102 util.Error(node.Tok, "Redefinition of '%s'", name)
103 }
104 existing.Type, existing.IsFunc, existing.IsType, existing.Node = typ, isFunc, isType, node
105 return existing
106 }
107
108 sym := &Symbol{Name: name, Type: typ, IsFunc: isFunc, IsType: isType, Node: node, Next: tc.currentScope.Symbols}
109 tc.currentScope.Symbols = sym
110 return sym
111}
112
113func (tc *TypeChecker) findSymbol(name string, findTypes bool) *Symbol {
114 return tc.findSymbolInScopes(name, findTypes, false)
115}
116
117func (tc *TypeChecker) findSymbolInCurrentScope(name string, findTypes bool) *Symbol {
118 return tc.findSymbolInScopes(name, findTypes, true)
119}
120
121func (tc *TypeChecker) findSymbolInScopes(name string, findTypes, currentOnly bool) *Symbol {
122 for s := tc.currentScope; s != nil; s = s.Parent {
123 for sym := s.Symbols; sym != nil; sym = sym.Next {
124 if sym.Name == name && sym.IsType == findTypes {
125 return sym
126 }
127 }
128 if currentOnly {
129 break
130 }
131 }
132 return nil
133}
134
135func (tc *TypeChecker) getAlignof(typ *ast.BxType) int64 {
136 if typ == nil {
137 return int64(tc.wordSize)
138 }
139
140 if (typ.Kind == ast.TYPE_PRIMITIVE || typ.Kind == ast.TYPE_STRUCT) && typ.Name != "" {
141 if sym := tc.findSymbol(typ.Name, true); sym != nil {
142 if sym.Type != typ {
143 return tc.getAlignof(sym.Type)
144 }
145 }
146 }
147
148 if typ.Kind == ast.TYPE_UNTYPED {
149 return int64(tc.wordSize)
150 }
151 switch typ.Kind {
152 case ast.TYPE_VOID: return 1
153 case ast.TYPE_POINTER: return int64(tc.wordSize)
154 case ast.TYPE_ARRAY: return tc.getAlignof(typ.Base)
155 case ast.TYPE_PRIMITIVE, ast.TYPE_FLOAT, ast.TYPE_ENUM: return tc.getSizeof(typ)
156 case ast.TYPE_STRUCT:
157 var maxAlign int64 = 1
158 for _, field := range typ.Fields {
159 fieldAlign := tc.getAlignof(field.Data.(ast.VarDeclNode).Type)
160 if fieldAlign > maxAlign {
161 maxAlign = fieldAlign
162 }
163 }
164 return maxAlign
165 }
166 return int64(tc.wordSize)
167}
168
169func (tc *TypeChecker) getSizeof(typ *ast.BxType) int64 {
170 if typ == nil || typ.Kind == ast.TYPE_UNTYPED {
171 return int64(tc.wordSize)
172 }
173 switch typ.Kind {
174 case ast.TYPE_VOID: return 0
175 case ast.TYPE_POINTER: return int64(tc.wordSize)
176 case ast.TYPE_ARRAY:
177 elemSize := tc.getSizeof(typ.Base)
178 var arrayLen int64 = 1
179 if typ.ArraySize != nil {
180 if folded := ast.FoldConstants(typ.ArraySize); folded.Type == ast.Number {
181 arrayLen = folded.Data.(ast.NumberNode).Value
182 } else {
183 util.Error(typ.ArraySize.Tok, "Array size must be a constant expression")
184 }
185 }
186 return elemSize * arrayLen
187 case ast.TYPE_PRIMITIVE, ast.TYPE_LITERAL_INT:
188 resolver := ir.NewTypeSizeResolver(tc.wordSize)
189 if size := resolver.GetTypeSize(typ.Name); size > 0 {
190 return size
191 }
192 // Fallback for user-defined types
193 if sym := tc.findSymbol(typ.Name, true); sym != nil {
194 return tc.getSizeof(sym.Type)
195 }
196 return int64(tc.wordSize)
197 case ast.TYPE_ENUM:
198 return tc.getSizeof(ast.TypeInt)
199 case ast.TYPE_FLOAT, ast.TYPE_LITERAL_FLOAT:
200 resolver := ir.NewTypeSizeResolver(tc.wordSize)
201 return resolver.GetTypeSize(typ.Name)
202 case ast.TYPE_STRUCT:
203 var totalSize, maxAlign int64 = 0, 1
204 for _, field := range typ.Fields {
205 fieldData := field.Data.(ast.VarDeclNode)
206 fieldAlign := tc.getAlignof(fieldData.Type)
207 if fieldAlign > maxAlign {
208 maxAlign = fieldAlign
209 }
210 totalSize = util.AlignUp(totalSize, fieldAlign)
211 totalSize += tc.getSizeof(fieldData.Type)
212 }
213 if maxAlign == 0 {
214 maxAlign = 1
215 }
216 return util.AlignUp(totalSize, maxAlign)
217 }
218 return int64(tc.wordSize)
219}
220
221func (tc *TypeChecker) Check(root *ast.Node) {
222 if !tc.cfg.IsFeatureEnabled(config.FeatTyped) {
223 return
224 }
225 tc.collectGlobals(root)
226 tc.checkNode(root)
227 tc.annotateGlobalDecls(root)
228}
229
230func (tc *TypeChecker) collectGlobals(node *ast.Node) {
231 if node == nil || node.Type != ast.Block {
232 return
233 }
234 for _, stmt := range node.Data.(ast.BlockNode).Stmts {
235 switch stmt.Type {
236 case ast.VarDecl:
237 if stmt.Data.(ast.VarDeclNode).IsDefine {
238 continue
239 }
240 tc.addSymbol(stmt)
241 case ast.FuncDecl, ast.ExtrnDecl, ast.TypeDecl, ast.EnumDecl:
242 tc.addSymbol(stmt)
243 case ast.MultiVarDecl:
244 for _, subStmt := range stmt.Data.(ast.MultiVarDeclNode).Decls {
245 if subStmt.Data.(ast.VarDeclNode).IsDefine {
246 continue
247 }
248 tc.addSymbol(subStmt)
249 }
250 }
251 }
252}
253
254func (tc *TypeChecker) annotateGlobalDecls(root *ast.Node) {
255 if root == nil || root.Type != ast.Block {
256 return
257 }
258 for _, stmt := range root.Data.(ast.BlockNode).Stmts {
259 if stmt.Type == ast.VarDecl {
260 d, ok := stmt.Data.(ast.VarDeclNode)
261 if !ok {
262 continue
263 }
264 if globalSym := tc.findSymbol(d.Name, false); globalSym != nil {
265 if (d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED) && (globalSym.Type != nil && globalSym.Type.Kind != ast.TYPE_UNTYPED) {
266 d.Type = globalSym.Type
267 stmt.Data, stmt.Typ = d, globalSym.Type
268 }
269 }
270 }
271 }
272}
273
274func (tc *TypeChecker) checkNode(node *ast.Node) {
275 if node == nil {
276 return
277 }
278 switch node.Type {
279 case ast.Block:
280 d := node.Data.(ast.BlockNode)
281 if !d.IsSynthetic {
282 tc.enterScope()
283 }
284 for _, stmt := range d.Stmts {
285 tc.checkNode(stmt)
286 }
287 if !d.IsSynthetic {
288 tc.exitScope()
289 }
290 case ast.FuncDecl:
291 tc.checkFuncDecl(node)
292 case ast.VarDecl:
293 tc.checkVarDecl(node)
294 case ast.MultiVarDecl:
295 for _, decl := range node.Data.(ast.MultiVarDeclNode).Decls {
296 tc.checkVarDecl(decl)
297 }
298 case ast.If:
299 d := node.Data.(ast.IfNode)
300 tc.checkExprAsCondition(d.Cond)
301 tc.checkNode(d.ThenBody)
302 tc.checkNode(d.ElseBody)
303 case ast.While:
304 d := node.Data.(ast.WhileNode)
305 tc.checkExprAsCondition(d.Cond)
306 tc.checkNode(d.Body)
307 case ast.Return:
308 tc.checkReturn(node)
309 case ast.Switch:
310 d := node.Data.(ast.SwitchNode)
311 tc.checkExpr(d.Expr)
312 tc.checkNode(d.Body)
313 case ast.Case:
314 for _, valueExpr := range node.Data.(ast.CaseNode).Values {
315 tc.checkExpr(valueExpr)
316 }
317 tc.checkNode(node.Data.(ast.CaseNode).Body)
318 case ast.Default:
319 tc.checkNode(node.Data.(ast.DefaultNode).Body)
320 case ast.Label:
321 tc.checkNode(node.Data.(ast.LabelNode).Stmt)
322 case ast.ExtrnDecl:
323 tc.addSymbol(node)
324 case ast.TypeDecl, ast.EnumDecl, ast.Goto, ast.Break, ast.Continue, ast.AsmStmt, ast.Directive:
325 default:
326 if node.Type <= ast.StructLiteral {
327 tc.checkExpr(node)
328 }
329 }
330}
331
332func (tc *TypeChecker) checkFuncDecl(node *ast.Node) {
333 d := node.Data.(ast.FuncDeclNode)
334 if d.Body == nil || d.Body.Type == ast.AsmStmt {
335 return
336 }
337 prevFunc := tc.currentFunc
338 tc.currentFunc = &d
339 defer func() { tc.currentFunc = prevFunc }()
340 tc.enterScope()
341 for _, pNode := range d.Params {
342 tc.addSymbol(pNode)
343 }
344 tc.checkNode(d.Body)
345 tc.exitScope()
346}
347
348func (tc *TypeChecker) checkVarDecl(node *ast.Node) {
349 d := node.Data.(ast.VarDeclNode)
350 if d.IsDefine {
351 if sym := tc.findSymbolInCurrentScope(d.Name, false); sym != nil {
352 util.Error(node.Tok, "no new variables on left side of := (redeclaration of '%s')", d.Name)
353 } else {
354 tc.addSymbol(node)
355 }
356 } else if tc.currentFunc != nil && tc.findSymbolInCurrentScope(d.Name, false) == nil {
357 tc.addSymbol(node)
358 }
359
360 if len(d.InitList) == 0 {
361 if (d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED) && !tc.cfg.IsFeatureEnabled(config.FeatAllowUninitialized) {
362 util.Error(node.Tok, "Uninitialized variable '%s' is not allowed in this mode", d.Name)
363 }
364 node.Typ = d.Type
365 return
366 }
367
368 initExpr := d.InitList[0]
369
370 if d.IsDefine && (d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED) {
371 if structTypeSym := tc.findSymbol(d.Name, true); structTypeSym != nil && structTypeSym.IsType {
372 structType := tc.resolveType(structTypeSym.Type)
373 if structType.Kind == ast.TYPE_STRUCT {
374 var operandExpr *ast.Node
375 if initExpr.Type == ast.UnaryOp {
376 unaryOp := initExpr.Data.(ast.UnaryOpNode)
377 if unaryOp.Op == token.Star {
378 operandExpr = unaryOp.Expr
379 }
380 } else if initExpr.Type == ast.Indirection {
381 indirOp := initExpr.Data.(ast.IndirectionNode)
382 operandExpr = indirOp.Expr
383 }
384
385 if operandExpr != nil {
386 operandType := tc.checkExpr(operandExpr)
387 resolvedOpType := tc.resolveType(operandType)
388 if resolvedOpType.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(resolvedOpType) {
389 promotedType := &ast.BxType{Kind: ast.TYPE_POINTER, Base: structType}
390 operandExpr.Typ = promotedType
391 if operandExpr.Type == ast.Ident {
392 if sym := tc.findSymbol(operandExpr.Data.(ast.IdentNode).Name, false); sym != nil {
393 sym.Type = promotedType
394 }
395 }
396 initExpr.Typ = structType
397 d.Type = structType
398 node.Data = d
399 if sym := tc.findSymbol(d.Name, false); sym != nil {
400 sym.Type = structType
401 }
402 node.Typ = structType
403 return
404 }
405 }
406 }
407 }
408 }
409
410 initType := tc.checkExpr(initExpr)
411 if initType == nil {
412 return
413 }
414
415 if d.Type == nil || d.Type.Kind == ast.TYPE_UNTYPED {
416 d.Type = initType
417 node.Data = d
418 if sym := tc.findSymbol(d.Name, false); sym != nil {
419 sym.Type = initType
420 }
421 if tc.cfg.IsWarningEnabled(config.WarnDebugComp) {
422 if d.IsDefine {
423 util.Warn(tc.cfg, config.WarnDebugComp, node.Tok, "Guessing (:=) is %s of type: '%s'", d.Name, ast.TypeToString(initType))
424 } else {
425 util.Warn(tc.cfg, config.WarnDebugComp, node.Tok, "Guessing (auto) is %s of type: '%s'", d.Name, ast.TypeToString(initType))
426 }
427 }
428 } else if initType.Kind == ast.TYPE_LITERAL_INT || initType.Kind == ast.TYPE_LITERAL_FLOAT {
429 if tc.isNumericType(d.Type) || d.Type.Kind == ast.TYPE_POINTER || d.Type.Kind == ast.TYPE_BOOL {
430 initExpr.Typ = d.Type
431 initType = d.Type
432 }
433 }
434 if !tc.areTypesCompatible(d.Type, initType, initExpr) {
435 tc.typeErrorOrWarn(node.Tok, "Initializing variable of type '%s' with expression of incompatible type '%s'", ast.TypeToString(d.Type), ast.TypeToString(initType))
436 }
437 node.Typ = d.Type
438}
439
440func (tc *TypeChecker) isSymbolLocal(name string) bool {
441 for s := tc.currentScope; s != nil && s != tc.globalScope; s = s.Parent {
442 for sym := s.Symbols; sym != nil; sym = sym.Next {
443 if sym.Name == name && !sym.IsType {
444 return true
445 }
446 }
447 }
448 return false
449}
450
451func (tc *TypeChecker) checkReturn(node *ast.Node) {
452 d := node.Data.(ast.ReturnNode)
453 if tc.currentFunc == nil {
454 if d.Expr != nil {
455 util.Error(node.Tok, "Return with value used outside of a function")
456 }
457 return
458 }
459
460 if d.Expr != nil && d.Expr.Type == ast.AddressOf {
461 lval := d.Expr.Data.(ast.AddressOfNode).LValue
462 if lval.Type == ast.Ident {
463 name := lval.Data.(ast.IdentNode).Name
464 if tc.isSymbolLocal(name) {
465 util.Warn(tc.cfg, config.WarnLocalAddress, d.Expr.Tok, "Returning address of local variable '%s'", name)
466 }
467 }
468 }
469
470 if !tc.currentFunc.IsTyped {
471 tc.checkExpr(d.Expr)
472 if d.Expr == nil {
473 tc.currentFunc.ReturnType = ast.TypeVoid
474 if sym := tc.findSymbol(tc.currentFunc.Name, false); sym != nil {
475 sym.Type = ast.TypeVoid
476 }
477 }
478 return
479 }
480
481 retType := tc.currentFunc.ReturnType
482 if d.Expr == nil {
483 if retType.Kind != ast.TYPE_VOID {
484 util.Error(node.Tok, "Return with no value in function returning non-void type ('%s')", ast.TypeToString(retType))
485 }
486 } else {
487 exprType := tc.checkExpr(d.Expr)
488 if retType.Kind == ast.TYPE_VOID {
489 util.Error(node.Tok, "Return with a value in function returning void")
490 } else if !tc.areTypesCompatible(retType, exprType, d.Expr) {
491 tc.typeErrorOrWarn(node.Tok, "Returning type '%s' is incompatible with function return type '%s'", ast.TypeToString(exprType), ast.TypeToString(retType))
492 }
493 }
494}
495
496func (tc *TypeChecker) checkExprAsCondition(node *ast.Node) {
497 typ := tc.checkExpr(node)
498 if !(tc.isScalarType(typ) || typ.Kind == ast.TYPE_UNTYPED || typ.Kind == ast.TYPE_LITERAL_INT) {
499 util.Warn(tc.cfg, config.WarnPromTypes, node.Tok, "Expression of type '%s' used as a condition", ast.TypeToString(typ))
500 }
501}
502
503func (tc *TypeChecker) checkExpr(node *ast.Node) *ast.BxType {
504 if node == nil {
505 return ast.TypeUntyped
506 }
507 if node.Typ != nil && node.Typ.Kind != ast.TYPE_LITERAL_INT && node.Typ.Kind != ast.TYPE_LITERAL_FLOAT {
508 return node.Typ
509 }
510 var typ *ast.BxType
511 switch d := node.Data.(type) {
512 case ast.AssignNode:
513 if d.Op == token.Define {
514 if d.Lhs.Type != ast.Ident {
515 util.Error(node.Tok, "Cannot declare non-identifier with ':='")
516 typ = ast.TypeUntyped
517 break
518 }
519 name := d.Lhs.Data.(ast.IdentNode).Name
520 if sym := tc.findSymbolInCurrentScope(name, false); sym != nil {
521 util.Error(node.Tok, "no new variables on left side of := (redeclaration of '%s')", name)
522 typ = sym.Type
523 break
524 }
525
526 rhsType := tc.checkExpr(d.Rhs)
527 varDeclNode := ast.NewVarDecl(d.Lhs.Tok, name, rhsType, []*ast.Node{d.Rhs}, nil, false, false, true)
528 tc.addSymbol(varDeclNode)
529 node.Type = ast.VarDecl
530 node.Data = varDeclNode.Data
531 node.Typ = rhsType
532 return rhsType
533 }
534
535 lhsType, rhsType := tc.checkExpr(d.Lhs), tc.checkExpr(d.Rhs)
536
537 isLhsScalar := tc.isScalarType(lhsType) && lhsType.Kind != ast.TYPE_POINTER
538 isRhsPtr := rhsType != nil && rhsType.Kind == ast.TYPE_POINTER
539 if isLhsScalar && isRhsPtr && d.Lhs.Type == ast.Ident {
540 if sym := tc.findSymbol(d.Lhs.Data.(ast.IdentNode).Name, false); sym != nil {
541 sym.Type = rhsType
542 lhsType = rhsType
543 }
544 }
545
546 if d.Lhs.Type == ast.Subscript {
547 subscript := d.Lhs.Data.(ast.SubscriptNode)
548 arrayExpr := subscript.Array
549 if arrayExpr.Typ != nil && arrayExpr.Typ.Kind == ast.TYPE_POINTER && arrayExpr.Typ.Base.Kind == ast.TYPE_UNTYPED {
550 arrayExpr.Typ.Base = rhsType
551 lhsType = rhsType
552 if arrayExpr.Type == ast.Ident {
553 if sym := tc.findSymbol(arrayExpr.Data.(ast.IdentNode).Name, false); sym != nil {
554 sym.Type = arrayExpr.Typ
555 }
556 }
557 }
558 }
559 if lhsType.Kind == ast.TYPE_UNTYPED && d.Lhs.Type == ast.Ident {
560 if sym := tc.findSymbol(d.Lhs.Data.(ast.IdentNode).Name, false); sym != nil {
561 sym.Type, sym.IsFunc = rhsType, false
562 lhsType = rhsType
563 }
564 }
565 if !tc.areTypesCompatible(lhsType, rhsType, d.Rhs) {
566 tc.typeErrorOrWarn(node.Tok, "Assigning to type '%s' from incompatible type '%s'", ast.TypeToString(lhsType), ast.TypeToString(rhsType))
567 } else if rhsType.Kind == ast.TYPE_LITERAL_INT || rhsType.Kind == ast.TYPE_LITERAL_FLOAT {
568 if tc.isNumericType(lhsType) || lhsType.Kind == ast.TYPE_POINTER || lhsType.Kind == ast.TYPE_BOOL {
569 d.Rhs.Typ = lhsType
570 }
571 }
572 typ = lhsType
573 case ast.BinaryOpNode:
574 leftType, rightType := tc.checkExpr(d.Left), tc.checkExpr(d.Right)
575 typ = tc.getBinaryOpResultType(d.Op, leftType, rightType, node.Tok, d.Left, d.Right)
576 case ast.UnaryOpNode:
577 operandType := tc.checkExpr(d.Expr)
578 switch d.Op {
579 case token.Star:
580 operandType := tc.checkExpr(d.Expr)
581 resolvedOpType := tc.resolveType(operandType)
582 if resolvedOpType.Kind == ast.TYPE_POINTER || resolvedOpType.Kind == ast.TYPE_ARRAY {
583 typ = resolvedOpType.Base
584 } else if resolvedOpType.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(resolvedOpType) {
585 promotedType := &ast.BxType{Kind: ast.TYPE_POINTER, Base: ast.TypeUntyped}
586 d.Expr.Typ = promotedType
587 if d.Expr.Type == ast.Ident {
588 if sym := tc.findSymbol(d.Expr.Data.(ast.IdentNode).Name, false); sym != nil {
589 if sym.Type == nil || sym.Type.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(sym.Type) {
590 sym.Type = promotedType
591 }
592 }
593 }
594 typ = promotedType.Base
595 } else {
596 util.Error(node.Tok, "Cannot dereference non-pointer type '%s'", ast.TypeToString(operandType))
597 typ = ast.TypeUntyped
598 }
599 case token.And:
600 typ = &ast.BxType{Kind: ast.TYPE_POINTER, Base: operandType}
601 default:
602 typ = operandType
603 }
604 case ast.PostfixOpNode:
605 typ = tc.checkExpr(d.Expr)
606 case ast.TernaryNode:
607 tc.checkExprAsCondition(d.Cond)
608 thenType, elseType := tc.checkExpr(d.ThenExpr), tc.checkExpr(d.ElseExpr)
609 if !tc.areTypesCompatible(thenType, elseType, d.ElseExpr) {
610 tc.typeErrorOrWarn(node.Tok, "Type mismatch in ternary expression branches ('%s' vs '%s')", ast.TypeToString(thenType), ast.TypeToString(elseType))
611 }
612 if thenType != nil && thenType.Kind == ast.TYPE_POINTER {
613 typ = thenType
614 } else if elseType != nil && elseType.Kind == ast.TYPE_POINTER {
615 typ = elseType
616 } else {
617 typ = thenType
618 }
619 case ast.SubscriptNode:
620 arrayType, indexType := tc.checkExpr(d.Array), tc.checkExpr(d.Index)
621 if !tc.isIntegerType(indexType) && indexType.Kind != ast.TYPE_UNTYPED && indexType.Kind != ast.TYPE_LITERAL_INT && indexType.Kind != ast.TYPE_ENUM {
622 tc.typeErrorOrWarn(d.Index.Tok, "Array subscript is not an integer type ('%s')", ast.TypeToString(indexType))
623 }
624 resolvedArrayType := tc.resolveType(arrayType)
625 if resolvedArrayType.Kind == ast.TYPE_ARRAY || resolvedArrayType.Kind == ast.TYPE_POINTER {
626 typ = resolvedArrayType.Base
627 } else if resolvedArrayType.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(resolvedArrayType) {
628 promotedType := &ast.BxType{Kind: ast.TYPE_POINTER, Base: ast.TypeUntyped}
629 d.Array.Typ = promotedType
630
631 if d.Array.Type == ast.Ident {
632 if sym := tc.findSymbol(d.Array.Data.(ast.IdentNode).Name, false); sym != nil {
633 if sym.Type == nil || sym.Type.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(sym.Type) {
634 sym.Type = promotedType
635 }
636 }
637 }
638 typ = promotedType.Base
639 } else {
640 util.Error(node.Tok, "Cannot subscript non-array/pointer type '%s'", ast.TypeToString(arrayType))
641 typ = ast.TypeUntyped
642 }
643 case ast.MemberAccessNode:
644 typ = tc.checkMemberAccess(node)
645 case ast.FuncCallNode:
646 typ = tc.checkFuncCall(node)
647 case ast.IndirectionNode:
648 // Handle indirection (dereferencing) operations
649 indirData := node.Data.(ast.IndirectionNode)
650 operandType := tc.checkExpr(indirData.Expr)
651 resolvedOpType := tc.resolveType(operandType)
652 if resolvedOpType.Kind == ast.TYPE_POINTER || resolvedOpType.Kind == ast.TYPE_ARRAY {
653 typ = resolvedOpType.Base
654 } else if resolvedOpType.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(resolvedOpType) {
655 promotedType := &ast.BxType{Kind: ast.TYPE_POINTER, Base: ast.TypeUntyped}
656 indirData.Expr.Typ = promotedType
657 if indirData.Expr.Type == ast.Ident {
658 if sym := tc.findSymbol(indirData.Expr.Data.(ast.IdentNode).Name, false); sym != nil {
659 if sym.Type == nil || sym.Type.Kind == ast.TYPE_UNTYPED || tc.isIntegerType(sym.Type) {
660 sym.Type = promotedType
661 }
662 }
663 }
664 typ = promotedType.Base
665 } else {
666 util.Error(node.Tok, "Cannot dereference non-pointer type '%s'", ast.TypeToString(operandType))
667 typ = ast.TypeUntyped
668 }
669 case ast.TypeCastNode:
670 tc.checkExpr(d.Expr)
671 typ = d.TargetType
672 case ast.TypeOfNode:
673 tc.checkExpr(d.Expr)
674 typ = ast.TypeString // typeOf always returns a string
675 case ast.StructLiteralNode:
676 typ = tc.checkStructLiteral(node)
677 case ast.ArrayLiteralNode:
678 typ = tc.checkArrayLiteral(node)
679 case ast.NumberNode:
680 typ = ast.TypeLiteralInt
681 case ast.FloatNumberNode:
682 typ = ast.TypeLiteralFloat
683 case ast.StringNode:
684 typ = ast.TypeString
685 case ast.NilNode:
686 typ = ast.TypeNil
687 case ast.IdentNode:
688 if sym := tc.findSymbol(d.Name, false); sym != nil {
689 if node.Parent != nil && node.Parent.Type == ast.FuncCall && node.Parent.Data.(ast.FuncCallNode).FuncExpr == node && !sym.IsFunc {
690 sym.IsFunc, sym.Type = true, ast.TypeInt
691 }
692 t := sym.Type
693 if t != nil && t.Kind == ast.TYPE_ARRAY {
694 typ = &ast.BxType{Kind: ast.TYPE_POINTER, Base: t.Base, IsConst: t.IsConst}
695 } else {
696 typ = t
697 }
698 } else {
699 util.Warn(tc.cfg, config.WarnImplicitDecl, node.Tok, "Implicit declaration of variable '%s'", d.Name)
700 sym := tc.addSymbol(ast.NewVarDecl(node.Tok, d.Name, ast.TypeUntyped, nil, nil, false, false, false))
701 typ = sym.Type
702 }
703 default:
704 typ = ast.TypeUntyped
705 }
706 node.Typ = typ
707 return typ
708}
709
710func (tc *TypeChecker) findStructWithMember(memberName string) *ast.BxType {
711 for s := tc.currentScope; s != nil; s = s.Parent {
712 for sym := s.Symbols; sym != nil; sym = sym.Next {
713 if sym.IsType {
714 typ := tc.resolveType(sym.Type)
715 if typ.Kind == ast.TYPE_STRUCT {
716 for _, field := range typ.Fields {
717 if field.Data.(ast.VarDeclNode).Name == memberName {
718 return typ
719 }
720 }
721 }
722 }
723 }
724 }
725 return nil
726}
727
728func (tc *TypeChecker) checkMemberAccess(node *ast.Node) *ast.BxType {
729 d := node.Data.(ast.MemberAccessNode)
730 exprType := tc.checkExpr(d.Expr)
731
732 baseType := tc.resolveType(exprType)
733
734 if baseType != nil && baseType.Kind == ast.TYPE_POINTER {
735 baseType = baseType.Base
736 }
737
738 resolvedStructType := tc.resolveType(baseType)
739
740 if resolvedStructType != nil && resolvedStructType.Kind == ast.TYPE_UNTYPED {
741 memberName := d.Member.Data.(ast.IdentNode).Name
742 if inferredType := tc.findStructWithMember(memberName); inferredType != nil {
743 if d.Expr.Typ.Kind == ast.TYPE_POINTER {
744 d.Expr.Typ.Base = inferredType
745 } else {
746 d.Expr.Typ = inferredType
747 }
748 if d.Expr.Type == ast.Ident {
749 if sym := tc.findSymbol(d.Expr.Data.(ast.IdentNode).Name, false); sym != nil {
750 sym.Type = d.Expr.Typ
751 }
752 }
753 resolvedStructType = inferredType
754 }
755 }
756
757 if resolvedStructType == nil || resolvedStructType.Kind != ast.TYPE_STRUCT {
758 memberName := d.Member.Data.(ast.IdentNode).Name
759 util.Error(node.Tok, "request for member '%s' in non-struct type '%s'", memberName, ast.TypeToString(exprType))
760 return ast.TypeUntyped
761 }
762
763 memberName := d.Member.Data.(ast.IdentNode).Name
764 for _, fieldNode := range resolvedStructType.Fields {
765 fieldData := fieldNode.Data.(ast.VarDeclNode)
766 if fieldData.Name == memberName {
767 node.Typ = fieldData.Type
768 return fieldData.Type
769 }
770 }
771
772 util.Error(node.Tok, "no member named '%s' in struct '%s'", memberName, ast.TypeToString(resolvedStructType))
773 return ast.TypeUntyped
774}
775
776func (tc *TypeChecker) typeFromName(name string) *ast.BxType {
777 if sym := tc.findSymbol(name, true); sym != nil && sym.IsType {
778 return sym.Type
779 }
780
781 tokType, isKeyword := token.KeywordMap[name]
782 if isKeyword && tokType >= token.Void && tokType <= token.Any {
783 if tokType == token.Void {
784 return ast.TypeVoid
785 }
786 if tokType == token.StringKeyword {
787 return ast.TypeString
788 }
789 if tokType >= token.Float && tokType <= token.Float64 {
790 return &ast.BxType{Kind: ast.TYPE_FLOAT, Name: name}
791 }
792 return &ast.BxType{Kind: ast.TYPE_PRIMITIVE, Name: name}
793 }
794 return nil
795}
796
797func (tc *TypeChecker) checkFuncCall(node *ast.Node) *ast.BxType {
798 d := node.Data.(ast.FuncCallNode)
799 if d.FuncExpr.Type == ast.Ident {
800 name := d.FuncExpr.Data.(ast.IdentNode).Name
801 if name == "sizeof" {
802 if len(d.Args) != 1 {
803 util.Error(node.Tok, "sizeof expects exactly one argument")
804 return ast.TypeUntyped
805 }
806 arg := d.Args[0]
807 var targetType *ast.BxType
808 if arg.Type == ast.Ident {
809 if sym := tc.findSymbol(arg.Data.(ast.IdentNode).Name, true); sym != nil && sym.IsType {
810 targetType = sym.Type
811 }
812 }
813 if targetType == nil {
814 targetType = tc.checkExpr(arg)
815 }
816 if targetType == nil {
817 util.Error(arg.Tok, "Cannot determine type for sizeof argument")
818 return ast.TypeUntyped
819 }
820 node.Type, node.Data, node.Typ = ast.Number, ast.NumberNode{Value: tc.getSizeof(targetType)}, ast.TypeInt
821 return ast.TypeInt
822 }
823
824 if targetType := tc.typeFromName(name); targetType != nil {
825 if len(d.Args) != 1 {
826 util.Error(node.Tok, "Type cast expects exactly one argument")
827 } else {
828 tc.checkExpr(d.Args[0])
829 }
830 node.Type = ast.TypeCast
831 node.Data = ast.TypeCastNode{Expr: d.Args[0], TargetType: targetType}
832 node.Typ = targetType
833 return targetType
834 }
835 }
836
837 if len(d.Args) == 1 {
838 if sizeArgCall, ok := d.Args[0].Data.(ast.FuncCallNode); ok && sizeArgCall.FuncExpr.Type == ast.Ident && sizeArgCall.FuncExpr.Data.(ast.IdentNode).Name == "sizeof" {
839 if len(sizeArgCall.Args) == 1 {
840 sizeofArg := sizeArgCall.Args[0]
841 var targetType *ast.BxType
842 if sizeofArg.Type == ast.Ident {
843 if sym := tc.findSymbol(sizeofArg.Data.(ast.IdentNode).Name, true); sym != nil && sym.IsType {
844 targetType = sym.Type
845 }
846 } else {
847 targetType = tc.checkExpr(sizeofArg)
848 }
849 if targetType != nil {
850 node.Typ = &ast.BxType{Kind: ast.TYPE_POINTER, Base: targetType}
851 tc.checkExpr(d.FuncExpr)
852 for _, arg := range d.Args {
853 tc.checkExpr(arg)
854 }
855 return node.Typ
856 }
857 }
858 }
859 }
860
861 funcExprType := tc.checkExpr(d.FuncExpr)
862
863 if d.FuncExpr.Type == ast.Ident {
864 name := d.FuncExpr.Data.(ast.IdentNode).Name
865 if sym := tc.findSymbol(name, false); sym != nil && sym.IsFunc && sym.Type.Kind == ast.TYPE_UNTYPED {
866 funcExprType = ast.TypeUntyped
867 } else if sym == nil {
868 util.Warn(tc.cfg, config.WarnImplicitDecl, d.FuncExpr.Tok, "Implicit declaration of function '%s'", name)
869 sym = tc.addSymbol(ast.NewFuncDecl(d.FuncExpr.Tok, name, nil, nil, false, false, ast.TypeUntyped))
870 funcExprType = ast.TypeUntyped
871 }
872 }
873
874 for _, arg := range d.Args {
875 tc.checkExpr(arg)
876 }
877
878 resolvedType := tc.resolveType(funcExprType)
879 if resolvedType != nil && resolvedType.Kind == ast.TYPE_STRUCT {
880 return resolvedType
881 }
882
883 return funcExprType
884}
885
886func (tc *TypeChecker) checkStructLiteral(node *ast.Node) *ast.BxType {
887 d := node.Data.(ast.StructLiteralNode)
888
889 typeIdent, ok := d.TypeNode.Data.(ast.IdentNode)
890 if !ok {
891 util.Error(d.TypeNode.Tok, "Invalid type expression in struct literal")
892 return ast.TypeUntyped
893 }
894
895 sym := tc.findSymbol(typeIdent.Name, true)
896 if sym == nil || !sym.IsType {
897 util.Error(d.TypeNode.Tok, "Unknown type name '%s' in struct literal", typeIdent.Name)
898 return ast.TypeUntyped
899 }
900
901 structType := tc.resolveType(sym.Type)
902 if structType.Kind != ast.TYPE_STRUCT {
903 util.Error(d.TypeNode.Tok, "'%s' is not a struct type", typeIdent.Name)
904 return ast.TypeUntyped
905 }
906
907 if d.Names == nil {
908 if len(d.Values) > 0 {
909 if len(structType.Fields) > 0 {
910 firstFieldType := tc.resolveType(structType.Fields[0].Data.(ast.VarDeclNode).Type)
911 for i := 1; i < len(structType.Fields); i++ {
912 currentFieldType := tc.resolveType(structType.Fields[i].Data.(ast.VarDeclNode).Type)
913 if !tc.areTypesEqual(firstFieldType, currentFieldType) {
914 util.Error(node.Tok, "positional struct literal for '%s' is only allowed if all fields have the same type, but found '%s' and '%s'",
915 typeIdent.Name, ast.TypeToString(firstFieldType), ast.TypeToString(currentFieldType))
916 break
917 }
918 }
919 }
920 }
921
922 if len(d.Values) != 0 && len(d.Values) > len(structType.Fields) {
923 util.Error(node.Tok, "Wrong number of initializers for struct '%s'. Expected %d, got %d", typeIdent.Name, len(structType.Fields), len(d.Values))
924 return structType
925 }
926
927 for i, valNode := range d.Values {
928 field := structType.Fields[i].Data.(ast.VarDeclNode)
929 valType := tc.checkExpr(valNode)
930 if !tc.areTypesCompatible(field.Type, valType, valNode) {
931 tc.typeErrorOrWarn(valNode.Tok, "Initializer for field '%s' has wrong type. Expected '%s', got '%s'", field.Name, ast.TypeToString(field.Type), ast.TypeToString(valType))
932 }
933 }
934 } else {
935 if len(d.Values) > len(structType.Fields) {
936 util.Error(node.Tok, "Too many initializers for struct '%s'", typeIdent.Name)
937 }
938
939 fieldMap := make(map[string]*ast.Node)
940 for _, fieldNode := range structType.Fields {
941 fieldData := fieldNode.Data.(ast.VarDeclNode)
942 fieldMap[fieldData.Name] = fieldNode
943 }
944
945 usedFields := make(map[string]bool)
946
947 for i, nameNode := range d.Names {
948 if nameNode == nil {
949 continue
950 }
951 fieldName := nameNode.Data.(ast.IdentNode).Name
952
953 if usedFields[fieldName] {
954 util.Error(nameNode.Tok, "Duplicate field '%s' in struct literal", fieldName)
955 continue
956 }
957 usedFields[fieldName] = true
958
959 field, ok := fieldMap[fieldName]
960 if !ok {
961 util.Error(nameNode.Tok, "Struct '%s' has no field named '%s'", typeIdent.Name, fieldName)
962 continue
963 }
964
965 valNode := d.Values[i]
966 valType := tc.checkExpr(valNode)
967 fieldType := field.Data.(ast.VarDeclNode).Type
968
969 if !tc.areTypesCompatible(fieldType, valType, valNode) {
970 tc.typeErrorOrWarn(valNode.Tok, "Initializer for field '%s' has wrong type. Expected '%s', got '%s'", fieldName, ast.TypeToString(fieldType), ast.TypeToString(valType))
971 }
972 }
973 }
974
975 return structType
976}
977
978func (tc *TypeChecker) checkArrayLiteral(node *ast.Node) *ast.BxType {
979 d := node.Data.(ast.ArrayLiteralNode)
980
981 // Create pointer type to element type (since array literals decay to pointers)
982 pointerType := &ast.BxType{Kind: ast.TYPE_POINTER, Base: d.ElementType}
983
984 // Type check all the values
985 for i, valueNode := range d.Values {
986 valueType := tc.checkExpr(valueNode)
987 if !tc.areTypesCompatible(d.ElementType, valueType, valueNode) {
988 tc.typeErrorOrWarn(valueNode.Tok, "Array element %d has wrong type. Expected '%s', got '%s'",
989 i, ast.TypeToString(d.ElementType), ast.TypeToString(valueType))
990 }
991 }
992
993 return pointerType
994}
995
996// getNodeName extracts a meaningful string representation from an AST node for error messages
997func (tc *TypeChecker) getNodeName(node *ast.Node) string {
998 if node == nil {
999 return "operand"
1000 }
1001
1002 switch node.Type {
1003 case ast.Ident:
1004 return node.Data.(ast.IdentNode).Name
1005 case ast.Number:
1006 return fmt.Sprintf("%d", node.Data.(ast.NumberNode).Value)
1007 case ast.FloatNumber:
1008 return fmt.Sprintf("%g", node.Data.(ast.FloatNumberNode).Value)
1009 case ast.String:
1010 return fmt.Sprintf("\"%s\"", node.Data.(ast.StringNode).Value)
1011 case ast.MemberAccess:
1012 // Handle member access like sp.shape_color
1013 memberData := node.Data.(ast.MemberAccessNode)
1014 exprName := tc.getNodeName(memberData.Expr)
1015 memberName := tc.getNodeName(memberData.Member)
1016 return fmt.Sprintf("%s.%s", exprName, memberName)
1017 default:
1018 return "operand"
1019 }
1020}
1021
1022func (tc *TypeChecker) getBinaryOpResultType(op token.Type, left, right *ast.BxType, tok token.Token, leftNode, rightNode *ast.Node) *ast.BxType {
1023 resLeft, resRight := tc.resolveType(left), tc.resolveType(right)
1024 lType, rType := resLeft, resRight
1025
1026 // Check for explicit type mismatch (both operands are explicitly typed but different)
1027 if lType.Kind != ast.TYPE_LITERAL_INT && lType.Kind != ast.TYPE_LITERAL_FLOAT && lType.Kind != ast.TYPE_UNTYPED &&
1028 rType.Kind != ast.TYPE_LITERAL_INT && rType.Kind != ast.TYPE_LITERAL_FLOAT && rType.Kind != ast.TYPE_UNTYPED &&
1029 tc.isNumericType(lType) && tc.isNumericType(rType) && !tc.areTypesEqual(lType, rType) {
1030 // For numeric types of different sizes, emit warning and promote to larger type
1031 if tc.isIntegerType(lType) && tc.isIntegerType(rType) {
1032 if tc.cfg.IsFeatureEnabled(config.FeatPromTypes) {
1033 util.Warn(tc.cfg, config.WarnDebugComp, tok, "Variable promoted from '%s' to '%s'", ast.TypeToString(left), ast.TypeToString(right))
1034 } else {
1035 // For enum vs int mismatches, suggest casting the enum to its base type
1036 if lType.Kind == ast.TYPE_ENUM && tc.isIntegerType(rType) && rType.Kind != ast.TYPE_ENUM {
1037 leftNodeName := tc.getNodeName(leftNode)
1038 baseTypeName := ast.TypeToString(lType.Base)
1039 tc.typeErrorOrWarn(tok, "operand of type '%s' mismatches operand of type '%s', use %s(%s)", ast.TypeToString(left), ast.TypeToString(right), baseTypeName, leftNodeName)
1040 } else if rType.Kind == ast.TYPE_ENUM && tc.isIntegerType(lType) && lType.Kind != ast.TYPE_ENUM {
1041 rightNodeName := tc.getNodeName(rightNode)
1042 baseTypeName := ast.TypeToString(rType.Base)
1043 tc.typeErrorOrWarn(tok, "operand of type '%s' mismatches operand of type '%s', use %s(%s)", ast.TypeToString(left), ast.TypeToString(right), baseTypeName, rightNodeName)
1044 } else {
1045 rightNodeName := tc.getNodeName(rightNode)
1046 tc.typeErrorOrWarn(tok, "operand of type '%s' mismatches operand of type '%s', use %s(%s)", ast.TypeToString(left), ast.TypeToString(right), ast.TypeToString(right), rightNodeName)
1047 }
1048 }
1049 // Promote to larger type for integer operations
1050 if tc.getSizeof(lType) > tc.getSizeof(rType) {
1051 rType = lType
1052 rightNode.Typ = rType
1053 } else {
1054 lType = rType
1055 leftNode.Typ = lType
1056 }
1057 } else {
1058 // For enum vs int mismatches, suggest casting the enum to its base type
1059 if lType.Kind == ast.TYPE_ENUM && tc.isIntegerType(rType) && rType.Kind != ast.TYPE_ENUM {
1060 leftNodeName := tc.getNodeName(leftNode)
1061 baseTypeName := ast.TypeToString(lType.Base)
1062 tc.typeErrorOrWarn(tok, "operand of type '%s' mismatches operand of type '%s', use %s(%s)", ast.TypeToString(left), ast.TypeToString(right), baseTypeName, leftNodeName)
1063 } else if rType.Kind == ast.TYPE_ENUM && tc.isIntegerType(lType) && lType.Kind != ast.TYPE_ENUM {
1064 rightNodeName := tc.getNodeName(rightNode)
1065 baseTypeName := ast.TypeToString(rType.Base)
1066 tc.typeErrorOrWarn(tok, "operand of type '%s' mismatches operand of type '%s', use %s(%s)", ast.TypeToString(left), ast.TypeToString(right), baseTypeName, rightNodeName)
1067 } else {
1068 rightNodeName := tc.getNodeName(rightNode)
1069 tc.typeErrorOrWarn(tok, "operand of type '%s' mismatches operand of type '%s', use %s(%s)", ast.TypeToString(left), ast.TypeToString(right), ast.TypeToString(right), rightNodeName)
1070 }
1071 }
1072 }
1073
1074 // Handle untyped promotion based on weak-types feature
1075 // But don't warn when both operands are the same untyped type
1076 bothUntypedInt := left.Kind == ast.TYPE_LITERAL_INT && right.Kind == ast.TYPE_LITERAL_INT
1077 bothUntyped := left.Kind == ast.TYPE_UNTYPED && right.Kind == ast.TYPE_UNTYPED
1078
1079 if lType.Kind == ast.TYPE_LITERAL_INT && tc.isIntegerType(rType) {
1080 if tc.cfg.IsFeatureEnabled(config.FeatPromTypes) && !bothUntypedInt {
1081 util.Warn(tc.cfg, config.WarnDebugComp, tok, "Untyped literal promoted from '%s' to '%s'", ast.TypeToString(left), ast.TypeToString(rType))
1082 }
1083 lType = rType
1084 leftNode.Typ = lType
1085 }
1086 if rType.Kind == ast.TYPE_LITERAL_INT && tc.isIntegerType(lType) {
1087 if tc.cfg.IsFeatureEnabled(config.FeatPromTypes) && !bothUntypedInt {
1088 util.Warn(tc.cfg, config.WarnDebugComp, tok, "Untyped literal promoted from '%s' to '%s'", ast.TypeToString(right), ast.TypeToString(lType))
1089 }
1090 rType = lType
1091 rightNode.Typ = rType
1092 }
1093
1094 if lType.Kind == ast.TYPE_LITERAL_FLOAT && tc.isFloatType(rType) {
1095 if tc.cfg.IsFeatureEnabled(config.FeatPromTypes) {
1096 util.Warn(tc.cfg, config.WarnDebugComp, tok, "Untyped literal promoted from '%s' to '%s'", ast.TypeToString(left), ast.TypeToString(rType))
1097 }
1098 lType = rType
1099 leftNode.Typ = rType
1100 }
1101 if rType.Kind == ast.TYPE_LITERAL_FLOAT && tc.isFloatType(lType) {
1102 if tc.cfg.IsFeatureEnabled(config.FeatPromTypes) {
1103 util.Warn(tc.cfg, config.WarnDebugComp, tok, "Untyped literal promoted from '%s' to '%s'", ast.TypeToString(right), ast.TypeToString(lType))
1104 }
1105 rType = lType
1106 rightNode.Typ = rType
1107 }
1108
1109 // Handle operations between two untyped operands
1110 if bothUntyped {
1111 // Check if both operands are literals
1112 leftIsLiteral := leftNode != nil && (leftNode.Type == ast.Number || leftNode.Type == ast.FloatNumber)
1113 rightIsLiteral := rightNode != nil && (rightNode.Type == ast.Number || rightNode.Type == ast.FloatNumber)
1114
1115 // For untyped + untyped, emit warning
1116 if leftIsLiteral && rightIsLiteral {
1117 if tc.cfg.IsWarningEnabled(config.WarnDebugComp) {
1118 util.Warn(tc.cfg, config.WarnDebugComp, tok, "Operation between untyped operands")
1119 }
1120 } else {
1121 if tc.cfg.IsWarningEnabled(config.WarnPromTypes) {
1122 util.Warn(tc.cfg, config.WarnDebugComp, tok, "Operation between untyped operands")
1123 }
1124 }
1125 // Default to int type for untyped operations
1126 lType = ast.TypeInt
1127 rType = ast.TypeInt
1128 leftNode.Typ = ast.TypeInt
1129 rightNode.Typ = ast.TypeInt
1130 }
1131
1132 // Handle operations between untyped and typed operands
1133 if lType.Kind == ast.TYPE_UNTYPED && tc.isIntegerType(rType) {
1134 if tc.cfg.IsFeatureEnabled(config.FeatPromTypes) || !bothUntyped {
1135 // Check if the operand is a literal
1136 leftIsLiteral := leftNode != nil && (leftNode.Type == ast.Number || leftNode.Type == ast.FloatNumber)
1137
1138 if leftIsLiteral {
1139 if tc.cfg.IsWarningEnabled(config.WarnDebugComp) {
1140 util.Warn(tc.cfg, config.WarnDebugComp, tok, "Untyped operand promoted to '%s'", ast.TypeToString(rType))
1141 }
1142 } else {
1143 if tc.cfg.IsWarningEnabled(config.WarnPromTypes) {
1144 if rType.Kind != ast.TYPE_UNTYPED {
1145 util.Warn(tc.cfg, config.WarnPromTypes, tok, "Untyped operand promoted to '%s'", ast.TypeToString(rType))
1146 }
1147 }
1148 }
1149 }
1150 lType = rType
1151 leftNode.Typ = rType
1152 }
1153 if rType.Kind == ast.TYPE_UNTYPED && tc.isIntegerType(lType) {
1154 if tc.cfg.IsFeatureEnabled(config.FeatPromTypes) || !bothUntyped {
1155 // Check if the operand is a literal
1156 rightIsLiteral := rightNode != nil && (rightNode.Type == ast.Number || rightNode.Type == ast.FloatNumber)
1157
1158 if rightIsLiteral {
1159 if tc.cfg.IsWarningEnabled(config.WarnDebugComp) {
1160 util.Warn(tc.cfg, config.WarnDebugComp, tok, "Untyped operand promoted to '%s'", ast.TypeToString(lType))
1161 }
1162 } else {
1163 if lType.Kind != ast.TYPE_UNTYPED {
1164 util.Warn(tc.cfg, config.WarnPromTypes, tok, "Untyped operand promoted to '%s'", ast.TypeToString(lType))
1165 }
1166 }
1167 }
1168 rType = lType
1169 rightNode.Typ = lType
1170 }
1171
1172 resLeft, resRight = lType, rType
1173
1174 if op >= token.EqEq && op <= token.OrOr {
1175 return ast.TypeInt
1176 }
1177
1178 if tc.isNumericType(resLeft) && tc.isNumericType(resRight) {
1179 if tc.isFloatType(resLeft) || tc.isFloatType(resRight) {
1180 // If both operands are float types, return the more precise one
1181 if tc.isFloatType(resLeft) && tc.isFloatType(resRight) {
1182 if tc.areTypesEqual(resLeft, resRight) {
1183 return resLeft // Both same float type, preserve it
1184 }
1185 // Different float types, promote to the larger one
1186 if tc.getSizeof(resLeft) > tc.getSizeof(resRight) {
1187 return resLeft
1188 }
1189 return resRight
1190 }
1191 // One float, one integer - return the float type (not machine float)
1192 if tc.isFloatType(resLeft) {
1193 return resLeft
1194 }
1195 return resRight
1196 }
1197 if tc.getSizeof(resLeft) > tc.getSizeof(resRight) {
1198 return resLeft
1199 }
1200 return resRight
1201 }
1202
1203 if op == token.Plus || op == token.Minus {
1204 if resLeft.Kind == ast.TYPE_POINTER && tc.isIntegerType(resRight) {
1205 return resLeft
1206 }
1207 if tc.isIntegerType(resLeft) && resRight.Kind == ast.TYPE_POINTER && op == token.Plus {
1208 return resRight
1209 }
1210 if op == token.Minus && resLeft.Kind == ast.TYPE_POINTER && resRight.Kind == ast.TYPE_POINTER {
1211 return ast.TypeInt
1212 }
1213 // Allow string concatenation: *byte + *byte -> *byte | (+ only)
1214 if op == token.Plus && resLeft.Kind == ast.TYPE_POINTER && resRight.Kind == ast.TYPE_POINTER &&
1215 resLeft.Base != nil && resRight.Base != nil &&
1216 resLeft.Base.Kind == ast.TYPE_PRIMITIVE && resLeft.Base.Name == "byte" &&
1217 resRight.Base.Kind == ast.TYPE_PRIMITIVE && resRight.Base.Name == "byte" {
1218 return resLeft
1219 }
1220 // TODO: Support othe array types
1221 }
1222
1223 tc.typeErrorOrWarn(tok, "Invalid binary operation between types '%s' and '%s'", ast.TypeToString(left), ast.TypeToString(right))
1224 return ast.TypeInt
1225}
1226
1227func (tc *TypeChecker) areTypesCompatible(a, b *ast.BxType, bNode *ast.Node) bool {
1228 if a == nil || b == nil || a.Kind == ast.TYPE_UNTYPED {
1229 return true
1230 }
1231
1232 if b.Kind == ast.TYPE_LITERAL_INT {
1233 return tc.isNumericType(a) || a.Kind == ast.TYPE_POINTER || a.Kind == ast.TYPE_BOOL
1234 }
1235 if b.Kind == ast.TYPE_LITERAL_FLOAT {
1236 return tc.isFloatType(a)
1237 }
1238 if b.Kind == ast.TYPE_UNTYPED {
1239 return true
1240 }
1241
1242 resA, resB := tc.resolveType(a), tc.resolveType(b)
1243
1244 if resA.Kind == ast.TYPE_POINTER && tc.isIntegerType(resB) {
1245 return true
1246 }
1247 if tc.isIntegerType(resA) && resB.Kind == ast.TYPE_POINTER {
1248 return true
1249 }
1250
1251 if resA.Kind == ast.TYPE_NIL {
1252 return resB.Kind == ast.TYPE_POINTER || resB.Kind == ast.TYPE_ARRAY || resB.Kind == ast.TYPE_NIL
1253 }
1254 if resB.Kind == ast.TYPE_NIL {
1255 return resA.Kind == ast.TYPE_POINTER || resA.Kind == ast.TYPE_ARRAY
1256 }
1257
1258 if resA.Kind == resB.Kind {
1259 switch resA.Kind {
1260 case ast.TYPE_POINTER:
1261 if (resA.Base != nil && resA.Base.Kind == ast.TYPE_VOID) || (resB.Base != nil && resB.Base.Kind == ast.TYPE_VOID) {
1262 return true
1263 }
1264 if (resA.Base != nil && resA.Base == ast.TypeByte) || (resB.Base != nil && resB.Base == ast.TypeByte) {
1265 return true
1266 }
1267 return tc.areTypesCompatible(resA.Base, resB.Base, nil)
1268 case ast.TYPE_ARRAY:
1269 return tc.areTypesCompatible(resA.Base, resB.Base, nil)
1270 case ast.TYPE_STRUCT:
1271 return resA == resB || (resA.Name != "" && resA.Name == resB.Name)
1272 case ast.TYPE_ENUM:
1273 return true
1274 default:
1275 return true
1276 }
1277 }
1278 if bNode != nil && bNode.Type == ast.Number && bNode.Data.(ast.NumberNode).Value == 0 && resA.Kind == ast.TYPE_POINTER && tc.isIntegerType(resB) {
1279 return true
1280 }
1281 if resA.Kind == ast.TYPE_POINTER && resB.Kind == ast.TYPE_ARRAY {
1282 return tc.areTypesCompatible(resA.Base, resB.Base, nil)
1283 }
1284 if (resA.Kind == ast.TYPE_ENUM && tc.isIntegerType(resB)) || (tc.isIntegerType(resA) && resB.Kind == ast.TYPE_ENUM) {
1285 return true
1286 }
1287 if tc.isNumericType(resA) && tc.isNumericType(resB) {
1288 return true
1289 }
1290 if (resA.Kind == ast.TYPE_BOOL && tc.isScalarType(resB)) || (tc.isScalarType(resA) && resB.Kind == ast.TYPE_BOOL) {
1291 return true
1292 }
1293 return false
1294}
1295
1296func (tc *TypeChecker) areTypesEqual(a, b *ast.BxType) bool {
1297 if a == nil || b == nil {
1298 return a == b
1299 }
1300 resA, resB := tc.resolveType(a), tc.resolveType(b)
1301 if resA.Kind != resB.Kind {
1302 return false
1303 }
1304 switch resA.Kind {
1305 case ast.TYPE_POINTER, ast.TYPE_ARRAY:
1306 return tc.areTypesEqual(resA.Base, resB.Base)
1307 case ast.TYPE_STRUCT, ast.TYPE_ENUM, ast.TYPE_PRIMITIVE, ast.TYPE_FLOAT:
1308 return resA.Name == resB.Name
1309 default:
1310 return true
1311 }
1312}
1313
1314func (tc *TypeChecker) resolveType(typ *ast.BxType) *ast.BxType {
1315 if typ == nil { return ast.TypeUntyped }
1316 if tc.resolving[typ] { return typ }
1317 tc.resolving[typ] = true
1318 defer func() { delete(tc.resolving, typ) }()
1319 if (typ.Kind == ast.TYPE_PRIMITIVE || typ.Kind == ast.TYPE_STRUCT || typ.Kind == ast.TYPE_ENUM) && typ.Name != "" {
1320 if sym := tc.findSymbol(typ.Name, true); sym != nil {
1321 resolved := tc.resolveType(sym.Type)
1322 if typ.IsConst {
1323 newType := *resolved
1324 newType.IsConst = true
1325 return &newType
1326 }
1327 return resolved
1328 }
1329 }
1330 return typ
1331}
1332
1333func (tc *TypeChecker) isIntegerType(t *ast.BxType) bool {
1334 if t == nil { return false }
1335 resolved := tc.resolveType(t)
1336 return resolved.Kind == ast.TYPE_PRIMITIVE || resolved.Kind == ast.TYPE_LITERAL_INT || resolved.Kind == ast.TYPE_UNTYPED || resolved.Kind == ast.TYPE_ENUM
1337}
1338
1339func (tc *TypeChecker) isFloatType(t *ast.BxType) bool {
1340 if t == nil { return false }
1341 resolved := tc.resolveType(t)
1342 return resolved.Kind == ast.TYPE_FLOAT || resolved.Kind == ast.TYPE_LITERAL_FLOAT
1343}
1344
1345func (tc *TypeChecker) isNumericType(t *ast.BxType) bool {
1346 return tc.isIntegerType(t) || tc.isFloatType(t)
1347}
1348func (tc *TypeChecker) isScalarType(t *ast.BxType) bool {
1349 if t == nil { return false }
1350 resolved := tc.resolveType(t)
1351 return tc.isNumericType(resolved) || resolved.Kind == ast.TYPE_POINTER || resolved.Kind == ast.TYPE_BOOL
1352}