UNPKG

zignet

Version:

MCP server for Zig — AI-powered code analysis, validation, and documentation with fine-tuned LLM

537 lines (536 loc) 14.8 kB
//#region src/type-checker.ts /** * Scope for symbol management */ var Scope = class { symbols = /* @__PURE__ */ new Map(); constructor(parent) { this.parent = parent; } /** * Define a symbol in this scope */ define(name, symbol) { if (this.symbols.has(name)) throw new TypeError(`Symbol '${name}' already defined in this scope`); this.symbols.set(name, symbol); } /** * Resolve a symbol by name (checks parent scopes) */ resolve(name) { const symbol = this.symbols.get(name); if (symbol) return symbol; if (this.parent) return this.parent.resolve(name); } /** * Check if symbol exists in current scope only */ has(name) { return this.symbols.has(name); } /** * Get parent scope */ getParent() { return this.parent; } }; /** * Type error class */ var TypeError = class extends Error { constructor(message, line, column) { super(message); this.line = line; this.column = column; this.name = "TypeError"; } toString() { if (this.line && this.column) return `${this.name} at ${this.line}:${this.column}: ${this.message}`; return `${this.name}: ${this.message}`; } }; /** * Type Checker class */ var TypeChecker = class { currentScope; globalScope; errors = []; currentFunction; constructor() { this.globalScope = new Scope(); this.currentScope = this.globalScope; this.initializeBuiltinTypes(); } /** * Initialize built-in types */ initializeBuiltinTypes() {} /** * Check the entire program */ check(program) { this.errors = []; try { for (const declaration of program.body) this.collectDeclaration(declaration); for (const declaration of program.body) this.checkDeclaration(declaration); } catch (error) { if (error instanceof TypeError) this.errors.push(error); else throw error; } return this.errors; } /** * Collect declaration (first pass) */ collectDeclaration(declaration) { if (declaration.type === "FunctionDeclaration") { const type = this.createFunctionType(declaration); this.currentScope.define(declaration.name, { name: declaration.name, type, kind: "function", isConst: true, declared: true }); } else if (declaration.type === "VariableDeclaration") {} else if (declaration.type === "StructDeclaration") { const type = this.createStructType(declaration); this.currentScope.define(declaration.name, { name: declaration.name, type, kind: "struct", isConst: true, declared: true }); } } /** * Create function type */ createFunctionType(fn) { const parameters = fn.parameters.map((param) => this.typeAnnotationToType(param.typeAnnotation)); const returnType = this.typeAnnotationToType(fn.returnType); return { kind: "function", name: fn.name, parameters, returnType }; } /** * Create struct type */ createStructType(struct) { const fields = /* @__PURE__ */ new Map(); for (const field of struct.fields) fields.set(field.name, this.typeAnnotationToType(field.typeAnnotation)); return { kind: "struct", name: struct.name, fields }; } /** * Convert type annotation to Type */ typeAnnotationToType(typeAnnotation) { if (typeAnnotation.type === "PrimitiveType") return { kind: "primitive", name: typeAnnotation.name }; else if (typeAnnotation.type === "IdentifierType") { const symbol = this.currentScope.resolve(typeAnnotation.name); if (symbol && symbol.type) return symbol.type; return { kind: "unknown", name: typeAnnotation.name }; } return { kind: "unknown", name: "unknown" }; } /** * Check declaration (second pass) */ checkDeclaration(declaration) { if (declaration.type === "FunctionDeclaration") this.checkFunctionDeclaration(declaration); else if (declaration.type === "VariableDeclaration") this.checkVariableDeclaration(declaration); } /** * Check function declaration */ checkFunctionDeclaration(fn) { this.currentFunction = fn; this.enterScope(); for (const param of fn.parameters) { const paramType = this.typeAnnotationToType(param.typeAnnotation); this.currentScope.define(param.name, { name: param.name, type: paramType, kind: "parameter", isConst: true, declared: true }); } this.checkBlockStatement(fn.body); const returnType = this.typeAnnotationToType(fn.returnType); if (returnType.name !== "void") { if (!this.blockHasReturn(fn.body)) this.addError(`Function '${fn.name}' must return a value of type '${returnType.name}'`, fn.line, fn.column); } this.exitScope(); this.currentFunction = void 0; } /** * Check if block has a return statement */ blockHasReturn(block) { for (const stmt of block.statements) { if (stmt.type === "ReturnStatement") return true; if (stmt.type === "IfStatement") { if ("consequent" in stmt && "alternate" in stmt && stmt.alternate) { const hasReturnInConsequent = this.statementHasReturn(stmt.consequent); const hasReturnInAlternate = this.statementHasReturn(stmt.alternate); if (hasReturnInConsequent && hasReturnInAlternate) return true; } } } return false; } /** * Check if statement has a return */ statementHasReturn(stmt) { if (stmt.type === "ReturnStatement") return true; if (stmt.type === "BlockStatement") return this.blockHasReturn(stmt); return false; } /** * Check variable declaration */ checkVariableDeclaration(varDecl) { let varType; if (varDecl.typeAnnotation) varType = this.typeAnnotationToType(varDecl.typeAnnotation); else if (varDecl.initializer) varType = this.checkExpression(varDecl.initializer); else { this.addError(`Variable '${varDecl.name}' must have either a type annotation or initializer`, varDecl.line, varDecl.column); return; } if (varDecl.initializer && varDecl.typeAnnotation) { const initType = this.checkExpression(varDecl.initializer); if (!this.typesCompatible(varType, initType)) this.addError(`Cannot assign value of type '${initType.name}' to variable of type '${varType.name}'`, varDecl.line, varDecl.column); } try { this.currentScope.define(varDecl.name, { name: varDecl.name, type: varType, kind: "variable", isConst: varDecl.isConst, declared: true }); } catch (error) { if (error instanceof TypeError) this.addError(error.message, varDecl.line, varDecl.column); } } /** * Check block statement */ checkBlockStatement(block) { this.enterScope(); for (const stmt of block.statements) this.checkStatement(stmt); this.exitScope(); } /** * Check statement */ checkStatement(stmt) { switch (stmt.type) { case "VariableDeclaration": this.checkVariableDeclaration(stmt); break; case "ReturnStatement": this.checkReturnStatement(stmt); break; case "IfStatement": this.checkIfStatement(stmt); break; case "WhileStatement": this.checkWhileStatement(stmt); break; case "ExpressionStatement": this.checkExpressionStatement(stmt); break; case "BlockStatement": this.checkBlockStatement(stmt); break; case "BreakStatement": case "ContinueStatement": break; case "ComptimeStatement": this.checkBlockStatement(stmt.body); break; } } /** * Check return statement */ checkReturnStatement(stmt) { if (!this.currentFunction) { this.addError("Return statement outside function", stmt.line, stmt.column); return; } const expectedType = this.typeAnnotationToType(this.currentFunction.returnType); if (stmt.value) { const returnType = this.checkExpression(stmt.value); if (!this.typesCompatible(expectedType, returnType)) this.addError(`Return type '${returnType.name}' does not match function return type '${expectedType.name}'`, stmt.line, stmt.column); } else if (expectedType.name !== "void") this.addError(`Function must return a value of type '${expectedType.name}'`, stmt.line, stmt.column); } /** * Check if statement */ checkIfStatement(stmt) { const conditionType = this.checkExpression(stmt.condition); if (conditionType.name !== "bool") this.addError(`If condition must be of type 'bool', got '${conditionType.name}'`, stmt.line, stmt.column); this.checkStatement(stmt.consequent); if (stmt.alternate) this.checkStatement(stmt.alternate); } /** * Check while statement */ checkWhileStatement(stmt) { const conditionType = this.checkExpression(stmt.condition); if (conditionType.name !== "bool") this.addError(`While condition must be of type 'bool', got '${conditionType.name}'`, stmt.line, stmt.column); this.checkStatement(stmt.body); } /** * Check expression statement */ checkExpressionStatement(stmt) { this.checkExpression(stmt.expression); } /** * Check expression and return its type */ checkExpression(expr) { switch (expr.type) { case "NumberLiteral": return { kind: "primitive", name: "i32" }; case "StringLiteral": return { kind: "primitive", name: "string" }; case "BooleanLiteral": return { kind: "primitive", name: "bool" }; case "Identifier": return this.checkIdentifier(expr); case "BinaryExpression": return this.checkBinaryExpression(expr); case "UnaryExpression": return this.checkUnaryExpression(expr); case "CallExpression": return this.checkCallExpression(expr); case "MemberExpression": return this.checkMemberExpression(expr); case "IndexExpression": return this.checkIndexExpression(expr); case "AssignmentExpression": return this.checkAssignmentExpression(expr); default: return { kind: "unknown", name: "unknown" }; } } /** * Check identifier */ checkIdentifier(ident) { const symbol = this.currentScope.resolve(ident.name); if (!symbol) { this.addError(`Undefined variable '${ident.name}'`, ident.line, ident.column); return { kind: "unknown", name: "unknown" }; } return symbol.type; } /** * Check binary expression */ checkBinaryExpression(expr) { const leftType = this.checkExpression(expr.left); const rightType = this.checkExpression(expr.right); if ([ "+", "-", "*", "/", "%" ].includes(expr.operator)) { if (!this.isNumericType(leftType) || !this.isNumericType(rightType)) this.addError(`Operator '${expr.operator}' requires numeric operands`, expr.line, expr.column); return leftType; } if ([ "<", ">", "<=", ">=", "==", "!=" ].includes(expr.operator)) { if (!this.typesCompatible(leftType, rightType)) this.addError(`Cannot compare values of type '${leftType.name}' and '${rightType.name}'`, expr.line, expr.column); return { kind: "primitive", name: "bool" }; } if (["&&", "||"].includes(expr.operator)) { if (leftType.name !== "bool" || rightType.name !== "bool") this.addError(`Operator '${expr.operator}' requires boolean operands`, expr.line, expr.column); return { kind: "primitive", name: "bool" }; } return { kind: "unknown", name: "unknown" }; } /** * Check unary expression */ checkUnaryExpression(expr) { const operandType = this.checkExpression(expr.operand); if (expr.operator === "-") { if (!this.isNumericType(operandType)) this.addError(`Operator '-' requires numeric operand`, expr.line, expr.column); return operandType; } if (expr.operator === "!") { if (operandType.name !== "bool") this.addError(`Operator '!' requires boolean operand`, expr.line, expr.column); return { kind: "primitive", name: "bool" }; } return { kind: "unknown", name: "unknown" }; } /** * Check call expression */ checkCallExpression(expr) { const calleeType = this.checkExpression(expr.callee); if (calleeType.kind !== "function") { this.addError("Expression is not callable", expr.line, expr.column); return { kind: "unknown", name: "unknown" }; } const expectedParams = calleeType.parameters || []; if (expr.arguments.length !== expectedParams.length) this.addError(`Expected ${expectedParams.length} arguments, got ${expr.arguments.length}`, expr.line, expr.column); for (let i = 0; i < Math.min(expr.arguments.length, expectedParams.length); i++) { const argType = this.checkExpression(expr.arguments[i]); const paramType = expectedParams[i]; if (!this.typesCompatible(paramType, argType)) this.addError(`Argument ${i + 1}: expected '${paramType.name}', got '${argType.name}'`, expr.line, expr.column); } return calleeType.returnType || { kind: "void", name: "void" }; } /** * Check member expression */ checkMemberExpression(expr) { const objectType = this.checkExpression(expr.object); if (objectType.kind !== "struct") { this.addError(`Type '${objectType.name}' has no members`, expr.line, expr.column); return { kind: "unknown", name: "unknown" }; } const field = objectType.fields?.get(expr.property); if (!field) { this.addError(`Struct '${objectType.name}' has no field '${expr.property}'`, expr.line, expr.column); return { kind: "unknown", name: "unknown" }; } return field; } /** * Check index expression */ checkIndexExpression(expr) { this.checkExpression(expr.object); const indexType = this.checkExpression(expr.index); if (!this.isNumericType(indexType)) this.addError("Array index must be numeric", expr.line, expr.column); return { kind: "unknown", name: "unknown" }; } /** * Check assignment expression */ checkAssignmentExpression(expr) { const leftType = this.checkExpression(expr.left); const rightType = this.checkExpression(expr.right); if (expr.left.type === "Identifier") { const symbol = this.currentScope.resolve(expr.left.name); if (symbol && symbol.isConst) this.addError(`Cannot assign to const variable '${expr.left.name}'`, expr.line, expr.column); } if (!this.typesCompatible(leftType, rightType)) this.addError(`Cannot assign value of type '${rightType.name}' to '${leftType.name}'`, expr.line, expr.column); return leftType; } /** * Check if types are compatible */ typesCompatible(expected, actual) { if (expected.kind === "unknown" || actual.kind === "unknown") return true; return expected.name === actual.name; } /** * Check if type is numeric */ isNumericType(type) { return type.kind === "primitive" && [ "i32", "i64", "u32", "f32", "f64" ].includes(type.name); } /** * Enter a new scope */ enterScope() { this.currentScope = new Scope(this.currentScope); } /** * Exit current scope */ exitScope() { const parent = this.currentScope.getParent(); if (parent) this.currentScope = parent; } /** * Add an error */ addError(message, line, column) { this.errors.push(new TypeError(message, line, column)); } /** * Get all errors */ getErrors() { return this.errors; } }; //#endregion export { Scope, TypeChecker, TypeError }; //# sourceMappingURL=type-checker.js.map