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}