zignet
Version:
MCP server for Zig — AI-powered code analysis, validation, and documentation with fine-tuned LLM
537 lines (536 loc) • 14.8 kB
JavaScript
//#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