diff --git a/data/nois3.no b/data/nois3.no new file mode 100644 index 00000000..9fc6849c --- /dev/null +++ b/data/nois3.no @@ -0,0 +1,78 @@ +// expressive type system: Haskell, Rust +// algebraic data types +// type aliases +// traits and impl specialization +// specialization type hint? e.g. foo.>into() +// polymorphic parameters only via trait bounds on generics (no params with trait type) +// self bounds on traits +// separate operators for Int/Float/String (e.g. +, +., ++) for better type inference +// +// Compiler phases: +// - Create AST +// - Lexing +// - Parsing +// - Name resolution +// - Module resolution (find names accessible outside of the module) +// - Name resolution (resolve all names to their definition) +// - Semantic check +// - No stupid things like missing method impls, illogical match statements +// - Type check +// - Constraint gathering (how factors constraint node's type: type annotations, usage as argument, return type) +// - Constraint unification (trying to combine constraints into a most broad type) +// - Type semantics (proper impl definition, match refutablility, call integrity) +// - Impl resolution (static dispatch references, dynamic dispatch upcasts) +// - Codegen + +type Option { + Some(value T) + None() +} + +type User(name Name, age Int) + +type Name = String + +trait Eq { + fn eq(self, other Self) Bool +} + +trait Ord { + fn cmp(self, other Self) Ordering + + fn lt(self, other Self) Bool { + self.cmp(other) == Less + } +} + +trait Show { + fn show(self) String +} + +impl Show for Option { + fn show(self) String { + "Maybe user!!!" + } +} + +impl Show for Option { + fn show(self) String { + match self { + Some(value) { value.show() } + None() { "None" } + } + } +} + +fn main() { + + let u = User("Jack", 13) + let u = User(name = "Jack", age = 13) + + let m = Some(5) + let value = 5 + match m) { + Some(value) { m.value }, + None() { ... } + } +} + diff --git a/package.json b/package.json index 76dbe4cc..bbc0ba5f 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@types/jasmine": "~4.3.5", "@types/node": "~18.16.20", "bun": "~1.0.23", - "typescript": "~5.3.3" - } + "typescript": "~5.5.3" + }, + "packageManager": "pnpm@9.4.0" } diff --git a/src/ast/expr.ts b/src/ast/expr.ts index d1cf0b18..b287b51f 100644 --- a/src/ast/expr.ts +++ b/src/ast/expr.ts @@ -1,15 +1,15 @@ import { ParseNode, filterNonAstNodes } from '../parser' import { Context, addError } from '../scope' -import { Typed, Virtual } from '../semantic' import { invalidOperatorChainError } from '../semantic/error' import { assert } from '../util/todo' -import { AstNode } from './index' +import { BaseAstNode } from './index' import { BinaryOp, PostfixOp, associativityMap, buildBinaryOp, buildPostfixOp, precedenceMap } from './op' import { Operand, buildOperand } from './operand' export type Expr = OperandExpr | UnaryExpr | BinaryExpr -export interface OperandExpr extends AstNode<'operand-expr'>, Partial, Partial { +export type OperandExpr = BaseAstNode & { + kind: 'operand-expr' operand: Operand } @@ -21,12 +21,14 @@ export const buildOperandExpr = (node: ParseNode, ctx: Context): OperandExpr => } } -export interface UnaryExpr extends AstNode<'unary-expr'>, Partial, Partial { +export type UnaryExpr = BaseAstNode & { + kind: 'unary-expr' operand: Operand op: PostfixOp } -export interface BinaryExpr extends AstNode<'binary-expr'>, Partial, Partial { +export type BinaryExpr = BaseAstNode & { + kind: 'binary-expr' binaryOp: BinaryOp lOperand: Operand rOperand: Operand diff --git a/src/ast/index.ts b/src/ast/index.ts index 7bd16ce9..496c11ff 100644 --- a/src/ast/index.ts +++ b/src/ast/index.ts @@ -1,18 +1,130 @@ import { ParseNode, filterNonAstNodes } from '../parser' -import { Context, Scope } from '../scope' -import { InstanceRelation } from '../scope/trait' +import { Context, DefinitionMap } from '../scope' import { VirtualIdentifier, VirtualIdentifierMatch } from '../scope/vid' -import { Typed } from '../semantic' import { VirtualUseExpr } from '../semantic/use-expr' import { Source } from '../source' -import { Expr, buildExpr } from './expr' -import { Pattern, buildPattern } from './match' -import { Name, buildName } from './operand' -import { Block, UseExpr, buildStatement, buildUseExpr } from './statement' -import { Type, buildType } from './type' - -export interface AstNode { - kind: T +import { BinaryExpr, Expr, OperandExpr, UnaryExpr, buildExpr } from './expr' +import { ConPattern, FieldPattern, Hole, ListPattern, MatchClause, MatchExpr, Pattern, buildPattern } from './match' +import { + AddOp, + AndOp, + AssignOp, + AwaitOp, + BindOp, + CallOp, + DivOp, + EqOp, + ExpOp, + FieldAccessOp, + GeOp, + GtOp, + LeOp, + LtOp, + MethodCallOp, + ModOp, + MultOp, + NeOp, + OrOp, + SubOp, + UnwrapOp +} from './op' +import { + BoolLiteral, + CharLiteral, + ClosureExpr, + FloatLiteral, + ForExpr, + Identifier, + IfExpr, + IfLetExpr, + IntLiteral, + ListExpr, + Name, + StringInterpolated, + StringLiteral, + WhileExpr, + buildName +} from './operand' +import { + Block, + BreakStmt, + FnDef, + ImplDef, + ReturnStmt, + TraitDef, + UseExpr, + VarDef, + buildStatement, + buildUseExpr +} from './statement' +import { FnType, Generic, Type, TypeBounds, buildType } from './type' +import { FieldDef, TypeDef, Variant } from './type-def' + +export type AstNode = + | Module + | UseExpr + | Variant + | ReturnStmt + | BreakStmt + | Arg + | Block + | Param + | TypeBounds + | FnType + | Generic + | IfExpr + | MatchClause + | Pattern + | ConPattern + | ListPattern + | FieldPattern + | Hole + | Identifier + | Name + | StringInterpolated + | OperandExpr + | UnaryExpr + | BinaryExpr + | ClosureExpr + | ListExpr + | IfLetExpr + | WhileExpr + | ForExpr + | MatchExpr + | VarDef + | FnDef + | TraitDef + | ImplDef + | TypeDef + | FieldDef + | StringLiteral + | CharLiteral + | IntLiteral + | FloatLiteral + | BoolLiteral + | AddOp + | SubOp + | MultOp + | DivOp + | ExpOp + | ModOp + | EqOp + | NeOp + | GeOp + | LeOp + | GtOp + | LtOp + | AndOp + | OrOp + | AssignOp + | MethodCallOp + | FieldAccessOp + | CallOp + | UnwrapOp + | BindOp + | AwaitOp + +export type BaseAstNode = { parseNode: ParseNode } @@ -65,7 +177,6 @@ export const astKinds = [ 'variant', 'return-stmt', 'break-stmt', - 'call', 'arg', 'block', 'param', @@ -91,7 +202,7 @@ export const astKinds = [ export type AstNodeKind = (typeof astKinds)[number] -export const compactAstNode = (node: AstNode): any => { +export const compactAstNode = (node: AstNode): any => { if (typeof node !== 'object') return node return Object.fromEntries( Object.entries(node) @@ -108,13 +219,14 @@ export const compactAstNode = (node: AstNode): any => { ) } -export interface Module extends AstNode<'module'> { +export type Module = BaseAstNode & { + kind: 'module' source: Source identifier: VirtualIdentifier mod: boolean block: Block - scopeStack: Scope[] + scopeStack: DefinitionMap[] useExprs: UseExpr[] /** @@ -126,17 +238,15 @@ export interface Module extends AstNode<'module'> { */ reExports?: VirtualUseExpr[] /** - * Persistent top level scope. - * Different from scopeStack[0] because it is always available - * If module is accessed during its check, use scopeStack.at(0) instead + * Persistent top level scope */ - topScope?: Scope + topScope: DefinitionMap compiled: boolean /** * List of resolved imports used by this module */ imports: VirtualIdentifierMatch[] - relImports: InstanceRelation[] + useScope: DefinitionMap } export const buildModuleAst = ( @@ -160,13 +270,15 @@ export const buildModuleAst = ( block, scopeStack: [], useExprs, + topScope: new Map(), compiled, imports: [], - relImports: [] + useScope: new Map() } } -export interface Param extends AstNode<'param'>, Partial { +export type Param = BaseAstNode & { + kind: 'param' pattern: Pattern paramType?: Type } @@ -178,7 +290,8 @@ export const buildParam = (node: ParseNode, ctx: Context): Param => { return { kind: 'param', parseNode: node, pattern, paramType: typeNode ? buildType(typeNode, ctx) : undefined } } -export interface Arg extends AstNode<'arg'> { +export type Arg = BaseAstNode & { + kind: 'arg' name?: Name expr: Expr } diff --git a/src/ast/match.ts b/src/ast/match.ts index d3534b66..829021e5 100644 --- a/src/ast/match.ts +++ b/src/ast/match.ts @@ -2,10 +2,9 @@ import { LexerToken } from '../lexer/lexer' import { ParseNode, filterNonAstNodes } from '../parser' import { nameLikeTokens } from '../parser/fns' import { Context } from '../scope' -import { Typed } from '../semantic' import { unreachable } from '../util/todo' import { Expr, buildExpr } from './expr' -import { AstNode } from './index' +import { BaseAstNode } from './index' import { BoolLiteral, CharLiteral, @@ -23,7 +22,8 @@ import { } from './operand' import { Block, buildBlock } from './statement' -export interface MatchExpr extends AstNode<'match-expr'>, Partial { +export type MatchExpr = BaseAstNode & { + kind: 'match-expr' expr: Expr clauses: MatchClause[] } @@ -38,7 +38,8 @@ export const buildMatchExpr = (node: ParseNode, ctx: Context): MatchExpr => { return { kind: 'match-expr', parseNode: node, expr, clauses } } -export interface MatchClause extends AstNode<'match-clause'> { +export type MatchClause = BaseAstNode & { + kind: 'match-clause' patterns: Pattern[] block: Block guard?: Expr @@ -53,7 +54,8 @@ export const buildMatchClause = (node: ParseNode, ctx: Context): MatchClause => return { kind: 'match-clause', parseNode: node, patterns, guard, block } } -export interface Pattern extends AstNode<'pattern'> { +export type Pattern = BaseAstNode & { + kind: 'pattern' name?: Name expr: PatternExpr } @@ -105,7 +107,8 @@ export const buildPatternExpr = (node: ParseNode, ctx: Context): PatternExpr => } } -export interface ConPattern extends AstNode<'con-pattern'>, Partial { +export type ConPattern = BaseAstNode & { + kind: 'con-pattern' identifier: Identifier fieldPatterns: FieldPattern[] } @@ -117,7 +120,8 @@ export const buildConPattern = (node: ParseNode, ctx: Context): ConPattern => { return { kind: 'con-pattern', parseNode: node, identifier, fieldPatterns } } -export interface ListPattern extends AstNode<'list-pattern'>, Partial { +export type ListPattern = BaseAstNode & { + kind: 'list-pattern' itemPatterns: Pattern[] } @@ -126,7 +130,8 @@ export const buildListPattern = (node: ParseNode, ctx: Context): ListPattern => return { kind: 'list-pattern', parseNode: node, itemPatterns: nodes.map(n => buildPattern(n, ctx)) } } -export interface FieldPattern extends AstNode<'field-pattern'> { +export type FieldPattern = BaseAstNode & { + kind: 'field-pattern' name: Name pattern?: Pattern } @@ -138,7 +143,9 @@ export const buildFieldPattern = (node: ParseNode, ctx: Context): FieldPattern = return { kind: 'field-pattern', parseNode: node, name, pattern } } -export interface Hole extends AstNode<'hole'>, Partial {} +export type Hole = BaseAstNode & { + kind: 'hole' +} export const buildHole = (node: ParseNode): Hole => { return { kind: 'hole', parseNode: node } diff --git a/src/ast/op.ts b/src/ast/op.ts index 629f2553..2234c583 100644 --- a/src/ast/op.ts +++ b/src/ast/op.ts @@ -1,15 +1,14 @@ import { ParseNode, filterNonAstNodes } from '../parser' import { Context } from '../scope' -import { MethodDef, VariantDef } from '../scope/vid' import { Static } from '../semantic' import { ConcreteGeneric } from '../typecheck' -import { Arg, AstNode, AstNodeKind, buildArg } from './index' +import { Arg, AstNode, AstNodeKind, BaseAstNode, buildArg } from './index' import { Name, buildName } from './operand' import { Type, buildType } from './type' export type PostfixOp = MethodCallOp | FieldAccessOp | CallOp | UnwrapOp | BindOp | AwaitOp -export const isPostfixOp = (op: AstNode): op is PostfixOp => { +export const isPostfixOp = (op: AstNode): op is PostfixOp => { return ( op.kind === 'method-call-op' || op.kind === 'field-access-op' || @@ -123,7 +122,8 @@ export const buildBinaryOp = (node: ParseNode): BinaryOp => { return { kind: node.kind, parseNode: node } } -export interface MethodCallOp extends AstNode<'method-call-op'> { +export type MethodCallOp = BaseAstNode & { + kind: 'method-call-op' name: Name typeArgs: Type[] call: CallOp @@ -138,7 +138,8 @@ export const buildMethodCallOp = (node: ParseNode, ctx: Context): MethodCallOp = return { kind: 'method-call-op', parseNode: node, name, typeArgs, call } } -export interface FieldAccessOp extends AstNode<'field-access-op'> { +export type FieldAccessOp = BaseAstNode & { + kind: 'field-access-op' name: Name } @@ -147,7 +148,8 @@ export const buildFieldAccessOp = (node: ParseNode, ctx: Context): FieldAccessOp return { kind: 'field-access-op', parseNode: node, name } } -export interface CallOp extends AstNode<'call-op'>, Partial { +export type CallOp = BaseAstNode & { + kind: 'call-op' args: Arg[] methodDef?: MethodDef variantDef?: VariantDef @@ -159,38 +161,74 @@ export const buildCallOp = (node: ParseNode, ctx: Context): CallOp => { return { kind: 'call-op', parseNode: node, args } } -export interface UnwrapOp extends AstNode<'unwrap-op'> {} +export type UnwrapOp = BaseAstNode & { + kind: 'unwrap-op' +} -export interface BindOp extends AstNode<'bind-op'> {} +export type BindOp = BaseAstNode & { + kind: 'bind-op' +} -export interface AwaitOp extends AstNode<'await-op'> {} +export type AwaitOp = BaseAstNode & { + kind: 'await-op' +} -export interface AddOp extends AstNode<'add-op'> {} +export type AddOp = BaseAstNode & { + kind: 'add-op' +} -export interface SubOp extends AstNode<'sub-op'> {} +export type SubOp = BaseAstNode & { + kind: 'sub-op' +} -export interface MultOp extends AstNode<'mult-op'> {} +export type MultOp = BaseAstNode & { + kind: 'mult-op' +} -export interface DivOp extends AstNode<'div-op'> {} +export type DivOp = BaseAstNode & { + kind: 'div-op' +} -export interface ExpOp extends AstNode<'exp-op'> {} +export type ExpOp = BaseAstNode & { + kind: 'exp-op' +} -export interface ModOp extends AstNode<'mod-op'> {} +export type ModOp = BaseAstNode & { + kind: 'mod-op' +} -export interface EqOp extends AstNode<'eq-op'> {} +export type EqOp = BaseAstNode & { + kind: 'eq-op' +} -export interface NeOp extends AstNode<'ne-op'> {} +export type NeOp = BaseAstNode & { + kind: 'ne-op' +} -export interface GeOp extends AstNode<'ge-op'> {} +export type GeOp = BaseAstNode & { + kind: 'ge-op' +} -export interface LeOp extends AstNode<'le-op'> {} +export type LeOp = BaseAstNode & { + kind: 'le-op' +} -export interface GtOp extends AstNode<'gt-op'> {} +export type GtOp = BaseAstNode & { + kind: 'gt-op' +} -export interface LtOp extends AstNode<'lt-op'> {} +export type LtOp = BaseAstNode & { + kind: 'lt-op' +} -export interface AndOp extends AstNode<'and-op'> {} +export type AndOp = BaseAstNode & { + kind: 'and-op' +} -export interface OrOp extends AstNode<'or-op'> {} +export type OrOp = BaseAstNode & { + kind: 'or-op' +} -export interface AssignOp extends AstNode<'assign-op'> {} +export type AssignOp = BaseAstNode & { + kind: 'assign-op' +} diff --git a/src/ast/operand.ts b/src/ast/operand.ts index a3c149a8..d31ade31 100644 --- a/src/ast/operand.ts +++ b/src/ast/operand.ts @@ -1,12 +1,12 @@ import { LexerToken } from '../lexer/lexer' import { ParseNode, ParseTree, filterNonAstNodes } from '../parser' import { nameLikeTokens } from '../parser/fns' -import { Context } from '../scope' +import { Context, Definition } from '../scope' import { VirtualIdentifierMatch } from '../scope/vid' -import { Static, Typed, Virtual } from '../semantic' +import { Virtual } from '../semantic' import { assert } from '../util/todo' import { Expr, buildExpr } from './expr' -import { AstNode, Param, buildParam } from './index' +import { BaseAstNode, Param, buildParam } from './index' import { MatchExpr, Pattern, buildMatchExpr, buildNumber, buildPattern } from './match' import { Block, buildBlock, buildStatement } from './statement' import { Type, buildType } from './type' @@ -71,7 +71,8 @@ export const identifierFromOperand = (operand: Operand): Identifier | undefined return undefined } -export interface IfExpr extends AstNode<'if-expr'>, Partial { +export type IfExpr = BaseAstNode & { + kind: 'if-expr' condition: Expr thenBlock: Block elseBlock?: Block @@ -90,7 +91,8 @@ export const buildIfExpr = (node: ParseNode, ctx: Context): IfExpr => { return { kind: 'if-expr', parseNode: node, condition, thenBlock, elseBlock } } -export interface IfLetExpr extends AstNode<'if-let-expr'>, Partial { +export type IfLetExpr = BaseAstNode & { + kind: 'if-let-expr' pattern: Pattern expr: Expr thenBlock: Block @@ -112,7 +114,8 @@ export const buildIfLetExpr = (node: ParseNode, ctx: Context): IfLetExpr => { return { kind: 'if-let-expr', parseNode: node, pattern, expr, thenBlock, elseBlock } } -export interface WhileExpr extends AstNode<'while-expr'>, Partial { +export type WhileExpr = BaseAstNode & { + kind: 'while-expr' condition: Expr block: Block } @@ -127,7 +130,8 @@ export const buildWhileExpr = (node: ParseNode, ctx: Context): WhileExpr => { return { kind: 'while-expr', parseNode: node, condition, block } } -export interface ForExpr extends AstNode<'for-expr'>, Partial { +export type ForExpr = BaseAstNode & { + kind: 'for-expr' pattern: Pattern expr: Expr block: Block @@ -146,7 +150,8 @@ export const buildForExpr = (node: ParseNode, ctx: Context): ForExpr => { return { kind: 'for-expr', parseNode: node, pattern, expr, block } } -export interface ClosureExpr extends AstNode<'closure-expr'>, Partial { +export type ClosureExpr = BaseAstNode & { + kind: 'closure-expr' params: Param[] block: Block returnType?: Type @@ -166,7 +171,8 @@ export const buildClosureExpr = (node: ParseNode, ctx: Context): ClosureExpr => return { kind: 'closure-expr', parseNode: node, params, block, returnType } } -export interface ListExpr extends AstNode<'list-expr'>, Partial { +export type ListExpr = BaseAstNode & { + kind: 'list-expr' exprs: Expr[] } @@ -176,11 +182,13 @@ export const buildListExpr = (node: ParseNode, ctx: Context): ListExpr => { return { kind: 'list-expr', parseNode: node, exprs } } -export interface StringLiteral extends AstNode<'string-literal'>, Partial { +export type StringLiteral = BaseAstNode & { + kind: 'string-literal' value: string } -export interface StringInterpolated extends AstNode<'string-interpolated'>, Partial { +export type StringInterpolated = BaseAstNode & { + kind: 'string-interpolated' tokens: (string | Expr)[] } @@ -206,7 +214,8 @@ export const buildStringPart = (node: ParseNode, ctx: Context): string | Expr => } } -export interface CharLiteral extends AstNode<'char-literal'>, Partial { +export type CharLiteral = BaseAstNode & { + kind: 'char-literal' value: string } @@ -214,15 +223,18 @@ export const buildChar = (node: ParseNode, ctx: Context): CharLiteral => { return { kind: 'char-literal', parseNode: node, value: (node).value } } -export interface IntLiteral extends AstNode<'int-literal'>, Partial { +export type IntLiteral = BaseAstNode & { + kind: 'int-literal' value: string } -export interface FloatLiteral extends AstNode<'float-literal'>, Partial { +export type FloatLiteral = BaseAstNode & { + kind: 'float-literal' value: string } -export interface BoolLiteral extends AstNode<'bool-literal'>, Partial { +export type BoolLiteral = BaseAstNode & { + kind: 'bool-literal' value: string } @@ -230,7 +242,8 @@ export const buildBool = (node: ParseNode, ctx: Context): BoolLiteral => { return { kind: 'bool-literal', parseNode: node, value: (node).value } } -export interface Identifier extends AstNode<'identifier'>, Partial, Partial { +export type Identifier = BaseAstNode & { + kind: 'identifier' names: Name[] typeArgs: Type[] ref?: VirtualIdentifierMatch @@ -245,8 +258,10 @@ export const buildIdentifier = (node: ParseNode, ctx: Context): Identifier => { return { kind: 'identifier', parseNode: node, names, typeArgs: typeArgs } } -export interface Name extends AstNode<'name'>, Partial { +export type Name = BaseAstNode & { + kind: 'name' value: string + def?: Definition } export const buildName = (node: ParseNode, ctx: Context): Name => { diff --git a/src/ast/statement.ts b/src/ast/statement.ts index e7db9b73..83f254ae 100644 --- a/src/ast/statement.ts +++ b/src/ast/statement.ts @@ -1,12 +1,11 @@ import { ParseNode, filterNonAstNodes } from '../parser' import { Context } from '../scope' import { InstanceRelation } from '../scope/trait' -import { MethodDef } from '../scope/vid' -import { Checked, Typed } from '../semantic' import { assert } from '../util/todo' import { Expr, buildExpr } from './expr' -import { AstNode, Param, buildParam } from './index' +import { BaseAstNode, Param, buildParam } from './index' import { Pattern, buildPattern } from './match' +import { CallOp } from './op' import { Identifier, Name, buildIdentifier, buildName } from './operand' import { Generic, Type, buildGeneric, buildType } from './type' import { TypeDef, buildTypeDef } from './type-def' @@ -36,7 +35,8 @@ export const buildStatement = (node: ParseNode, ctx: Context): Statement => { throw Error(`expected statement, got ${node.kind}`) } -export interface UseExpr extends AstNode<'use-expr'> { +export type UseExpr = BaseAstNode & { + kind: 'use-expr' scope: Name[] expr: UseExpr[] | Name pub: boolean @@ -72,7 +72,8 @@ export const buildUseExpr = (node: ParseNode, ctx: Context): UseExpr => { return { kind: 'use-expr', parseNode: node, scope: names.slice(0, -1), expr: names.at(-1)!, pub } } -export interface VarDef extends AstNode<'var-def'>, Partial { +export type VarDef = BaseAstNode & { + kind: 'var-def' pattern: Pattern varType?: Type expr?: Expr @@ -93,7 +94,7 @@ export const buildVarDef = (node: ParseNode, ctx: Context): VarDef => { return { kind: 'var-def', parseNode: node, pattern, varType, expr, pub } } -export interface FnDef extends AstNode<'fn-def'>, Partial, Partial { +export type FnDef = BaseAstNode & { kind: 'fn-def' name: Name generics: Generic[] @@ -120,7 +121,8 @@ export const buildFnDef = (node: ParseNode, ctx: Context): FnDef => { return { kind: 'fn-def', parseNode: node, name, generics, params, block, returnType, pub } } -export interface TraitDef extends AstNode<'trait-def'> { +export type TraitDef = BaseAstNode & { + kind: 'trait-def' name: Name generics: Generic[] block: Block @@ -141,12 +143,13 @@ export const buildTraitDef = (node: ParseNode, ctx: Context): TraitDef => { return { kind: 'trait-def', parseNode: node, name, generics, block, pub } } -export interface ImplDef extends AstNode<'impl-def'>, Partial { +export type ImplDef = BaseAstNode & { + kind: 'impl-def' identifier: Identifier generics: Generic[] forTrait?: Identifier block: Block - superMethods?: MethodDef[] + superMethods?: CallOp[] rel?: InstanceRelation } @@ -164,7 +167,8 @@ export const buildImplDef = (node: ParseNode, ctx: Context): ImplDef => { return { kind: 'impl-def', parseNode: node, identifier, generics, forTrait, block } } -export interface ReturnStmt extends AstNode<'return-stmt'>, Partial { +export type ReturnStmt = BaseAstNode & { + kind: 'return-stmt' returnExpr: Expr } @@ -175,13 +179,16 @@ export const buildReturnStmt = (node: ParseNode, ctx: Context): ReturnStmt => { return { kind: 'return-stmt', parseNode: node, returnExpr } } -export interface BreakStmt extends AstNode<'break-stmt'> {} +export type BreakStmt = BaseAstNode & { + kind: 'break-stmt' +} export const buildBreakStmt = (node: ParseNode, ctx: Context): BreakStmt => { return { kind: 'break-stmt', parseNode: node } } -export interface Block extends AstNode<'block'>, Partial { +export type Block = BaseAstNode & { + kind: 'block' statements: Statement[] } diff --git a/src/ast/type-def.ts b/src/ast/type-def.ts index 802f71f0..ba156e69 100644 --- a/src/ast/type-def.ts +++ b/src/ast/type-def.ts @@ -1,11 +1,11 @@ +import { BaseAstNode } from '.' import { ParseNode, filterNonAstNodes } from '../parser' import { Context } from '../scope' -import { Checked, Typed } from '../semantic' -import { AstNode } from './index' import { Name, buildName } from './operand' import { Generic, Type, buildGeneric, buildType } from './type' -export interface TypeDef extends AstNode<'type-def'>, Partial { +export type TypeDef = BaseAstNode & { + kind: 'type-def' name: Name generics: Generic[] variants: Variant[] @@ -36,9 +36,11 @@ export const buildTypeDef = (node: ParseNode, ctx: Context): TypeDef => { return { kind: 'type-def', parseNode: node, name, generics, variants, pub } } -export interface Variant extends AstNode<'variant'>, Partial { +export type Variant = BaseAstNode & { + kind: 'variant' name: Name fieldDefs: FieldDef[] + typeDef?: TypeDef } export const buildTypeCon = (node: ParseNode, ctx: Context): Variant => { @@ -48,7 +50,8 @@ export const buildTypeCon = (node: ParseNode, ctx: Context): Variant => { return { kind: 'variant', parseNode: node, name, fieldDefs } } -export interface FieldDef extends AstNode<'field-def'>, Partial { +export type FieldDef = BaseAstNode & { + kind: 'field-def' name: Name fieldType: Type pub: boolean diff --git a/src/ast/type.ts b/src/ast/type.ts index 08f26790..6822fe60 100644 --- a/src/ast/type.ts +++ b/src/ast/type.ts @@ -1,7 +1,7 @@ +import { BaseAstNode } from '.' import { ParseNode, filterNonAstNodes } from '../parser' import { Context } from '../scope' import { Checked } from '../semantic' -import { AstNode } from './index' import { Hole, buildHole } from './match' import { Identifier, Name, buildIdentifier, buildName } from './operand' @@ -25,7 +25,8 @@ export const buildType = (node: ParseNode, ctx: Context): Type => { } } -export interface TypeBounds extends AstNode<'type-bounds'> { +export type TypeBounds = BaseAstNode & { + kind: 'type-bounds' bounds: Identifier[] } @@ -35,7 +36,8 @@ export const buildTypeBounds = (node: ParseNode, ctx: Context): TypeBounds => { return { kind: 'type-bounds', parseNode: node, bounds } } -export interface Generic extends AstNode<'generic'> { +export type Generic = BaseAstNode & { + kind: 'generic' name: Name key?: string bounds: Identifier[] @@ -48,7 +50,8 @@ export const buildGeneric = (node: ParseNode, ctx: Context): Generic => { return { kind: 'generic', parseNode: node, name, bounds: bounds } } -export interface FnType extends AstNode<'fn-type'> { +export type FnType = BaseAstNode & { + kind: 'fn-type' generics: Generic[] paramTypes: Type[] returnType: Type diff --git a/src/cli.ts b/src/cli.ts index 46475771..619e01f7 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,4 +1,4 @@ -import { colorError, prettySourceMessage } from './error' +import { colorError, colorWarning, prettySourceMessage } from './error' import { getSpan } from './parser' import { Context } from './scope' @@ -20,3 +20,16 @@ export const reportErrors = (ctx: Context): void | never => { process.exit(1) } } + +export const reportWarnings = (ctx: Context): void | never => { + for (const warning of ctx.warnings) { + console.error( + prettySourceMessage( + colorWarning(warning.message), + getSpan(warning.node.parseNode), + warning.source, + warning.notes + ) + ) + } +} diff --git a/src/codegen/js/expr.ts b/src/codegen/js/expr.ts index e46a92d8..bc160b09 100644 --- a/src/codegen/js/expr.ts +++ b/src/codegen/js/expr.ts @@ -4,14 +4,13 @@ import { BinaryExpr, Expr, OperandExpr, UnaryExpr } from '../../ast/expr' import { MatchExpr, Pattern, PatternExpr } from '../../ast/match' import { Identifier, Operand } from '../../ast/operand' import { Context } from '../../scope' -import { relTypeName } from '../../scope/trait' import { operatorImplMap } from '../../semantic/op' import { ConcreteGeneric } from '../../typecheck' import { unreachable } from '../../util/todo' import { EmitNode, EmitToken, emitToken, emitTree, jsError, jsVariable } from './node' import { emitBlock, emitBlockStatements } from './statement' -export interface EmitExpr { +export type EmitExpr = { emit: EmitNode resultVar: string } @@ -304,24 +303,24 @@ export const emitOperand = (operand: Operand, ctx: Context): EmitExpr => { } export const emitIdentifier = (identifier: Identifier, ctx: Context): EmitExpr => { - if (identifier.ref?.def.kind === 'method-def') { + if (identifier.ref?.node.kind === 'method-def') { const staticCallEmit = identifier.impl ? jsRelName(identifier.impl) : undefined - if (identifier.ref.def.fn.static === true) { + if (identifier.ref.node.fn.static === true) { const typeName = identifier.names.at(-2)!.value - const traitName = relTypeName(identifier.ref.def.rel) + const traitName = relTypeName(identifier.ref.node.rel) const callerEmit = staticCallEmit ?? `${typeName}.${traitName}` return { emit: emitToken(''), - resultVar: `${callerEmit}().${identifier.ref.def.fn.name.value}` + resultVar: `${callerEmit}().${identifier.ref.node.fn.name.value}` } } else { - const args = identifier.ref.def.fn.params.map((_, i) => { + const args = identifier.ref.node.fn.params.map((_, i) => { const v = nextVariable(ctx) const upcast = (identifier).upcastFn?.paramUpcasts.at(i) return { emit: upcast ? emitUpcasts(v, [upcast]) : emitToken(''), resultVar: v } }) - const relName = jsRelName(identifier.ref.def.rel) - const fnName = identifier.ref.def.fn.name.value + const relName = jsRelName(identifier.ref.node.rel) + const fnName = identifier.ref.node.fn.name.value const callerEmit = staticCallEmit ?? `${args[0].resultVar}.${relName}` const delegate = `return ${callerEmit}().${fnName}(${args.map(a => a.resultVar)});` const block = `${args.map(a => (a.emit).value)}${delegate}` diff --git a/src/codegen/js/index.ts b/src/codegen/js/index.ts index 60c21e9b..b73bd635 100644 --- a/src/codegen/js/index.ts +++ b/src/codegen/js/index.ts @@ -1,6 +1,5 @@ import { Module } from '../../ast' import { Context } from '../../scope' -import { InstanceRelation, relTypeName } from '../../scope/trait' import { concatVid, vidFromString } from '../../scope/util' import { VirtualIdentifier } from '../../scope/vid' import { Upcast } from '../../semantic/upcast' @@ -10,7 +9,7 @@ import { unreachable } from '../../util/todo' import { EmitNode, EmitToken, emitToken, emitTree } from './node' import { emitStatement } from './statement' -export interface JsImport { +export type JsImport = { def: string path: string } @@ -35,8 +34,8 @@ export const emitImports = (module: Module, ctx: Context): EmitNode => { let vid = i.vid // variant constructors are always accessible from type reference, e.g. `Option.Some`, so only `Option` // needs to be imported - if (i.def.kind === 'variant') { - vid = { names: [...i.module.identifier.names, i.def.typeDef.name.value] } + if (i.node.kind === 'variant') { + vid = { names: [...i.module.identifier.names, i.node.typeDef.name.value] } } return makeJsImport(vid, i.module, module, ctx) }) diff --git a/src/codegen/js/node.ts b/src/codegen/js/node.ts index 4f6b896c..f4ce80d6 100644 --- a/src/codegen/js/node.ts +++ b/src/codegen/js/node.ts @@ -3,7 +3,7 @@ import { ParseNode } from '../../parser' export type EmitNode = EmitToken | EmitTree -export interface EmitToken { +export type EmitToken = { kind: 'token' value: string parseNode?: ParseNode @@ -17,7 +17,7 @@ export const emitToken = (value: string, parseNode?: ParseNode): EmitToken => { } } -export interface EmitTree { +export type EmitTree = { kind: 'node' nodes: EmitNode[] parseNode?: ParseNode diff --git a/src/config.ts b/src/config.ts index 22544538..07dd58be 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,6 @@ import { parseOption } from './cli' -export interface Config { +export type Config = { pkgName?: string pkgPath: string srcPath: string @@ -12,7 +12,7 @@ export interface Config { output: OutConfig } -export interface OutConfig { +export type OutConfig = { write: boolean } diff --git a/src/error.ts b/src/error.ts index c3545400..08f2c7de 100644 --- a/src/error.ts +++ b/src/error.ts @@ -4,7 +4,7 @@ import { red, yellow } from './output' import { Parser } from './parser' import { Source } from './source' -export interface SyntaxError { +export type SyntaxError = { expected: TokenKind[] got: LexerToken message?: string diff --git a/src/index.ts b/src/index.ts index dd93320b..c7f7039b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,16 @@ import { existsSync, readFileSync, statSync } from 'fs' import { basename, dirname, join } from 'path' import { fileURLToPath } from 'url' -import { parseOption, reportErrors } from './cli' +import { parseOption, reportErrors, reportWarnings } from './cli' import { fromCmd } from './config' -import { colorWarning, prettySourceMessage } from './error' import { Package } from './package' import { buildModule } from './package/build' import { emitPackage } from './package/emit' import { buildPackage } from './package/io' -import { getSpan } from './parser' -import { Context, pathToVid } from './scope' -import { buildInstanceRelations } from './scope/trait' -import { checkModule, checkTopLevelDefinition, prepareModule } from './semantic' +import { resolveImport } from './phase/import-resolve' +import { resolveModuleScope } from './phase/module-resolve' +import { resolveName } from './phase/name-resolve' +import { Context, eachModule as forEachModule, pathToVid } from './scope' import { Source } from './source' import { assert } from './util/todo' @@ -52,13 +51,10 @@ const ctx: Context = { config, moduleStack: [], packages: [], - impls: [], errors: [], warnings: [], - check: false, silent: false, - variableCounter: 0, - relChainsMemo: new Map() + variableCounter: 0 } let pkg: Package @@ -123,34 +119,13 @@ if (!std) { ctx.packages = packages ctx.prelude = std.modules.find(m => m.identifier.names.at(-1)! === 'prelude')! +assert(!!ctx.prelude, 'no prelude') -ctx.packages.forEach(p => p.modules.forEach(m => prepareModule(m))) -ctx.impls = buildInstanceRelations(ctx) -assert(ctx.moduleStack.length === 0, ctx.moduleStack.length.toString()) - -ctx.impls.forEach(impl => checkTopLevelDefinition(impl.module, impl.instanceDef, ctx)) -assert(ctx.moduleStack.length === 0, ctx.moduleStack.length.toString()) - -ctx.check = true -if (ctx.config.libCheck) { - ctx.packages.flatMap(p => p.modules).forEach(m => checkModule(m, ctx)) -} else { - pkg.modules.forEach(m => checkModule(m, ctx)) -} -assert(ctx.moduleStack.length === 0, ctx.moduleStack.length.toString()) +const phases = [resolveModuleScope, resolveImport, resolveName] +phases.forEach(f => forEachModule(f, ctx)) reportErrors(ctx) - -for (const warning of ctx.warnings) { - console.error( - prettySourceMessage( - colorWarning(warning.message), - getSpan(warning.node.parseNode), - warning.source, - warning.notes - ) - ) -} +reportWarnings(ctx) if (config.emit) { await emitPackage(isDir, pkg, ctx) diff --git a/src/lexer/lexer.ts b/src/lexer/lexer.ts index d532268f..b2b9a928 100644 --- a/src/lexer/lexer.ts +++ b/src/lexer/lexer.ts @@ -71,7 +71,7 @@ export type TokenKind = (typeof lexerTokenKinds)[number] export const erroneousTokenKinds: TokenKind[] = ['unknown', 'char-unterminated'] -export interface LexerToken { +export type LexerToken = { kind: TokenKind value: string span: Span @@ -139,7 +139,7 @@ export const isWhitespace = (char: string): boolean => char === ' ' || char === export const isNewline = (char: string): boolean => char === '\n' || char === '\r' -interface LexerContext { +export type LexerContext = { code: string pos: number tokens: LexerToken[] diff --git a/src/location.ts b/src/location.ts index 1cdf3b8e..b5d85f7a 100644 --- a/src/location.ts +++ b/src/location.ts @@ -1,7 +1,7 @@ import { isNewline } from './lexer/lexer' import { Source } from './source' -export interface Span { +export type Span = { /** * Start position index of this span, inclusive */ @@ -12,7 +12,7 @@ export interface Span { end: number } -export interface Location { +export type Location = { line: number column: number } diff --git a/src/package/index.ts b/src/package/index.ts index 70828b63..54a20ccb 100644 --- a/src/package/index.ts +++ b/src/package/index.ts @@ -1,6 +1,6 @@ import { Module } from '../ast' -export interface Package { +export type Package = { path: string name: string modules: Module[] diff --git a/src/parser/index.ts b/src/parser/index.ts index ddc2db94..77c322bb 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -91,7 +91,7 @@ export type TreeKind = (typeof treeKinds)[number] export type NodeKind = TokenKind | TreeKind -export interface ParseTree { +export type ParseTree = { kind: TreeKind nodes: ParseNode[] } diff --git a/src/phase/import-resolve.ts b/src/phase/import-resolve.ts new file mode 100644 index 00000000..e45e14bf --- /dev/null +++ b/src/phase/import-resolve.ts @@ -0,0 +1,13 @@ +import { Module } from '../ast' +import { Context } from '../scope' +import { useExprToVids } from '../semantic/use-expr' +import { todo } from '../util/todo' + +/** + * Check use exprs and populate module.useScope + */ +export const resolveImport = (module: Module, ctx: Context): void => { + module.references = module.useExprs.filter(e => !e.pub).flatMap(e => useExprToVids(e)) + module.reExports = module.useExprs.filter(e => e.pub).flatMap(e => useExprToVids(e)) + todo() +} diff --git a/src/phase/module-resolve.ts b/src/phase/module-resolve.ts new file mode 100644 index 00000000..fbcaaaa2 --- /dev/null +++ b/src/phase/module-resolve.ts @@ -0,0 +1,55 @@ +import { AstNode } from '../ast' +import { Name } from '../ast/operand' +import { FnDef, TraitDef } from '../ast/statement' +import { TypeDef } from '../ast/type-def' +import { Context, addError, defKey } from '../scope' +import { duplicateDefError } from '../semantic/error' +import { todo } from '../util/todo' + +/** + * Resolve module definitions available from the outside + */ +export const resolveModuleScope = (node: AstNode, ctx: Context): void => { + switch (node.kind) { + case 'module': { + for (const statement of node.block.statements) { + resolveModuleScope(statement, ctx) + } + break + } + case 'fn-def': + case 'trait-def': + case 'type-def': { + addDef(node, ctx) + break + } + case 'var-def': { + if (node.pub && node.pattern.expr.kind === 'name') { + addDef(node.pattern.expr, ctx) + } else if (node.pattern.expr.kind === 'hole') { + } else { + todo('destructuring is not allowed in module scope') + } + break + } + } + switch (node.kind) { + case 'type-def': { + for (const variant of node.variants) { + variant.typeDef = node + } + } + } +} + +const addDef = (node: FnDef | TraitDef | TypeDef | Name, ctx: Context): void => { + const m = ctx.moduleStack.at(-1)! + const key = defKey(node) + if (m.topScope.has(key)) { + addError(ctx, duplicateDefError(ctx, node)) + return + } + if (node.kind === 'name' || node.pub) { + m.topScope.set(key, node) + } +} diff --git a/src/phase/name-resolve.ts b/src/phase/name-resolve.ts new file mode 100644 index 00000000..38313822 --- /dev/null +++ b/src/phase/name-resolve.ts @@ -0,0 +1,77 @@ +import { AstNode } from '../ast' +import { Context } from '../scope' + +/** + * Resolve every name to its definition + */ +export const resolveName = (node: AstNode, ctx: Context): void => { + switch (node.kind) { + case 'module': { + for (const statement of node.block.statements) { + resolveName(statement, ctx) + } + break + } + case 'use-expr': + case 'variant': + case 'return-stmt': + case 'break-stmt': + case 'arg': + case 'block': + case 'param': + case 'type-bounds': + case 'fn-type': + case 'generic': + case 'if-expr': + case 'match-clause': + case 'pattern': + case 'con-pattern': + case 'list-pattern': + case 'field-pattern': + case 'hole': + case 'identifier': + case 'name': + case 'string-interpolated': + case 'operand-expr': + case 'unary-expr': + case 'binary-expr': + case 'closure-expr': + case 'list-expr': + case 'if-let-expr': + case 'while-expr': + case 'for-expr': + case 'match-expr': + case 'var-def': + case 'fn-def': + case 'trait-def': + case 'impl-def': + case 'type-def': + case 'field-def': + case 'string-literal': + case 'char-literal': + case 'int-literal': + case 'float-literal': + case 'bool-literal': + case 'add-op': + case 'sub-op': + case 'mult-op': + case 'div-op': + case 'exp-op': + case 'mod-op': + case 'eq-op': + case 'ne-op': + case 'ge-op': + case 'le-op': + case 'gt-op': + case 'lt-op': + case 'and-op': + case 'or-op': + case 'assign-op': + case 'method-call-op': + case 'field-access-op': + case 'call-op': + case 'unwrap-op': + case 'bind-op': + case 'await-op': + } +} diff --git a/src/scope/index.ts b/src/scope/index.ts index c1357f8b..c2660712 100644 --- a/src/scope/index.ts +++ b/src/scope/index.ts @@ -1,100 +1,51 @@ import { Module } from '../ast' -import { ClosureExpr, Operand } from '../ast/operand' +import { Name } from '../ast/operand' import { FnDef, ImplDef, TraitDef } from '../ast/statement' -import { TypeDef } from '../ast/type-def' +import { Generic } from '../ast/type' +import { TypeDef, Variant } from '../ast/type-def' import { Config } from '../config' import { Package } from '../package' import { SemanticError } from '../semantic/error' -import { InstanceRelation } from './trait' import { vidToString } from './util' -import { Definition, VirtualIdentifier } from './vid' +import { VirtualIdentifier } from './vid' -export interface Context { +export type Context = { config: Config - // TODO: store reference chain instead of plain modules + // TODO: store reference chain instead of plain modules to track recursion moduleStack: Module[] packages: Package[] /** * `std::prelude` module */ prelude?: Module - impls: InstanceRelation[] errors: SemanticError[] warnings: SemanticError[] - /** - * When disabled, semantic checker will visit fn-def blocks only to populate top-level type information - */ - check: boolean /** * Suppress all errors and warnings that coming while the field is false */ silent: boolean variableCounter: number - - relChainsMemo: Map } -export type Scope = InstanceScope | TypeDefScope | FnDefScope | BlockScope | ModuleScope - /** - * Map id has to be composite, since different defs might have the same vid, e.g. - * type Option and impl Option. - * Due to JS limitations, definition must be converted into string first - * Use {@link defKey} to create keys + * Key is a name of the def */ export type DefinitionMap = Map -export interface InstanceScope extends BaseScope { - kind: 'instance' - def: TraitDef | ImplDef - rel?: InstanceRelation -} - -export interface TypeDefScope extends BaseScope { - kind: 'type' - def: TypeDef - vid: VirtualIdentifier -} - -export interface FnDefScope extends BaseScope { - kind: 'fn' - def: FnDef | ClosureExpr - returns: Operand[] -} - -export interface BlockScope extends BaseScope { - kind: 'block' - isLoop: boolean - allBranchesReturned: boolean -} - -export interface ModuleScope extends BaseScope { - kind: 'module' -} - -export interface BaseScope { - definitions: DefinitionMap - closures: ClosureExpr[] -} +export type Definition = Module | Name | FnDef | TraitDef | TypeDef | Variant | Generic export const defKey = (def: Definition): string => { switch (def.kind) { case 'module': - return def.kind + vidToString(def.identifier) - case 'self': - return def.kind - case 'name-def': + return vidToString(def.identifier) + case 'name': + return def.value case 'fn-def': case 'trait-def': case 'type-def': - case 'generic': - return def.kind + def.name.value - case 'method-def': - return 'fn-def' + def.fn.name.value - case 'impl-def': - return def.kind + def.identifier.names.at(-1)!.value case 'variant': - return def.kind + def.variant.name.value + case 'generic': + return def.name.value } } @@ -149,3 +100,13 @@ export const leaveScope = (module: Module, ctx: Context): void => { // TODO: check malleable closures getting out of scope module.scopeStack.pop() } + +export const eachModule = (f: (module: Module, ctx: Context) => void, ctx: Context): void => { + ctx.packages.forEach(p => + p.modules.forEach(m => { + ctx.moduleStack.push(m) + f(m, ctx) + ctx.moduleStack.pop() + }) + ) +} diff --git a/src/scope/trait.spec.ts b/src/scope/trait.spec.ts deleted file mode 100644 index d87e8092..00000000 --- a/src/scope/trait.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { dirname, join } from 'path' -import { fileURLToPath } from 'url' -import { Context } from '.' -import { makeConfig } from '../config' -import { buildPackage } from '../package/io' -import { prepareModule } from '../semantic' -import { virtualTypeToString } from '../typecheck' -import { InstanceRelation, buildInstanceRelations, findSuperRelChains } from './trait' -import { vidFromString } from './util' - -describe('trait', () => { - const makeCtx = (): Context => { - const config = makeConfig('test', 'test.no') - const ctx: Context = { - config, - moduleStack: [], - packages: [], - impls: [], - errors: [], - warnings: [], - check: false, - silent: false, - variableCounter: 0, - relChainsMemo: new Map() - } - - const std = buildPackage(join(dirname(fileURLToPath(import.meta.url)), '..', 'std'), 'std', ctx)! - ctx.packages = [std] - ctx.prelude = std.modules.find(m => m.identifier.names.at(-1)! === 'prelude')! - - ctx.packages.forEach(p => { - p.modules.forEach(m => { - prepareModule(m) - }) - }) - ctx.impls = buildInstanceRelations(ctx) - ctx.check = true - - return ctx - } - - it('findSuperRelChains', () => { - const ctx = makeCtx() - const formatImplTypes = (chains: InstanceRelation[][]): string[][] => - chains.map(c => c.map(rel => rel.implType).map(virtualTypeToString)) - - expect(formatImplTypes(findSuperRelChains(vidFromString('std::unit::Unit'), ctx))).toEqual([ - ['std::io::trace::Trace'] - ]) - - expect(formatImplTypes(findSuperRelChains(vidFromString('std::string::String'), ctx))).toEqual([ - ['std::io::show::Show'], - ['std::io::trace::Trace'], - ['std::eq::Eq'], - ['std::iter::Collector'], - ['std::iter::Collector'], - ['std::copy::Copy'] - ]) - - expect(formatImplTypes(findSuperRelChains(vidFromString('std::list::List'), ctx))).toEqual([ - ['std::iter::Iterable'], - ['std::iter::Collector'], - ['std::io::show::Show'], - ['std::io::show::Show'], - ['std::io::trace::Trace'], - ['std::copy::Copy'] - ]) - }) -}) diff --git a/src/scope/trait.ts b/src/scope/trait.ts deleted file mode 100644 index 9836e8e3..00000000 --- a/src/scope/trait.ts +++ /dev/null @@ -1,302 +0,0 @@ -import { Module } from '../ast' -import { ImplDef, TraitDef } from '../ast/statement' -import { TypeDef } from '../ast/type-def' -import { notFoundError } from '../semantic/error' -import { - VidType, - VirtualGeneric, - VirtualType, - genericToVirtual, - isAssignable, - typeEq, - typeToVirtual -} from '../typecheck' -import { makeGenericMapOverStructure, resolveType } from '../typecheck/generic' -import { assert } from '../util/todo' -import { Context, Scope, addError, defKey, enterScope, leaveScope } from './index' -import { concatVid, idToVid, vidEq, vidFromString, vidToString } from './util' -import { MethodDef, VirtualIdentifier, VirtualIdentifierMatch, resolveVid, typeKinds } from './vid' - -/** - * Description of type/trait/impl relations - */ -export interface InstanceRelation { - /** - * Module impl defined in - */ - module: Module - /** - * Implemented type - */ - implType: VirtualType - /** - * For type - */ - forType: VirtualType - /** - * Implemented type def - */ - implDef: VirtualIdentifierMatch - /** - * For type def - */ - forDef: VirtualIdentifierMatch - /** - * Instance def - */ - instanceDef: TraitDef | ImplDef - /** - * Generics - */ - generics: VirtualGeneric[] - /** - * There are two types of implementations: - * - trait impl: implementing trait for some type - * - inherent impl: attaching methods to some type - */ - inherent: boolean -} - -export const buildInstanceRelations = (ctx: Context): InstanceRelation[] => { - const impls = ctx.packages - .flatMap(p => p.modules) - .flatMap(m => - m.block.statements - .filter(s => s.kind === 'trait-def' || s.kind === 'impl-def') - .map(impl => [m, impl]) - ) - return impls.flatMap(([module, impl]) => { - ctx.moduleStack.push(module) - const implRel = getRel(impl, ctx) - ctx.moduleStack.pop() - return implRel ? [implRel] : [] - }) -} - -/** - * Construct instance relation from instance definition - */ -const getRel = (instance: TraitDef | ImplDef, ctx: Context): InstanceRelation | undefined => { - const module = ctx.moduleStack.at(-1)! - const scope: Scope = { - kind: 'instance', - def: instance, - definitions: new Map(instance.generics.map(g => [defKey(g), g])), - closures: [] - } - enterScope(module, scope, ctx) - - const implRel = - instance.kind === 'trait-def' ? getTraitRel(instance, module, ctx) : getImplRel(instance, module, ctx) - - leaveScope(module, ctx) - - return implRel -} - -const getTraitRel = (instance: TraitDef, module: Module, ctx: Context): InstanceRelation | undefined => { - const generics = instance.generics.filter(g => g.name.value !== 'Self').map(g => genericToVirtual(g, ctx)) - const traitType: VirtualType = { - kind: 'vid-type', - identifier: { names: [...module.identifier.names, instance.name.value] }, - // self args are for bounds and should be excluded from virtual types - typeArgs: generics - } - const ref = resolveVid(traitType.identifier, ctx, ['trait-def']) - assert(!!ref, 'traitDef did not find itself by name') - const traitRef = >ref! - return { - module, - implType: traitType, - forType: traitType, - implDef: traitRef, - forDef: traitRef, - instanceDef: instance, - generics, - inherent: false - } -} - -const getImplRel = (instance: ImplDef, module: Module, ctx: Context): InstanceRelation | undefined => { - const generics = instance.generics.filter(g => g.name.value !== 'Self').map(g => genericToVirtual(g, ctx)) - const implVid = idToVid(instance.identifier) - const ref = resolveVid(implVid, ctx, ['trait-def', 'type-def']) - if (!ref || (ref.def.kind !== 'trait-def' && ref.def.kind !== 'type-def')) { - addError(ctx, notFoundError(ctx, instance.identifier, vidToString(implVid))) - return undefined - } - const implRef = >ref - - let forDef: VirtualIdentifierMatch = implRef - if (instance.forTrait) { - const forTraitVid = idToVid(instance.forTrait) - const ref = resolveVid(forTraitVid, ctx, typeKinds) - if (!ref) { - addError(ctx, notFoundError(ctx, instance.forTrait, vidToString(forTraitVid))) - return undefined - } - forDef = >ref - } - - const implType = typeToVirtual(instance.identifier, ctx) - const forType = instance.forTrait ? typeToVirtual(instance.forTrait, ctx) : implType - return { - module, - implType, - forType, - implDef: >ref, - forDef: forDef, - instanceDef: instance, - generics, - inherent: !instance.forTrait - } -} - -/** - * Find all instance relation chains to supertypes (types/traits implemented by specified type), ignoring current scope - * Chains take a format [type, ..., super] - * Every chain corresponds to a single `impl for` where type is assignable to impl trait - * For all, chain.length > 0 - * For example: - * - String -> [Show for String], [Eq for String], ...] - * - Iter -> [[Iterable for Iter], [PeekableAdapter for Iter], [MapAdapter for Iter], ...] - * - ListIter -> [[Iter for ListIter], [Iter for ListIter, Iterable for Iter], ...] - * TODO: detect cases where impl.forType is a generic, e.g. `impl A for T` - */ -export const findSuperRelChains = ( - typeVid: VirtualIdentifier, - ctx: Context, - chain: InstanceRelation[] = [] -): InstanceRelation[][] => { - const key = vidToString(typeVid) - const memo = ctx.relChainsMemo.get(key) - if (memo) return memo - - const chains = ctx.impls - // avoid infinite recursion by looking up for the same type - .filter(r => !vidEq(r.implDef.vid, typeVid) && vidEq(r.forDef.vid, typeVid)) - .flatMap(r => { - const newChain = [...chain, r] - return [newChain, ...findSuperRelChains(r.implDef.vid, ctx, newChain)] - }) - - ctx.relChainsMemo.set(key, chains) - return chains -} - -export const getInstanceForType = (implDef: TraitDef | ImplDef, ctx: Context): VirtualType => { - const implRel = ctx.impls.find(i => i.instanceDef === implDef) - assert(!!implRel) - return implRel!.forType -} - -/** - * Convert instance def into virtual type. - * Must be defined in a module that is currently at the top of module stack - */ -export const traitDefToVirtualType = (traitDef: TraitDef | ImplDef, ctx: Context): VidType => { - const module = ctx.moduleStack.at(-1)! - const name = traitDef.kind === 'trait-def' ? traitDef.name.value : traitDef.identifier.names.at(-1)!.value - return { - kind: 'vid-type', - identifier: concatVid(module.identifier, vidFromString(name)), - typeArgs: traitDef.generics.map(g => genericToVirtual(g, ctx)) - } -} - -/** - * Convert type def into virtual type. - */ -export const typeDefToVirtualType = (typeDef: TypeDef, ctx: Context, module = ctx.moduleStack.at(-1)!): VidType => { - const name = typeDef.name.value - return { - kind: 'vid-type', - identifier: concatVid(module.identifier, vidFromString(name)), - typeArgs: typeDef.generics.map(g => genericToVirtual(g, ctx)) - } -} - -/** - * Find concrete type of implType by mapping generics - * Example: `getConcreteTrait(List, impl Iterable for List) -> Iterable` - */ -export const getConcreteTrait = (type: VirtualType, rel: InstanceRelation, ctx: Context): VirtualType => { - const genericMap = makeGenericMapOverStructure(type, rel.forType) - return resolveType(rel.implType, [genericMap], ctx) -} - -export const relTypeName = (rel: InstanceRelation): string => { - if (rel.instanceDef.kind === 'impl-def') { - return rel.instanceDef.identifier.names.at(-1)!.value - } else { - return rel.instanceDef.name.value - } -} - -export const resolveTypeImpl = ( - type: VirtualType, - traitType: VirtualType, - ctx: Context -): { trait: InstanceRelation; impl: InstanceRelation } | undefined => { - if (traitType.kind !== 'vid-type') return undefined - const traitRef = resolveVid(traitType.identifier, ctx, typeKinds) - if (!traitRef || traitRef.def.kind !== 'trait-def') return undefined - const trait = ctx.impls.find(i => i.instanceDef === traitRef.def)! - const candidates = ctx.impls - .filter(i => { - return ( - !i.inherent && - (i.instanceDef.kind === 'impl-def' || - i.instanceDef.block.statements.every(s => s.kind === 'fn-def' && s.block)) && - isAssignable(type, i.forType, ctx) && - isAssignable(i.implType, traitType, ctx) - ) - }) - .toSorted((a, b) => relComparator(b, ctx, type, traitType) - relComparator(a, ctx, type, traitType)) - const impl = candidates.at(0) - return impl ? { trait, impl } : undefined -} - -export const resolveMethodImpl = (type: VirtualType, method: MethodDef, ctx: Context): InstanceRelation | undefined => { - const candidates = ctx.impls - .filter( - i => - (i.instanceDef.kind === 'impl-def' || - i.instanceDef.block.statements.find( - s => s.kind === 'fn-def' && s.name.value === method.fn.name.value && s.block - )) && - isAssignable(type, i.forType, ctx) && - typeEq(i.implType, method.rel.implType) && - (!i.inherent || - i.instanceDef.block.statements.find( - s => s.kind === 'fn-def' && s.name.value === method.fn.name.value - )) - ) - .toSorted( - (a, b) => relComparator(b, ctx, type, method.rel.forType) - relComparator(a, ctx, type, method.rel.forType) - ) - return candidates.at(0) -} - -export const relComparator = ( - rel: InstanceRelation, - ctx?: Context, - implType?: VirtualType, - forType?: VirtualType -): number => { - let score = 0 - // TODO: refactor - if ( - implType && - implType.kind === 'vid-type' && - rel.implType.kind === 'vid-type' && - implType.typeArgs.length > 0 && - rel.implType.typeArgs.length > 0 && - implType.typeArgs[0].kind === 'vid-type' && - rel.implType.typeArgs[0].kind === 'vid-type' - ) { - score += 16 - } - if (rel.instanceDef.kind === 'impl-def') score += 8 - return score -} diff --git a/src/scope/vid.ts b/src/scope/vid.ts index 560cf875..eefbe9f8 100644 --- a/src/scope/vid.ts +++ b/src/scope/vid.ts @@ -1,17 +1,12 @@ -import { Module } from '../ast' -import { Name } from '../ast/operand' -import { FnDef, ImplDef, Statement, TraitDef } from '../ast/statement' -import { Generic } from '../ast/type' -import { TypeDef, Variant } from '../ast/type-def' -import { TopLevelChecked, checkTopLevelDefinition } from '../semantic' -import { Upcast } from '../semantic/upcast' +import { AstNode, Module } from '../ast' +import { checkTopLevelDefinition } from '../semantic' import { selfType } from '../typecheck/type' import { unreachable } from '../util/todo' import { Context, Scope, instanceScope } from './index' -import { InstanceRelation, findSuperRelChains } from './trait' +import { findSuperRelChains } from './trait' import { concatVid, idToVid, vidEq, vidFromString, vidToString } from './util' -export interface VirtualIdentifier { +export type VirtualIdentifier = { names: string[] } @@ -32,47 +27,10 @@ export type DefinitionKind = (typeof defKinds)[number] export const typeKinds: DefinitionKind[] = ['type-def', 'trait-def', 'generic', 'self'] -export type Definition = ( - | Module - | NameDef - | FnDef - | TraitDef - | ImplDef - | TypeDef - | VariantDef - | Generic - | SelfDef - | MethodDef -) & - Partial - -export interface NameDef { - kind: 'name-def' - name: Name - parent?: Statement -} - -export interface SelfDef { - kind: 'self' -} - -export interface VariantDef { - kind: 'variant' - variant: Variant - typeDef: TypeDef -} - -export interface MethodDef { - kind: 'method-def' - fn: FnDef - rel: InstanceRelation - paramUpcasts?: (Upcast | undefined)[] -} - -export interface VirtualIdentifierMatch { +export type VirtualIdentifierMatch = { vid: VirtualIdentifier module: Module - def: D + node: T } export const resolveVid = ( @@ -82,7 +40,7 @@ export const resolveVid = ( ): VirtualIdentifierMatch | undefined => { const module = ctx.moduleStack.at(-1)! const res = resolveVid_(vid, ctx, ofKind) - if (res && ['variant', 'fn-def', 'type-def', 'name-def'].includes(res.def.kind)) { + if (res && ['variant', 'fn-def', 'type-def', 'name-def'].includes(res.node.kind)) { module.imports.push(res) } return res @@ -110,7 +68,7 @@ const resolveVid_ = ( let ref: VirtualIdentifierMatch | undefined if (vidToString(vid) === selfType.name && instanceScope(ctx)) { - return { vid, module, def: { kind: 'self' } } + return { vid, module, node: { kind: 'self' } } } // walk through scopes inside out @@ -120,12 +78,12 @@ const resolveVid_ = ( // in case of top-level ref, qualify with module if (i === 0) { if (ctx.check) { - checkTopLevelDefinition(module, ref.def, ctx) + checkTopLevelDefinition(module, ref.node, ctx) } return { vid: concatVid(module.identifier, ref.vid), module, - def: ref.def + node: ref.node } } return ref @@ -136,12 +94,12 @@ const resolveVid_ = ( ref = resolveScopeVid(vid, module.topScope!, ctx, ofKind, module) if (ref) { if (ctx.check) { - checkTopLevelDefinition(module, ref.def, ctx) + checkTopLevelDefinition(module, ref.node, ctx) } return { vid: concatVid(module.identifier, ref.vid), module, - def: ref.def + node: ref.node } } @@ -176,7 +134,7 @@ export const resolveScopeVid = ( const name = vid.names[0] const def = scope.definitions.get(k + name) if (def) { - return { vid, module, def } + return { vid, module, node: def } } } if (vid.names.length === 2) { @@ -190,7 +148,7 @@ export const resolveScopeVid = ( // if matched, try to find variant with matching name const variant = typeDef.variants.find(v => v.name.value === variantName) if (variant) { - return { vid, module, def: { kind: 'variant', typeDef, variant } } + return { vid, module, node: { kind: 'variant', typeDef, variant } } } } } @@ -206,7 +164,7 @@ export const resolveScopeVid = ( const fn = def.block.statements.find(s => s.kind === 'fn-def' && s.name.value === fnName) if (fn && fn.kind === 'fn-def') { const rel = ctx.impls.find(i => i.instanceDef === def)! - return { vid, module, def: { kind: 'method-def', fn, rel: rel } } + return { vid, module, node: { kind: 'method-def', fn, rel: rel } } } } // if matched, try to find fn with matching name in type's inherent impl @@ -217,14 +175,14 @@ export const resolveScopeVid = ( s => s.kind === 'fn-def' && s.name.value === fnName ) if (fn && fn.kind === 'fn-def') { - return { vid, module, def: { kind: 'method-def', fn, rel: rel! } } + return { vid, module, node: { kind: 'method-def', fn, rel: rel! } } } } if (def && checkSuper) { // lookup supertypes' traits/impls that might contain that function const fullTypeVid = { names: [...(module.identifier.names ?? []), traitName] } const typeRef = resolveVid(fullTypeVid, ctx, ['type-def', 'trait-def']) - if (!typeRef || (typeRef.def.kind !== 'type-def' && typeRef.def.kind !== 'trait-def')) { + if (!typeRef || (typeRef.node.kind !== 'type-def' && typeRef.node.kind !== 'trait-def')) { return unreachable() } // TODO: only include traits that are in scope @@ -233,10 +191,10 @@ export const resolveScopeVid = ( const methodCandidates = superRels.flatMap(superRel => { const fullMethodVid = { names: [...superRel.implDef.vid.names, fnName] } const methodRef = resolveVid(fullMethodVid, ctx, ['method-def']) - if (methodRef && methodRef.def.kind === 'method-def') { + if (methodRef && methodRef.node.kind === 'method-def') { const module = resolveVid(methodRef.module.identifier, ctx, ['module']) - if (!module || module.def.kind !== 'module') return unreachable() - checkTopLevelDefinition(module.def, methodRef.def, ctx) + if (!module || module.node.kind !== 'module') return unreachable() + checkTopLevelDefinition(module.node, methodRef.node, ctx) return [>methodRef] } return [] @@ -245,7 +203,7 @@ export const resolveScopeVid = ( // unqualified trait name must be in scope const traitName = vidFromString(m.def.rel.implDef.vid.names.at(-1)!) const resolved = resolveVid(traitName, ctx, ['trait-def']) - return resolved && resolved.def === m.def.rel.instanceDef + return resolved && resolved.node === m.def.rel.instanceDef }) if (methodsInScope.length === 1) { return methodsInScope[0] @@ -268,7 +226,7 @@ export const resolveScopeVid = ( const fnVid: VirtualIdentifier = { names: [...boundVid.names, fnName] } const boundRef = resolveVid(fnVid, ctx, ['method-def']) if (boundRef) { - checkTopLevelDefinition(module, boundRef.def, ctx) + checkTopLevelDefinition(module, boundRef.node, ctx) return boundRef } } @@ -300,7 +258,7 @@ export const resolveMatchedVid = ( // if vid is module, e.g. std::option module = pkg.modules.find(m => vidEq(m.identifier, vid)) if (module) { - return { vid, module, def: module } + return { vid, module, node: module } } // if vid is varDef, typeDef, trait or impl, e.g. std::option::Option @@ -310,9 +268,9 @@ export const resolveMatchedVid = ( const ref = resolveScopeVid(moduleLocalVid, module.topScope!, ctx, ofKind, module) if (ref) { const defModule = resolveVid(ref.module.identifier, ctx, ['module']) - if (!defModule || defModule.def.kind !== 'module') return unreachable() - checkTopLevelDefinition(defModule.def, ref.def, ctx) - return { vid, module, def: ref.def } + if (!defModule || defModule.node.kind !== 'module') return unreachable() + checkTopLevelDefinition(defModule.node, ref.node, ctx) + return { vid, module, node: ref.node } } // check re-exports @@ -333,7 +291,7 @@ export const resolveMatchedVid = ( const moduleLocalVid = { names: vid.names.slice(-2) } const ref = resolveScopeVid(moduleLocalVid, module.topScope!, ctx, ofKind, module) if (ref) { - return { vid, module, def: ref.def } + return { vid, module, node: ref.node } } // check re-exports diff --git a/src/semantic/error.ts b/src/semantic/error.ts index 98f0265c..fb912760 100644 --- a/src/semantic/error.ts +++ b/src/semantic/error.ts @@ -8,16 +8,15 @@ import { Type } from '../ast/type' import { FieldDef } from '../ast/type-def' import { Context } from '../scope' import { vidToString } from '../scope/util' -import { MethodDef } from '../scope/vid' import { Source } from '../source' import { VirtualFnType, VirtualType, virtualTypeToString } from '../typecheck' import { assert, unreachable } from '../util/todo' import { MatchTree, unmatchedPaths } from './exhaust' -export interface SemanticError { +export type SemanticError = { code: number source: Source - node: AstNode + node: AstNode message: string notes: string[] } @@ -25,7 +24,7 @@ export interface SemanticError { export const semanticError = ( code: number, ctx: Context, - node: AstNode, + node: AstNode, message: string, notes: string[] = [] ): SemanticError => { @@ -35,16 +34,16 @@ export const semanticError = ( export const notFoundError = ( ctx: Context, - node: AstNode, + node: AstNode, id: string, kind: string = 'identifier', notes?: string[] ): SemanticError => semanticError(1, ctx, node, `${kind} \`${id}\` not found`, notes) -export const notImplementedError = (ctx: Context, node: AstNode, message?: string): SemanticError => +export const notImplementedError = (ctx: Context, node: AstNode, message?: string): SemanticError => semanticError(2, ctx, node, `not implemented:${message ? ` ${message}` : ''}`) -export const unknownTypeError = (ctx: Context, node: AstNode, type: VirtualType): SemanticError => { +export const unknownTypeError = (ctx: Context, node: AstNode, type: VirtualType): SemanticError => { if (type.kind === 'unknown-type') { if (type.mismatchedBranches) { return mismatchedBranchesError(ctx, node, type.mismatchedBranches.then, type.mismatchedBranches.else) @@ -56,12 +55,7 @@ export const unknownTypeError = (ctx: Context, node: AstNode, type: Virtual return semanticError(3, ctx, node, 'unknown type') } -export const typeError = ( - ctx: Context, - node: AstNode, - actual: VirtualType, - expected: VirtualType -): SemanticError => { +export const typeError = (ctx: Context, node: AstNode, actual: VirtualType, expected: VirtualType): SemanticError => { if (actual.kind === 'unknown-type' && actual.mismatchedBranches) { return mismatchedBranchesError(ctx, node, actual.mismatchedBranches.then, actual.mismatchedBranches.else) } @@ -73,7 +67,7 @@ type error: expected ${virtualTypeToString(expected)} export const mismatchedBranchesError = ( ctx: Context, - node: AstNode, + node: AstNode, thenType: VirtualType, elseType: VirtualType | undefined ): SemanticError => { @@ -89,7 +83,7 @@ if branches have incompatible types: /** * TODO: include clause ref for better error reporting */ -export const mismatchedClausesError = (ctx: Context, node: AstNode, types: VirtualType[]): SemanticError => { +export const mismatchedClausesError = (ctx: Context, node: AstNode, types: VirtualType[]): SemanticError => { const typesStr = types.map(t => ` ${virtualTypeToString(t)}`).join('\n') const message = `match clauses have incompatible types:\n${typesStr}` return semanticError(6, ctx, node, message) @@ -104,7 +98,7 @@ export const circularModuleError = (ctx: Context, module: Module): SemanticError export const duplicateError = ( ctx: Context, - node: AstNode, + node: AstNode, id: string, kind: string = 'identifier', notes?: string[] @@ -184,7 +178,7 @@ export const topLevelVarNotDefinedError = (ctx: Context, varDef: VarDef): Semant return semanticError(21, ctx, varDef, 'top level variable must be defined') } -export const notInFnScopeError = (ctx: Context, node: AstNode): SemanticError => { +export const notInFnScopeError = (ctx: Context, node: AstNode): SemanticError => { return semanticError(22, ctx, node, 'outside of the function scope') } @@ -192,7 +186,7 @@ export const notInLoopScopeError = (ctx: Context, statement: Statement): Semanti return semanticError(23, ctx, statement, 'outside of the loop') } -export const privateAccessError = (ctx: Context, node: AstNode, kind: string, name: string): SemanticError => { +export const privateAccessError = (ctx: Context, node: AstNode, kind: string, name: string): SemanticError => { return semanticError(24, ctx, node, `${kind} \`${name}\` is private`) } @@ -212,7 +206,7 @@ export const typeArgCountMismatchError = ( export const argCountMismatchError = ( ctx: Context, - node: AstNode, + node: AstNode, paramCount: number, argCount: number ): SemanticError => { @@ -262,7 +256,7 @@ export const invalidOperatorChainError = (ctx: Context, o1: BinaryOp, o2: Binary export const unexpectedTypeError = ( ctx: Context, - node: AstNode, + node: AstNode, expected: string, type: VirtualType ): SemanticError => { @@ -293,7 +287,7 @@ export const missingVarInitError = (ctx: Context, varDef: VarDef): SemanticError export const noImplFoundError = ( ctx: Context, - node: AstNode, + node: AstNode, methodDef: MethodDef, operandType: VirtualType ): SemanticError => { @@ -306,3 +300,8 @@ export const unexpectedRefutablePatternError = (ctx: Context, patternExpr: Patte const msg = `unexpected refutable pattern` return semanticError(41, ctx, patternExpr, msg) } + +export const duplicateDefError = (ctx: Context, def: AstNode): SemanticError => { + const msg = `duplicate definition` + return semanticError(42, ctx, def, msg) +} diff --git a/src/semantic/exhaust.ts b/src/semantic/exhaust.ts index 0d88e075..76cdf4ea 100644 --- a/src/semantic/exhaust.ts +++ b/src/semantic/exhaust.ts @@ -26,33 +26,34 @@ */ import { MatchExpr, PatternExpr } from '../ast/match' +import { Variant } from '../ast/type-def' import { Context, addError, addWarning } from '../scope' import { concatVid, idToVid, vidFromScope, vidFromString, vidToString } from '../scope/util' -import { VariantDef, VirtualIdentifierMatch, resolveVid } from '../scope/vid' +import { VirtualIdentifierMatch, resolveVid } from '../scope/vid' import { assert } from '../util/todo' import { nonExhaustiveMatchError, unreachableMatchClauseError } from './error' -export interface MatchTree { +export type MatchTree = { node: MatchNode } export type MatchNode = MatchType | MatchVariant | Exhaustive | Unmatched -export interface MatchType { +export type MatchType = { kind: 'type' - ref: VirtualIdentifierMatch + ref: VirtualIdentifierMatch variants: Map } -export interface MatchVariant { +export type MatchVariant = { kind: 'variant' fields: Map } -export interface Exhaustive { +export type Exhaustive = { kind: 'exhaustive' } -export interface Unmatched { +export type Unmatched = { kind: 'unmatched' } @@ -104,10 +105,10 @@ const matchPattern = (pattern: PatternExpr, tree: MatchTree, ctx: Context): bool const vid = idToVid(pattern.identifier) if (tree.node.kind !== 'type') { const ref = resolveVid(vid, ctx, ['variant']) - if (!ref || ref.def.kind !== 'variant') throw Error(`\`${vidToString(vid)}\` not found`) + if (!ref || ref.node.kind !== 'variant') throw Error(`\`${vidToString(vid)}\` not found`) const variants: Map = new Map( - ref.def.typeDef.variants.map(v => { + ref.node.typeDef.variants.map(v => { const variantVid = concatVid(vidFromScope(vid), vidFromString(v.name.value)) return [vidToString(variantVid), { node: { kind: 'unmatched' } }] }) diff --git a/src/semantic/expr.ts b/src/semantic/expr.ts index 0c8b8312..911aeecf 100644 --- a/src/semantic/expr.ts +++ b/src/semantic/expr.ts @@ -103,11 +103,11 @@ export const checkOperand = (operand: Operand, ctx: Context): void => { case 'string-literal': { const vid = vidFromString('std::string::String') const ref = resolveVid(vid, ctx, ['type-def']) - if (!ref || ref.def.kind !== 'type-def') { + if (!ref || ref.node.kind !== 'type-def') { addError(ctx, notFoundError(ctx, operand, vidToString(vid))) break } - operand.type = typeDefToVirtualType(ref.def, ctx, ref.module) + operand.type = typeDefToVirtualType(ref.node, ctx, ref.module) break } case 'string-interpolated': { @@ -127,41 +127,41 @@ export const checkOperand = (operand: Operand, ctx: Context): void => { case 'char-literal': { const vid = vidFromString('std::char::Char') const ref = resolveVid(vid, ctx, ['type-def']) - if (!ref || ref.def.kind !== 'type-def') { + if (!ref || ref.node.kind !== 'type-def') { addError(ctx, notFoundError(ctx, operand, vidToString(vid))) break } - operand.type = typeDefToVirtualType(ref.def, ctx, ref.module) + operand.type = typeDefToVirtualType(ref.node, ctx, ref.module) break } case 'int-literal': { const vid = vidFromString('std::int::Int') const ref = resolveVid(vid, ctx, ['type-def']) - if (!ref || ref.def.kind !== 'type-def') { + if (!ref || ref.node.kind !== 'type-def') { addError(ctx, notFoundError(ctx, operand, vidToString(vid))) break } - operand.type = typeDefToVirtualType(ref.def, ctx, ref.module) + operand.type = typeDefToVirtualType(ref.node, ctx, ref.module) break } case 'float-literal': { const vid = vidFromString('std::float::Float') const ref = resolveVid(vid, ctx, ['type-def']) - if (!ref || ref.def.kind !== 'type-def') { + if (!ref || ref.node.kind !== 'type-def') { addError(ctx, notFoundError(ctx, operand, vidToString(vid))) break } - operand.type = typeDefToVirtualType(ref.def, ctx, ref.module) + operand.type = typeDefToVirtualType(ref.node, ctx, ref.module) break } case 'bool-literal': { const vid = vidFromString('std::bool::Bool') const ref = resolveVid(vid, ctx, ['type-def']) - if (!ref || ref.def.kind !== 'type-def') { + if (!ref || ref.node.kind !== 'type-def') { addError(ctx, notFoundError(ctx, operand, vidToString(vid))) break } - operand.type = typeDefToVirtualType(ref.def, ctx, ref.module) + operand.type = typeDefToVirtualType(ref.node, ctx, ref.module) break } case 'identifier': @@ -207,7 +207,7 @@ export const checkBinaryExpr = (binaryExpr: BinaryExpr, ctx: Context): void => { const opImplFnVid = operatorImplMap.get(binaryExpr.binaryOp.kind) assert(!!opImplFnVid, `operator ${binaryExpr.binaryOp.kind} without impl function`) - const methodRef = resolveVid(opImplFnVid!, ctx, ['method-def'])?.def + const methodRef = resolveVid(opImplFnVid!, ctx, ['method-def'])?.node assert(!!methodRef, `impl fn \`${vidToString(opImplFnVid!)}\` not found`) assert(!!methodRef.fn.type, 'untyped impl fn') assert(methodRef.fn.type!.kind === 'fn-type', 'impl fn type in not fn') @@ -455,7 +455,7 @@ export const checkClosureExpr = (closureExpr: ClosureExpr, ctx: Context): void = export const checkResolvedClosureExpr = ( closureExpr: ClosureExpr, ctx: Context, - caller: AstNode, + caller: AstNode, inferredType: VirtualFnType ): VirtualType => { if (closureExpr.params.length > inferredType.paramTypes.length) { @@ -503,7 +503,7 @@ export const checkQualifiedMethodCall = ( impl = resolved ctx.moduleStack.at(-1)!.relImports.push(impl) } else { - if (operandTypeRef && operandTypeRef.def.kind !== 'trait-def' && operandTypeRef.def.kind !== 'generic') { + if (operandTypeRef && operandTypeRef.node.kind !== 'trait-def' && operandTypeRef.node.kind !== 'generic') { addError(ctx, noImplFoundError(ctx, identifier, ref.def, self)) } } @@ -658,8 +658,8 @@ export const checkCall_ = (call: CallOp, operand: Operand, args: Expr[], ctx: Co case 'identifier': // TODO: properly const ref = operand.type!.operand.ref - if (ref?.def.kind !== 'method-def') return unreachable() - call.methodDef = ref.def + if (ref?.node.kind !== 'method-def') return unreachable() + call.methodDef = ref.node operand.type = checkQualifiedMethodCall( operand.type.operand, >ref, @@ -796,7 +796,7 @@ export const variantCallRef = (operand: Operand, ctx: Context): VirtualIdentifie const vid = idToVid(operand) const ref = resolveVid(vid, ctx) - if (!ref || ref.def.kind !== 'variant') { + if (!ref || ref.node.kind !== 'variant') { return undefined } return >ref @@ -814,7 +814,7 @@ export const checkListExpr = (listExpr: ListExpr, ctx: Context): void => { } const listVid = vidFromString('std::list::List') const ref = resolveVid(listVid, ctx, ['type-def']) - if (!ref || ref.def.kind !== 'type-def') { + if (!ref || ref.node.kind !== 'type-def') { addError(ctx, notFoundError(ctx, listExpr, vidToString(listVid))) listExpr.type = unknownType return diff --git a/src/semantic/index.ts b/src/semantic/index.ts index 1a7f7093..748ddc8f 100644 --- a/src/semantic/index.ts +++ b/src/semantic/index.ts @@ -59,23 +59,23 @@ import { typeNames } from './type-def' import { Upcast, UpcastFn, makeUpcast, upcast } from './upcast' import { VirtualUseExpr, useExprToVids } from './use-expr' -export interface Checked { +export type Checked = { checked: boolean } -export interface TopLevelChecked { +export type TopLevelChecked = { topLevelChecked: boolean } -export interface Typed { +export type Typed = { type: VirtualType } -export interface Static { +export type Static = { impl: InstanceRelation } -export interface Virtual { +export type Virtual = { upcasts: Upcast[] upcastFn: UpcastFn } @@ -472,9 +472,9 @@ const checkImplDef = (implDef: ImplDef, ctx: Context) => { const ref = resolveVid(vid, ctx, ['type-def', 'trait-def']) if (ref) { if (module.compiled) { - } else if (ref.def.kind === 'trait-def') { + } else if (ref.node.kind === 'trait-def') { const traitRels = [ - ctx.impls.find(rel => rel.instanceDef === ref.def)!, + ctx.impls.find(rel => rel.instanceDef === ref.node)!, ...findSuperRelChains(ref.vid, ctx).flat() ] const traitMethods: MethodDef[] = traitRels.flatMap(t => { @@ -679,19 +679,19 @@ export const checkIdentifier = (identifier: Identifier, ctx: Context): void => { const ref = resolveVid(vid, ctx) if (ref) { identifier.ref = ref - switch (ref.def.kind) { + switch (ref.node.kind) { case 'self': identifier.type = instanceScope(ctx)?.rel?.forType ?? unknownType break case 'name-def': - const name = ref.def.name + const name = ref.node.name if ( - ref.def.parent && - ref.def.parent.kind === 'var-def' && - !ref.def.parent.pub && + ref.node.parent && + ref.node.parent.kind === 'var-def' && + !ref.node.parent.pub && ref.module !== ctx.moduleStack.at(-1)! ) { - addError(ctx, privateAccessError(ctx, identifier, 'variable', ref.def.name.value)) + addError(ctx, privateAccessError(ctx, identifier, 'variable', ref.node.name.value)) } if (name.type === selfType) { const instScope = instanceScope(ctx) @@ -706,23 +706,23 @@ export const checkIdentifier = (identifier: Identifier, ctx: Context): void => { break case 'method-def': if ( - !ref.def.fn.pub && - ref.def.rel.instanceDef.kind === 'impl-def' && + !ref.node.fn.pub && + ref.node.rel.instanceDef.kind === 'impl-def' && ref.module !== ctx.moduleStack.at(-1)! ) { - addError(ctx, privateAccessError(ctx, identifier, 'method', ref.def.fn.name.value)) + addError(ctx, privateAccessError(ctx, identifier, 'method', ref.node.fn.name.value)) } identifier.type = { kind: 'malleable-type', operand: identifier } break case 'variant': - identifier.type = ref.def.variant.type + identifier.type = ref.node.variant.type break case 'fn-def': - if (!ref.def.pub && ref.module !== ctx.moduleStack.at(-1)!) { - addError(ctx, privateAccessError(ctx, identifier, 'function', ref.def.name.value)) + if (!ref.node.pub && ref.module !== ctx.moduleStack.at(-1)!) { + addError(ctx, privateAccessError(ctx, identifier, 'function', ref.node.name.value)) } - identifier.type = ref.def.type + identifier.type = ref.node.type break case 'module': addError(ctx, vidResolveToModuleError(ctx, identifier, vidToString(vid))) @@ -753,14 +753,14 @@ export const checkType = (type: Type, ctx: Context) => { addError(ctx, notFoundError(ctx, type, vidToString(vid), 'type')) return } - const k = ref.def.kind + const k = ref.node.kind if (type.typeArgs.length > 0 && (k === 'generic' || k === 'self')) { addError(ctx, typeArgCountMismatchError(ctx, type, 0, type.typeArgs.length)) return } if (k === 'type-def' || k === 'trait-def') { type.typeArgs.forEach(tp => checkType(tp, ctx)) - const typeParams = ref.def.generics.filter(g => g.name.value !== selfType.name) + const typeParams = ref.node.generics.filter(g => g.name.value !== selfType.name) if (type.typeArgs.length !== typeParams.length) { addError(ctx, typeArgCountMismatchError(ctx, type, typeParams.length, type.typeArgs.length)) return @@ -788,7 +788,7 @@ export const resolveMalleableType = (operand: Operand, inferred: VirtualType, ct case 'identifier': // TODO: properly const ref = operand.type!.operand.ref - assert(ref?.def.kind === 'method-def') + assert(ref?.node.kind === 'method-def') operand.type = checkQualifiedMethodCall( operand.type!.operand, >ref, @@ -803,7 +803,7 @@ export const resolveMalleableType = (operand: Operand, inferred: VirtualType, ct } } -export const checkCallArgs = (node: AstNode, args: Operand[], paramTypes: VirtualType[], ctx: Context): void => { +export const checkCallArgs = (node: AstNode, args: Operand[], paramTypes: VirtualType[], ctx: Context): void => { if (args.length !== paramTypes.length) { addError(ctx, argCountMismatchError(ctx, node, paramTypes.length, args.length)) return diff --git a/src/semantic/instance.ts b/src/semantic/instance.ts index d3f8ce85..203e6510 100644 --- a/src/semantic/instance.ts +++ b/src/semantic/instance.ts @@ -36,11 +36,11 @@ export const checkFieldAccess = (operand: Operand, name: Name, ctx: Context): Vi } const typeVid = operand.type.identifier const typeRef = resolveVid(typeVid, ctx, ['type-def']) - if (!typeRef || typeRef.def.kind !== 'type-def') { + if (!typeRef || typeRef.node.kind !== 'type-def') { addError(ctx, notFoundError(ctx, operand, vidToString(typeVid), 'type')) return } - const typeDef = typeRef.def + const typeDef = typeRef.node const fieldName = name.value // check that every type variant has such field const matchedCount = typeDef.variants.filter(v => v.fieldDefs.find(f => f.name.value === fieldName)).length @@ -76,7 +76,7 @@ export const checkFieldAccess = (operand: Operand, name: Name, ctx: Context): Vi const conGenericMap = makeGenericMapOverStructure(operand.type, { kind: 'vid-type', identifier: typeRef.vid, - typeArgs: typeRef.def.generics.map(g => genericToVirtual(g, ctx)) + typeArgs: typeRef.node.generics.map(g => genericToVirtual(g, ctx)) }) return resolveType(fieldType, [conGenericMap], ctx) } @@ -114,22 +114,22 @@ export const checkMethodCall_ = ( methodVid: VirtualIdentifier, ctx: Context, typeArgs?: Type[], - node: AstNode = call + node: AstNode = call ): VirtualType | undefined => { const typeVid = vidFromScope(methodVid) const ref = resolveVid(methodVid, ctx, ['method-def']) - if (!ref || ref.def.kind !== 'method-def') { + if (!ref || ref.node.kind !== 'method-def') { addError(ctx, notFoundError(ctx, node ?? call, vidToString(methodVid), 'method')) return } - call.methodDef = ref.def - const fnType = ref.def.fn.type + call.methodDef = ref.node + const fnType = ref.node.fn.type const operandTypeRef = resolveVid(typeVid, ctx, typeKinds) - if (ref.def.rel.instanceDef.kind === 'trait-def' && !ref.def.fn.static && args.length > 0) { + if (ref.node.rel.instanceDef.kind === 'trait-def' && !ref.node.fn.static && args.length > 0) { const self = args[0] - const resolved = resolveMethodImpl(self.type!, ref.def, ctx) + const resolved = resolveMethodImpl(self.type!, ref.node, ctx) if (resolved) { call.impl = resolved @@ -137,20 +137,20 @@ export const checkMethodCall_ = ( // TODO: upcast only happen to the direct implType, but not its supertypes // Use case: std::range has to return Iter instead of RangeIter. When RangeIter is passed into a method // where MapIter is expected, upcast of RangeIter for MapIter does not upcast it to Iter - upcast(self, self.type!, ref.def.rel.implType, ctx) + upcast(self, self.type!, ref.node.rel.implType, ctx) } else { - if (operandTypeRef && operandTypeRef.def.kind !== 'trait-def' && operandTypeRef.def.kind !== 'generic') { - addError(ctx, noImplFoundError(ctx, call, ref.def, self.type!)) + if (operandTypeRef && operandTypeRef.node.kind !== 'trait-def' && operandTypeRef.node.kind !== 'generic') { + addError(ctx, noImplFoundError(ctx, call, ref.node, self.type!)) } } } else { - call.impl = ref.def.rel + call.impl = ref.node.rel ctx.moduleStack.at(-1)!.relImports.push(call.impl) } let genericMaps = makeMethodGenericMaps( args.map(a => a.type!), - ref.def, + ref.node, call, ctx, typeArgs @@ -163,13 +163,13 @@ export const checkMethodCall_ = ( // recalculate generic maps since malleable args might've been updated genericMaps = makeMethodGenericMaps( args.map(a => a.type!), - ref.def, + ref.node, call, ctx, typeArgs ) - const implForType = getInstanceForType(ref.def.rel.instanceDef, ctx) + const implForType = getInstanceForType(ref.node.rel.instanceDef, ctx) const implForGenericMap = selfType ? makeGenericMapOverStructure(selfType, implForType) : new Map() call.generics = fnType.generics.map((g, i) => { const typeArg = typeArgs?.at(i) diff --git a/src/semantic/match.ts b/src/semantic/match.ts index f55ab94f..dbe5732e 100644 --- a/src/semantic/match.ts +++ b/src/semantic/match.ts @@ -106,26 +106,26 @@ const checkConPattern = ( const conVid = idToVid(pattern.identifier) const ref = resolveVid(conVid, ctx, ['variant']) - if (!ref || ref.def.kind !== 'variant') { + if (!ref || ref.node.kind !== 'variant') { addError(ctx, notFoundError(ctx, pattern, vidToString(conVid), 'variant')) return [] } - if (ref.def.typeDef.name.value !== expectedType.identifier.names.at(-1)!) { + if (ref.node.typeDef.name.value !== expectedType.identifier.names.at(-1)!) { addError(ctx, nonDestructurableTypeError(ctx, pattern, expectedType)) return [] } - if (!refutable && ref.def.typeDef.variants.length > 1) { + if (!refutable && ref.node.typeDef.variants.length > 1) { addError(ctx, unexpectedRefutablePatternError(ctx, pattern)) } - const conType = ref.def.variant.type + const conType = ref.node.variant.type const conGenericMap = makeGenericMapOverStructure(expectedType, conType.returnType) pattern.type = resolveType(conType.returnType, [conGenericMap], ctx) for (const fp of pattern.fieldPatterns) { - const field = ref.def.variant.fieldDefs.find(fd => fd.name.value === fp.name.value) + const field = ref.node.variant.fieldDefs.find(fd => fd.name.value === fp.name.value) if (!field) { addError(ctx, notFoundError(ctx, fp, fp.name.value, 'field')) return [] diff --git a/src/semantic/upcast.ts b/src/semantic/upcast.ts index b5c31e5c..3e4d23ca 100644 --- a/src/semantic/upcast.ts +++ b/src/semantic/upcast.ts @@ -6,12 +6,12 @@ import { VirtualType } from '../typecheck' import { makeGenericMapOverStructure } from '../typecheck/generic' import { zip } from '../util/array' -export interface Upcast { +export type Upcast = { self: { [trait: string]: InstanceRelation } generics: Upcast[] } -export interface UpcastFn { +export type UpcastFn = { paramUpcasts: (Upcast | undefined)[] returnUpcast?: Upcast } diff --git a/src/semantic/use-expr.ts b/src/semantic/use-expr.ts index df1b3445..7d47299b 100644 --- a/src/semantic/use-expr.ts +++ b/src/semantic/use-expr.ts @@ -1,7 +1,7 @@ import { UseExpr } from '../ast/statement' import { VirtualIdentifier } from '../scope/vid' -export interface VirtualUseExpr { +export type VirtualUseExpr = { vid: VirtualIdentifier useExpr: UseExpr } diff --git a/src/source.ts b/src/source.ts index 116976b0..7580819d 100644 --- a/src/source.ts +++ b/src/source.ts @@ -1,4 +1,4 @@ -export interface Source { +export type Source = { code: string filepath: string } diff --git a/src/sourcemap/index.ts b/src/sourcemap/index.ts index c535d438..c6bcabe4 100644 --- a/src/sourcemap/index.ts +++ b/src/sourcemap/index.ts @@ -3,7 +3,7 @@ import { Span, indexToLocation } from '../location' import { getSpan } from '../parser' import { encode } from './base64vlq' -export interface SourceMap { +export type SourceMap = { version: number file: string sourceRoot: string diff --git a/src/typecheck/index.ts b/src/typecheck/index.ts index 0bb7b584..b61611ef 100644 --- a/src/typecheck/index.ts +++ b/src/typecheck/index.ts @@ -11,20 +11,20 @@ import { holeType, selfType, unknownType } from './type' export type VirtualType = VidType | VirtualFnType | VirtualGeneric | UnknownType | MalleableType | HoleType -export interface VidType { +export type VidType = { kind: 'vid-type' identifier: VirtualIdentifier typeArgs: VirtualType[] } -export interface VirtualFnType { +export type VirtualFnType = { kind: 'fn-type' generics: VirtualGeneric[] paramTypes: VirtualType[] returnType: VirtualType } -export interface UnknownType { +export type UnknownType = { kind: 'unknown-type' mismatchedBranches?: { then: VirtualType; else?: VirtualType } mismatchedMatchClauses?: VirtualType[] @@ -34,23 +34,23 @@ export interface UnknownType { * Type that is resolved to its first usage. * Closures are initially defined with this type */ -export interface MalleableType { +export type MalleableType = { kind: 'malleable-type' operand: Operand } -export interface HoleType { +export type HoleType = { kind: 'hole-type' } -export interface VirtualGeneric { +export type VirtualGeneric = { kind: 'generic' name: string key: string bounds: VirtualType[] } -export interface ConcreteGeneric { +export type ConcreteGeneric = { generic: VirtualGeneric impls: InstanceRelation[] } @@ -94,11 +94,11 @@ export const typeToVirtual = (type: Type, ctx: Context): VirtualType => { if (!ref) { return unknownType } - if (ref.def.kind === 'self') { + if (ref.node.kind === 'self') { return selfType - } else if (ref.def.kind === 'generic') { - return genericToVirtual(ref.def, ctx) - } else if (ref.def.kind === 'trait-def' || ref.def.kind === 'type-def') { + } else if (ref.node.kind === 'generic') { + return genericToVirtual(ref.node, ctx) + } else if (ref.node.kind === 'trait-def' || ref.node.kind === 'type-def') { return { kind: 'vid-type', identifier: ref.vid, @@ -108,7 +108,7 @@ export const typeToVirtual = (type: Type, ctx: Context): VirtualType => { .map(arg => typeToVirtual(arg, ctx)) } } else { - addError(ctx, expectedTypeError(ctx, type, ref.def.kind)) + addError(ctx, expectedTypeError(ctx, type, ref.node.kind)) return unknownType } case 'fn-type': diff --git a/tsconfig.json b/tsconfig.json index b7840e09..cde975b7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ "outDir": "./dist", "incremental": true, "lib": [ - "esnext.array" + "es2023.array" ], "skipLibCheck": true },