add trait checking

This commit is contained in:
2025-08-19 21:54:06 -06:00
parent d370fb44a2
commit 90381840af
6 changed files with 109 additions and 14 deletions

View File

@@ -7,3 +7,25 @@ fn main(): String {
a = (b + c.d()); // comment a = (b + c.d()); // comment
return a.b() ; return a.b() ;
} }
type User struct {
id: i64
}
type TestTrait trait {
fn class_method(id: i64): Self;
fn instance_method(self: Self): i64;
fn default_impl(self: Self): i64;
}
impl TestTrait for User {
fn class_method(id: i64): Self {
return Self{id: id};
}
fn instance_method(self: Self): i64 {
return self.get_id();
}
fn default_impl(self: Self): i64 {
return self.instance_method();
}
}

View File

@@ -1,6 +1,7 @@
import { defineCommand } from "@bunli/core"; import { defineCommand } from "@bunli/core";
import { boringGrammar } from "../parse/grammar"; import { boringGrammar } from "../parse/grammar";
import { semantics } from "../parse/semantics"; import { semantics } from "../parse/semantics";
import TraitChecker from "../types/trait_checker";
export const run = defineCommand({ export const run = defineCommand({
name: "run", name: "run",
@@ -20,6 +21,8 @@ export const run = defineCommand({
if (match.succeeded()) { if (match.succeeded()) {
const adapter = semantics(match); const adapter = semantics(match);
const ast = adapter.toAST(); const ast = adapter.toAST();
new TraitChecker().withModule(ast);
console.log(JSON.stringify(ast, null, 2)); console.log(JSON.stringify(ast, null, 2));
} else { } else {
console.log(match.message); console.log(match.message);

View File

@@ -3,7 +3,7 @@ import * as ohm from "ohm-js";
export const boringGrammar = ohm.grammar(String.raw` export const boringGrammar = ohm.grammar(String.raw`
Boringlang { Boringlang {
ReturnStatement = "return" Expression ";" ReturnStatement = "return" Expression ";"
LetStatement = "let" Identifier (":" TypeUsage)? "=" Expression ";" LetStatement = "let" identifier (":" TypeUsage)? "=" Expression ";"
AssignmentStatement = VariableUsage "=" Expression ";" AssignmentStatement = VariableUsage "=" Expression ";"
| StructGetter "=" Expression ";" | StructGetter "=" Expression ";"
ExpressionStatement = Expression ";" ExpressionStatement = Expression ";"
@@ -16,12 +16,12 @@ export const boringGrammar = ohm.grammar(String.raw`
LiteralBool = "true" | "false" LiteralBool = "true" | "false"
LiteralString = "\"" (~"\"" any)* "\"" LiteralString = "\"" (~"\"" any)* "\""
| "'" (~"'" any)* "'" | "'" (~"'" any)* "'"
LiteralStructField = Identifier ":" Expression LiteralStructField = identifier ":" Expression
LiteralStruct = Identifier "{" ListOf<LiteralStructField, ","> "}" LiteralStruct = identifier "{" ListOf<LiteralStructField, ","> "}"
Identifier = (letter | "_")+ (letter | digit | "_")* identifier = (letter | "_")+(letter | digit | "_")*
FunctionCall = Expression "(" ListOf<Expression, ","> ")" FunctionCall = Expression "(" ListOf<Expression, ","> ")"
StructGetter = Expression "." Identifier StructGetter = Expression "." identifier
VariableUsage = Identifier VariableUsage = identifier
IfExpression = "if" "(" Expression ")" Block ("else" Block)? IfExpression = "if" "(" Expression ")" Block ("else" Block)?
Term = LiteralInt Term = LiteralInt
| LiteralFloat | LiteralFloat
@@ -41,16 +41,16 @@ export const boringGrammar = ohm.grammar(String.raw`
| FunctionCall | FunctionCall
| Factor | Factor
Block = "{" Statement* Expression? "}" Block = "{" Statement* Expression? "}"
NamedTypeUsage = Identifier NamedTypeUsage = identifier
TypeUsage = NamedTypeUsage TypeUsage = NamedTypeUsage
| "fn" "(" ListOf<TypeUsage, ","> ")" ":" TypeUsage -- function_tu | "fn" "(" ListOf<TypeUsage, ","> ")" ":" TypeUsage -- function_tu
FunctionArgument = Identifier ":" TypeUsage FunctionArgument = identifier ":" TypeUsage
FunctionDeclaration = "fn" Identifier "(" ListOf<FunctionArgument, ","> ")" ":" TypeUsage FunctionDeclaration = "fn" identifier "(" ListOf<FunctionArgument, ","> ")" ":" TypeUsage
Function = FunctionDeclaration Block Function = FunctionDeclaration Block
StructTypeField = Identifier ":" TypeUsage StructTypeField = identifier ":" TypeUsage
StructTypeDeclaration = "type" Identifier "struct" "{" ListOf<StructTypeField, ","> "}" StructTypeDeclaration = "type" identifier "struct" "{" ListOf<StructTypeField, ","> "}"
TraitMethod = FunctionDeclaration ";" TraitMethod = FunctionDeclaration ";"
TraitTypeDeclaration = "type" Identifier "trait" "{" TraitMethod* "}" TraitTypeDeclaration = "type" identifier "trait" "{" TraitMethod* "}"
TypeDeclaration = StructTypeDeclaration TypeDeclaration = StructTypeDeclaration
| TraitTypeDeclaration | TraitTypeDeclaration
Impl = "impl" (NamedTypeUsage "for")? NamedTypeUsage "{" Function* "}" Impl = "impl" (NamedTypeUsage "for")? NamedTypeUsage "{" Function* "}"

View File

@@ -98,7 +98,7 @@ semantics.addOperation<any>("toAST", {
type: { typeUsage: "NamedTypeUsage", name: identifier.toAST() }, type: { typeUsage: "NamedTypeUsage", name: identifier.toAST() },
}; };
}, },
Identifier(_1, _2): Identifier { identifier(_1, _2): Identifier {
return { return {
name: this.sourceString, name: this.sourceString,
spanStart: this.source.startIdx, spanStart: this.source.startIdx,

View File

@@ -0,0 +1,70 @@
import { Impl, Module, TraitTypeDeclaration, TypeUsage } from "../parse/ast";
interface Context {
environment: Record<string, TraitTypeDeclaration>;
}
const compareTypes = (typeA: TypeUsage, typeB: TypeUsage) => {
if (typeA.typeUsage !== typeB.typeUsage) {
throw Error(`Mismatched types: ${typeA.typeUsage} ${typeB.typeUsage}`);
}
if (typeA.typeUsage == "NamedTypeUsage" && typeB.typeUsage == "NamedTypeUsage") {
if (typeA.name.name !== typeB.name.name) {
throw Error(`Mismatched types: ${typeA.name.name} ${typeB.name.name}`);
}
}
if (typeA.typeUsage == "FunctionTypeUsage" && typeB.typeUsage == "FunctionTypeUsage") {
if (typeA.arguments.length !== typeB.arguments.length) {
throw Error(`Mismatched arg lengths: ${typeA.arguments.length} ${typeB.arguments.length}`);
}
for (let i = 0; i < typeA.arguments.length; i++) {
compareTypes(typeA.arguments[i], typeB.arguments[i]);
}
compareTypes(typeA.returnType, typeB.returnType);
}
};
export default class TraitChecker {
withModule = (module: Module) => {
let ctx: Context = { environment: {} };
for (const item of module.items) {
if (item.moduleItem == "TraitTypeDeclaration") {
ctx.environment[item.name.name] = item;
}
}
for (const item of module.items) {
if (item.moduleItem == "Impl") {
this.withImpl(ctx, item);
}
}
};
withImpl = (ctx: Context, impl: Impl) => {
if (new Set(impl.functions.map((fn) => fn.declaration.name)).size !== impl.functions.length) {
throw Error(`Duplicate functions in ${impl.struct.name.name}`);
}
if (impl.trait == null) {
return;
}
const trait = ctx.environment[impl.trait.name.name];
if (!trait) {
throw Error(`No such trait: ${impl.trait.name}`);
}
if (impl.functions.length !== trait.functions.length) {
throw Error(`Mismatched impl/trait len ${impl.trait.name.name} for ${impl.struct.name.name}`);
}
for (let i = 0; i < impl.functions.length; i++) {
if (impl.functions[i].declaration.name.name !== trait.functions[i].name.name) {
throw Error(
`Mismatched impl/trait names ${impl.functions[i].declaration.name} for ${trait.functions[i].name}`,
);
}
for (let j = 0; j < impl.functions[i].declaration.arguments.length; j++) {
compareTypes(
impl.functions[i].declaration.arguments[j].type,
trait.functions[i].arguments[j].type,
);
}
compareTypes(impl.functions[i].declaration.returnType, trait.functions[i].returnType);
}
};
}

View File

@@ -2,7 +2,7 @@
const config = { const config = {
trailingComma: "all", trailingComma: "all",
singleQuote: false, singleQuote: false,
printWidth: 80, printWidth: 100,
semi: true, semi: true,
}; };