mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-14 16:35:03 +01:00
refactor typeck
This commit is contained in:
parent
5fefc46402
commit
bf9fbcc069
9 changed files with 756 additions and 783 deletions
2
.envrc
2
.envrc
|
|
@ -1 +1 @@
|
||||||
use flake
|
use nix
|
||||||
|
|
|
||||||
26
flake.lock
generated
26
flake.lock
generated
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1699091535,
|
|
||||||
"narHash": "sha256-cAwCoYXMO1FSiEhla/Bp98dMfPTNyLFk3MjT/UBWv+I=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "47ab3596a7163c69d642235da3d88f9fbe95df03",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
||||||
41
flake.nix
41
flake.nix
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
description = "Programming language written in TypeScript compiling to Wasm";
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs";
|
|
||||||
};
|
|
||||||
outputs = { self, nixpkgs }:
|
|
||||||
let
|
|
||||||
allSystems = [
|
|
||||||
"x86_64-linux" # 64-bit Intel/AMD Linux
|
|
||||||
"aarch64-linux" # 64-bit ARM Linux
|
|
||||||
"x86_64-darwin" # 64-bit Intel macOS
|
|
||||||
"aarch64-darwin" # 64-bit ARM macOS
|
|
||||||
];
|
|
||||||
|
|
||||||
# Helper to provide system-specific attributes
|
|
||||||
forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
});
|
|
||||||
in
|
|
||||||
{
|
|
||||||
devShells = forAllSystems ({ pkgs }: {
|
|
||||||
default = pkgs.mkShell {
|
|
||||||
buildInputs = with pkgs; [
|
|
||||||
rustup
|
|
||||||
];
|
|
||||||
packages = with pkgs; [
|
|
||||||
nodejs-18_x # Node.js 18, plus npm, npx, and corepack
|
|
||||||
yarn
|
|
||||||
wasmtime
|
|
||||||
wasm-tools
|
|
||||||
binaryen
|
|
||||||
];
|
|
||||||
|
|
||||||
shellHook = ''
|
|
||||||
export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin
|
|
||||||
export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
18
shell.nix
Normal file
18
shell.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
rustup
|
||||||
|
];
|
||||||
|
packages = with pkgs; [
|
||||||
|
nodejs-18_x # Node.js 18, plus npm, npx, and corepack
|
||||||
|
yarn
|
||||||
|
wasmtime
|
||||||
|
wasm-tools
|
||||||
|
binaryen
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
export PATH=$PATH:''${CARGO_HOME:-~/.cargo}/bin
|
||||||
|
export PATH=$PATH:''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-x86_64-unknown-linux-gnu/bin/
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|
@ -1,736 +1,43 @@
|
||||||
import {
|
import {
|
||||||
Crate,
|
|
||||||
BuiltinName,
|
BuiltinName,
|
||||||
COMPARISON_KINDS,
|
COMPARISON_KINDS,
|
||||||
mkDefaultFolder,
|
Crate,
|
||||||
EQUALITY_KINDS,
|
EQUALITY_KINDS,
|
||||||
Expr,
|
Expr,
|
||||||
ExprBinary,
|
ExprBinary,
|
||||||
|
ExprCall,
|
||||||
ExprUnary,
|
ExprUnary,
|
||||||
foldAst,
|
|
||||||
Folder,
|
Folder,
|
||||||
ItemId,
|
|
||||||
LOGICAL_KINDS,
|
LOGICAL_KINDS,
|
||||||
LoopId,
|
LoopId,
|
||||||
Resolution,
|
Resolution,
|
||||||
Resolved,
|
Resolved,
|
||||||
Ty,
|
StructLiteralField,
|
||||||
TY_BOOL,
|
TY_BOOL,
|
||||||
TY_I32,
|
TY_I32,
|
||||||
TY_INT,
|
TY_INT,
|
||||||
TY_NEVER,
|
TY_NEVER,
|
||||||
TY_STRING,
|
TY_STRING,
|
||||||
TY_UNIT,
|
TY_UNIT,
|
||||||
|
Ty,
|
||||||
TyFn,
|
TyFn,
|
||||||
tyIsUnit,
|
|
||||||
Type,
|
Type,
|
||||||
Typecked,
|
Typecked,
|
||||||
Item,
|
mkDefaultFolder,
|
||||||
StructLiteralField,
|
|
||||||
superFoldExpr,
|
superFoldExpr,
|
||||||
ExprCall,
|
} from "../ast";
|
||||||
substituteTy,
|
import { CompilerError, ErrorEmitted, Span, unreachable } from "../error";
|
||||||
} from "./ast";
|
import { printTy } from "../printer";
|
||||||
import { GlobalContext } from "./context";
|
import { InferContext } from "./infer";
|
||||||
import {
|
import { TypeckCtx, emitError, lowerAstTy, mkTyFn, tyError, tyErrorFrom, typeOfItem } from "./item";
|
||||||
CompilerError,
|
|
||||||
ErrorEmitted,
|
|
||||||
ErrorHandler,
|
|
||||||
Span,
|
|
||||||
unreachable,
|
|
||||||
} from "./error";
|
|
||||||
import { printTy } from "./printer";
|
|
||||||
import { ComplexMap } from "./utils";
|
|
||||||
|
|
||||||
type TypeckCtx = {
|
export function exprError(err: ErrorEmitted, span: Span): Expr<Typecked> {
|
||||||
gcx: GlobalContext;
|
|
||||||
/**
|
|
||||||
* A cache of all item types.
|
|
||||||
* Starts off as undefined, then gets set to null
|
|
||||||
* while computing the type (for cycle detection) and
|
|
||||||
* afterwards, we get the ty.
|
|
||||||
*/
|
|
||||||
itemTys: ComplexMap<ItemId, Ty | null>;
|
|
||||||
ast: Crate<Resolved>;
|
|
||||||
};
|
|
||||||
|
|
||||||
function mkTyFn(params: Ty[], returnTy: Ty): Ty {
|
|
||||||
return { kind: "fn", params, returnTy };
|
|
||||||
}
|
|
||||||
|
|
||||||
function tyError(cx: TypeckCtx, err: CompilerError): Ty {
|
|
||||||
return {
|
return {
|
||||||
kind: "error",
|
kind: "error",
|
||||||
err: emitError(cx, err),
|
err,
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function tyErrorFrom(prev: { err: ErrorEmitted }): Ty {
|
|
||||||
return { kind: "error", err: prev.err };
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitError(cx: TypeckCtx, err: CompilerError): ErrorEmitted {
|
|
||||||
return cx.gcx.error.emit(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
function builtinAsTy(cx: TypeckCtx, name: string, span: Span): Ty {
|
|
||||||
switch (name) {
|
|
||||||
case "String": {
|
|
||||||
return TY_STRING;
|
|
||||||
}
|
|
||||||
case "Int": {
|
|
||||||
return TY_INT;
|
|
||||||
}
|
|
||||||
case "I32": {
|
|
||||||
return TY_I32;
|
|
||||||
}
|
|
||||||
case "Bool": {
|
|
||||||
return TY_BOOL;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return tyError(cx, new CompilerError(`\`${name}\` is not a type`, span));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function typeOfBuiltinValue(fcx: FuncCtx, name: BuiltinName, span: Span): Ty {
|
|
||||||
switch (name) {
|
|
||||||
case "false":
|
|
||||||
case "true":
|
|
||||||
return TY_BOOL;
|
|
||||||
case "print":
|
|
||||||
return mkTyFn([TY_STRING], TY_UNIT);
|
|
||||||
case "trap":
|
|
||||||
return mkTyFn([], TY_NEVER);
|
|
||||||
case "__NULL":
|
|
||||||
return { kind: "rawptr", inner: fcx.infcx.newVar() };
|
|
||||||
case "__i32_store":
|
|
||||||
return mkTyFn([TY_I32, TY_I32], TY_UNIT);
|
|
||||||
case "__i64_store":
|
|
||||||
return mkTyFn([TY_I32, TY_INT], TY_UNIT);
|
|
||||||
case "__i32_load":
|
|
||||||
return mkTyFn([TY_I32], TY_I32);
|
|
||||||
case "__i64_load":
|
|
||||||
return mkTyFn([TY_I32], TY_INT);
|
|
||||||
case "__memory_size":
|
|
||||||
return mkTyFn([], TY_I32);
|
|
||||||
case "__memory_grow":
|
|
||||||
return mkTyFn([TY_I32], TY_I32);
|
|
||||||
case "__i32_extend_to_i64_u":
|
|
||||||
return mkTyFn([TY_I32], TY_INT);
|
|
||||||
default: {
|
|
||||||
return tyError(
|
|
||||||
fcx.cx,
|
|
||||||
new CompilerError(`\`${name}\` cannot be used as a value`, span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Cleanup, maybe get the ident switch into this function because typeOfItem is unused.
|
|
||||||
function lowerAstTy(cx: TypeckCtx, type: Type<Resolved>): Ty {
|
|
||||||
switch (type.kind) {
|
|
||||||
case "ident": {
|
|
||||||
const ident = type.value;
|
|
||||||
const res = ident.res;
|
|
||||||
|
|
||||||
const generics = type.generics.map((type) => lowerAstTy(cx, type));
|
|
||||||
let ty: Ty;
|
|
||||||
switch (res.kind) {
|
|
||||||
case "local": {
|
|
||||||
throw new Error("Item type cannot refer to local variable");
|
|
||||||
}
|
|
||||||
case "item": {
|
|
||||||
ty = typeOfItem(cx, res.id, generics, type.span);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "builtin": {
|
|
||||||
ty = builtinAsTy(cx, res.name, ident.span);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "tyParam": {
|
|
||||||
ty = { kind: "param", idx: res.index, name: res.name };
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "error": {
|
|
||||||
ty = tyErrorFrom(res);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ty.kind === "struct" || ty.kind === "alias") {
|
|
||||||
if (generics.length === ty.params.length) {
|
|
||||||
if (ty.kind === "alias") {
|
|
||||||
return substituteTy(ty.genericArgs, ty.actual);
|
|
||||||
}
|
|
||||||
return { ...ty, genericArgs: generics };
|
|
||||||
} else {
|
|
||||||
return tyError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
`expected ${ty.params.length} generic arguments, found ${generics.length}`,
|
|
||||||
type.span,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (ty.kind !== "error") {
|
|
||||||
if (generics.length > 0) {
|
|
||||||
return tyError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
`type ${printTy(ty)} does not take generic arguments`,
|
|
||||||
type.span,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ty;
|
|
||||||
}
|
|
||||||
case "tuple": {
|
|
||||||
return {
|
|
||||||
kind: "tuple",
|
|
||||||
elems: type.elems.map((type) => lowerAstTy(cx, type)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "rawptr": {
|
|
||||||
const inner = lowerAstTy(cx, type.inner);
|
|
||||||
if (inner.kind !== "struct") {
|
|
||||||
return tyError(
|
|
||||||
cx,
|
|
||||||
new CompilerError("raw pointers must point to structs", type.span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { kind: "rawptr", inner };
|
|
||||||
}
|
|
||||||
case "never": {
|
|
||||||
return TY_NEVER;
|
|
||||||
}
|
|
||||||
case "error": {
|
|
||||||
return tyErrorFrom(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function typeOfItem(
|
|
||||||
cx: TypeckCtx,
|
|
||||||
itemId: ItemId,
|
|
||||||
genericArgs: Ty[],
|
|
||||||
cause: Span,
|
|
||||||
): Ty {
|
|
||||||
if (itemId.crateId !== cx.ast.id) {
|
|
||||||
// Look up foreign items in the foreign crates, we don't need to lower those
|
|
||||||
// ourselves.
|
|
||||||
const item = cx.gcx.findItem(itemId);
|
|
||||||
|
|
||||||
switch (item.kind) {
|
|
||||||
case "function":
|
|
||||||
case "import":
|
|
||||||
case "type":
|
|
||||||
case "global":
|
|
||||||
return substituteTy(genericArgs, item.ty!);
|
|
||||||
case "mod": {
|
|
||||||
return tyError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
`module ${item.name} cannot be used as a type or value`,
|
|
||||||
cause,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "extern": {
|
|
||||||
return tyError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
`extern declaration ${item.name} cannot be used as a type or value`,
|
|
||||||
cause,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = cx.gcx.findItem(itemId, cx.ast);
|
|
||||||
const cachedTy = cx.itemTys.get(itemId);
|
|
||||||
if (cachedTy) {
|
|
||||||
return cachedTy;
|
|
||||||
}
|
|
||||||
if (cachedTy === null) {
|
|
||||||
return tyError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
`cycle computing type of #G${itemId.toString()}`,
|
|
||||||
item.span,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
cx.itemTys.set(itemId, null);
|
|
||||||
|
|
||||||
let ty: Ty;
|
|
||||||
|
|
||||||
switch (item.kind) {
|
|
||||||
case "function":
|
|
||||||
case "import": {
|
|
||||||
const args = item.params.map((arg) => lowerAstTy(cx, arg.type));
|
|
||||||
const returnTy: Ty = item.returnType
|
|
||||||
? lowerAstTy(cx, item.returnType)
|
|
||||||
: TY_UNIT;
|
|
||||||
|
|
||||||
ty = { kind: "fn", params: args, returnTy };
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "type": {
|
|
||||||
switch (item.type.kind) {
|
|
||||||
case "struct": {
|
|
||||||
ty = {
|
|
||||||
kind: "struct",
|
|
||||||
genericArgs: item.generics.map(
|
|
||||||
({ name }, idx): Ty => ({
|
|
||||||
kind: "param",
|
|
||||||
name,
|
|
||||||
idx,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
params: item.generics.map((ident) => ident.name),
|
|
||||||
itemId: item.id,
|
|
||||||
_name: item.name,
|
|
||||||
fields_no_subst: [
|
|
||||||
/*dummy*/
|
|
||||||
],
|
|
||||||
};
|
|
||||||
// Set it here already to allow for recursive types.
|
|
||||||
cx.itemTys.set(item.id, ty);
|
|
||||||
|
|
||||||
const fields = item.type.fields.map<[string, Ty]>(
|
|
||||||
({ name, type }) => [name.name, lowerAstTy(cx, type)],
|
|
||||||
);
|
|
||||||
|
|
||||||
ty.fields_no_subst = fields;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "alias": {
|
|
||||||
const actual = lowerAstTy(cx, item.type.type);
|
|
||||||
|
|
||||||
ty = {
|
|
||||||
kind: "alias",
|
|
||||||
actual,
|
|
||||||
genericArgs: item.generics.map(
|
|
||||||
({ name }, idx): Ty => ({
|
|
||||||
kind: "param",
|
|
||||||
name,
|
|
||||||
idx,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
params: item.generics.map((ident) => ident.name),
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "mod": {
|
|
||||||
return tyError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
`module ${item.name} cannot be used as a type or value`,
|
|
||||||
cause,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "extern": {
|
|
||||||
return tyError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
`extern declaration ${item.name} cannot be used as a type or value`,
|
|
||||||
cause,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "global": {
|
|
||||||
ty = lowerAstTy(cx, item.type);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "error": {
|
|
||||||
return tyErrorFrom(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ty = substituteTy(genericArgs, ty);
|
|
||||||
|
|
||||||
cx.itemTys.set(item.id, ty);
|
|
||||||
return ty;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function typeck(
|
|
||||||
gcx: GlobalContext,
|
|
||||||
ast: Crate<Resolved>,
|
|
||||||
): Crate<Typecked> {
|
|
||||||
const cx = {
|
|
||||||
gcx,
|
|
||||||
itemTys: new ComplexMap<ItemId, Ty | null>(),
|
|
||||||
ast,
|
|
||||||
};
|
|
||||||
|
|
||||||
const checker: Folder<Resolved, Typecked> = {
|
|
||||||
...mkDefaultFolder(),
|
|
||||||
itemInner(item: Item<Resolved>): Item<Typecked> {
|
|
||||||
switch (item.kind) {
|
|
||||||
case "function": {
|
|
||||||
// Functions do not have generic arguments right now.
|
|
||||||
const fnTy = typeOfItem(cx, item.id, [], item.span) as TyFn;
|
|
||||||
const body = checkBody(cx, ast, item.body, fnTy);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
name: item.name,
|
|
||||||
params: item.params.map((arg) => ({ ...arg })),
|
|
||||||
body,
|
|
||||||
ty: fnTy,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "import": {
|
|
||||||
const fnTy = typeOfItem(cx, item.id, [], item.span) as TyFn;
|
|
||||||
|
|
||||||
fnTy.params.forEach((param, i) => {
|
|
||||||
switch (param.kind) {
|
|
||||||
case "int":
|
|
||||||
case "i32":
|
|
||||||
break;
|
|
||||||
default: {
|
|
||||||
emitError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
`import parameters must be I32 or Int`,
|
|
||||||
item.params[i].span,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!tyIsUnit(fnTy.returnTy)) {
|
|
||||||
switch (fnTy.returnTy.kind) {
|
|
||||||
case "int":
|
|
||||||
case "i32":
|
|
||||||
break;
|
|
||||||
default: {
|
|
||||||
emitError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
`import return must be I32, Int or ()`,
|
|
||||||
item.returnType!.span,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
kind: "import",
|
|
||||||
params: item.params.map((arg) => ({ ...arg })),
|
|
||||||
ty: fnTy,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "type": {
|
|
||||||
const ty = typeOfItem(cx, item.id, [], item.span);
|
|
||||||
|
|
||||||
switch (item.type.kind) {
|
|
||||||
case "struct": {
|
|
||||||
const fieldNames = new Set();
|
|
||||||
item.type.fields.forEach(({ name }) => {
|
|
||||||
if (fieldNames.has(name)) {
|
|
||||||
emitError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
`type ${item.name} has a duplicate field: ${name.name}`,
|
|
||||||
name.span,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
fieldNames.add(name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
type: {
|
|
||||||
kind: "struct",
|
|
||||||
fields: item.type.fields.map((field) => ({ ...field })),
|
|
||||||
},
|
|
||||||
ty,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "alias": {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
type: { ...item.type },
|
|
||||||
ty,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "mod": {
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
contents: item.contents.map((item) => this.item(item)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "extern": {
|
|
||||||
// Nothing to check.
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
case "global": {
|
|
||||||
const ty = typeOfItem(cx, item.id, [], item.span);
|
|
||||||
const { init } = item;
|
|
||||||
|
|
||||||
let initChecked: Expr<Typecked>;
|
|
||||||
if (init.kind !== "literal" || init.value.kind !== "int") {
|
|
||||||
const err: ErrorEmitted = emitError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
"globals must be initialized with an integer literal",
|
|
||||||
init.span,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
initChecked = exprError(err, init.span);
|
|
||||||
} else {
|
|
||||||
const initTy = init.value.type === "I32" ? TY_I32 : TY_INT;
|
|
||||||
const infcx = new InferContext(cx.gcx.error);
|
|
||||||
infcx.assign(ty, initTy, init.span);
|
|
||||||
initChecked = { ...init, ty };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
ty,
|
|
||||||
init: initChecked,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "error": {
|
|
||||||
return { ...item };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
expr(_expr) {
|
|
||||||
throw new Error("expressions need to be handled in checkBody");
|
|
||||||
},
|
|
||||||
ident(ident) {
|
|
||||||
return ident;
|
|
||||||
},
|
|
||||||
type(_type) {
|
|
||||||
throw new Error("all types should be typechecked manually");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const typecked = foldAst(ast, checker);
|
|
||||||
|
|
||||||
const main = typecked.rootItems.find((item) => {
|
|
||||||
if (item.kind === "function" && item.name === "main") {
|
|
||||||
if (!tyIsUnit(item.ty!.returnTy)) {
|
|
||||||
emitError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
`\`main\` has an invalid signature. main takes no arguments and returns nothing`,
|
|
||||||
item.span,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ast.id === 0) {
|
|
||||||
// Only the final id=0 crate needs and cares about main.
|
|
||||||
if (!main) {
|
|
||||||
emitError(
|
|
||||||
cx,
|
|
||||||
new CompilerError(
|
|
||||||
`\`main\` function not found`,
|
|
||||||
Span.startOfFile(ast.rootFile),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
typecked.typeckResults = { main: undefined };
|
|
||||||
if (main) {
|
|
||||||
typecked.typeckResults.main = { kind: "item", id: main.id };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return typecked;
|
|
||||||
}
|
|
||||||
|
|
||||||
type TyVarRes =
|
|
||||||
| {
|
|
||||||
kind: "final";
|
|
||||||
ty: Ty;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
kind: "unified";
|
|
||||||
index: number;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
kind: "unknown";
|
|
||||||
};
|
|
||||||
|
|
||||||
export class InferContext {
|
|
||||||
tyVars: TyVarRes[] = [];
|
|
||||||
|
|
||||||
constructor(public error: ErrorHandler) {}
|
|
||||||
|
|
||||||
public newVar(): Ty {
|
|
||||||
const index = this.tyVars.length;
|
|
||||||
this.tyVars.push({ kind: "unknown" });
|
|
||||||
return { kind: "var", index };
|
|
||||||
}
|
|
||||||
|
|
||||||
private tryResolveVar(variable: number): Ty | undefined {
|
|
||||||
const varRes = this.tyVars[variable];
|
|
||||||
switch (varRes.kind) {
|
|
||||||
case "final": {
|
|
||||||
return varRes.ty;
|
|
||||||
}
|
|
||||||
case "unified": {
|
|
||||||
const ty = this.tryResolveVar(varRes.index);
|
|
||||||
if (ty) {
|
|
||||||
this.tyVars[variable] = { kind: "final", ty };
|
|
||||||
return ty;
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "unknown": {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private findRoot(variable: number): number {
|
|
||||||
let root = variable;
|
|
||||||
let nextVar;
|
|
||||||
while ((nextVar = this.tyVars[root]).kind === "unified") {
|
|
||||||
root = nextVar.index;
|
|
||||||
}
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to constrain a type variable to be of a specific type.
|
|
||||||
* INVARIANT: Both sides must not be of res "final", use resolveIfPossible
|
|
||||||
* before calling this.
|
|
||||||
*/
|
|
||||||
private constrainVar(variable: number, ty: Ty) {
|
|
||||||
const root = this.findRoot(variable);
|
|
||||||
|
|
||||||
if (ty.kind === "var") {
|
|
||||||
// Now we point our root to the other root to unify the two graphs.
|
|
||||||
const otherRoot = this.findRoot(ty.index);
|
|
||||||
|
|
||||||
// If both types have the same root, we don't need to do anything
|
|
||||||
// as they're already part of the same graph.
|
|
||||||
if (root != otherRoot) {
|
|
||||||
this.tyVars[root] = { kind: "unified", index: otherRoot };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.tyVars[root] = { kind: "final", ty };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public resolveIfPossible(ty: Ty): Ty {
|
|
||||||
// TODO: dont be shallow resolve
|
|
||||||
// note that fixing this will cause cycles. fix those cycles instead using
|
|
||||||
// the fancy occurs check as errs called it.
|
|
||||||
if (ty.kind === "var") {
|
|
||||||
return this.tryResolveVar(ty.index) ?? ty;
|
|
||||||
} else {
|
|
||||||
return ty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public assign(lhs_: Ty, rhs_: Ty, span: Span): void {
|
|
||||||
const lhs = this.resolveIfPossible(lhs_);
|
|
||||||
const rhs = this.resolveIfPossible(rhs_);
|
|
||||||
|
|
||||||
if (lhs.kind === "var") {
|
|
||||||
this.constrainVar(lhs.index, rhs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (rhs.kind === "var") {
|
|
||||||
this.constrainVar(rhs.index, lhs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lhs.kind === "error" || rhs.kind === "error") {
|
|
||||||
// This Is Fine 🐶🔥.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rhs.kind === "never") {
|
|
||||||
// not sure whether this is entirely correct wrt inference.. it will work out, probably.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (lhs.kind) {
|
|
||||||
case "string": {
|
|
||||||
if (rhs.kind === "string") return;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "int": {
|
|
||||||
if (rhs.kind === "int") return;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "i32": {
|
|
||||||
if (rhs.kind === "i32") return;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "bool": {
|
|
||||||
if (rhs.kind === "bool") return;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "tuple": {
|
|
||||||
if (rhs.kind === "tuple" && lhs.elems.length === rhs.elems.length) {
|
|
||||||
lhs.elems.forEach((lhs, i) => this.assign(lhs, rhs.elems[i], span));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "fn": {
|
|
||||||
if (rhs.kind === "fn" && lhs.params.length === rhs.params.length) {
|
|
||||||
// swapping because of contravariance in the future maybe
|
|
||||||
lhs.params.forEach((lhs, i) => this.assign(rhs.params[i], lhs, span));
|
|
||||||
|
|
||||||
this.assign(lhs.returnTy, rhs.returnTy, span);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "struct": {
|
|
||||||
if (rhs.kind === "struct" && lhs.itemId === rhs.itemId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "rawptr": {
|
|
||||||
if (rhs.kind === "rawptr") {
|
|
||||||
this.assign(lhs.inner, rhs.inner, span);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.error.emit(
|
|
||||||
new CompilerError(
|
|
||||||
`cannot assign ${printTy(rhs)} to ${printTy(lhs)}`,
|
|
||||||
span,
|
span,
|
||||||
),
|
ty: tyErrorFrom({ err }),
|
||||||
);
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FuncCtx = {
|
type FuncCtx = {
|
||||||
|
|
@ -764,13 +71,43 @@ function typeOfValue(fcx: FuncCtx, res: Resolution, span: Span): Ty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function exprError(err: ErrorEmitted, span: Span): Expr<Typecked> {
|
|
||||||
return {
|
export function typeOfBuiltinValue(
|
||||||
kind: "error",
|
fcx: FuncCtx,
|
||||||
err,
|
name: BuiltinName,
|
||||||
span,
|
span: Span,
|
||||||
ty: tyErrorFrom({ err }),
|
): Ty {
|
||||||
};
|
switch (name) {
|
||||||
|
case "false":
|
||||||
|
case "true":
|
||||||
|
return TY_BOOL;
|
||||||
|
case "print":
|
||||||
|
return mkTyFn([TY_STRING], TY_UNIT);
|
||||||
|
case "trap":
|
||||||
|
return mkTyFn([], TY_NEVER);
|
||||||
|
case "__NULL":
|
||||||
|
return { kind: "rawptr", inner: fcx.infcx.newVar() };
|
||||||
|
case "__i32_store":
|
||||||
|
return mkTyFn([TY_I32, TY_I32], TY_UNIT);
|
||||||
|
case "__i64_store":
|
||||||
|
return mkTyFn([TY_I32, TY_INT], TY_UNIT);
|
||||||
|
case "__i32_load":
|
||||||
|
return mkTyFn([TY_I32], TY_I32);
|
||||||
|
case "__i64_load":
|
||||||
|
return mkTyFn([TY_I32], TY_INT);
|
||||||
|
case "__memory_size":
|
||||||
|
return mkTyFn([], TY_I32);
|
||||||
|
case "__memory_grow":
|
||||||
|
return mkTyFn([TY_I32], TY_I32);
|
||||||
|
case "__i32_extend_to_i64_u":
|
||||||
|
return mkTyFn([TY_I32], TY_INT);
|
||||||
|
default: {
|
||||||
|
return tyError(
|
||||||
|
fcx.cx,
|
||||||
|
new CompilerError(`\`${name}\` cannot be used as a value`, span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkBody(
|
export function checkBody(
|
||||||
209
src/typeck/index.ts
Normal file
209
src/typeck/index.ts
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
import { Crate, Expr, Folder, Item, ItemId, Resolved, TY_I32, TY_INT, Ty, TyFn, Typecked, foldAst, mkDefaultFolder, tyIsUnit } from "../ast";
|
||||||
|
import { GlobalContext } from "../context";
|
||||||
|
import { CompilerError, ErrorEmitted, Span } from "../error";
|
||||||
|
import { ComplexMap } from "../utils";
|
||||||
|
import { checkBody, exprError } from "./expr";
|
||||||
|
import { InferContext } from "./infer";
|
||||||
|
import { emitError, typeOfItem } from "./item";
|
||||||
|
|
||||||
|
export function typeck(
|
||||||
|
gcx: GlobalContext,
|
||||||
|
ast: Crate<Resolved>,
|
||||||
|
): Crate<Typecked> {
|
||||||
|
const cx = {
|
||||||
|
gcx,
|
||||||
|
itemTys: new ComplexMap<ItemId, Ty | null>(),
|
||||||
|
ast,
|
||||||
|
};
|
||||||
|
|
||||||
|
const checker: Folder<Resolved, Typecked> = {
|
||||||
|
...mkDefaultFolder(),
|
||||||
|
itemInner(item: Item<Resolved>): Item<Typecked> {
|
||||||
|
switch (item.kind) {
|
||||||
|
case "function": {
|
||||||
|
// Functions do not have generic arguments right now.
|
||||||
|
const fnTy = typeOfItem(cx, item.id, [], item.span) as TyFn;
|
||||||
|
const body = checkBody(cx, ast, item.body, fnTy);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
name: item.name,
|
||||||
|
params: item.params.map((arg) => ({ ...arg })),
|
||||||
|
body,
|
||||||
|
ty: fnTy,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "import": {
|
||||||
|
const fnTy = typeOfItem(cx, item.id, [], item.span) as TyFn;
|
||||||
|
|
||||||
|
fnTy.params.forEach((param, i) => {
|
||||||
|
switch (param.kind) {
|
||||||
|
case "int":
|
||||||
|
case "i32":
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
emitError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
`import parameters must be I32 or Int`,
|
||||||
|
item.params[i].span,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tyIsUnit(fnTy.returnTy)) {
|
||||||
|
switch (fnTy.returnTy.kind) {
|
||||||
|
case "int":
|
||||||
|
case "i32":
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
emitError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
`import return must be I32, Int or ()`,
|
||||||
|
item.returnType!.span,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
kind: "import",
|
||||||
|
params: item.params.map((arg) => ({ ...arg })),
|
||||||
|
ty: fnTy,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "type": {
|
||||||
|
const ty = typeOfItem(cx, item.id, [], item.span);
|
||||||
|
|
||||||
|
switch (item.type.kind) {
|
||||||
|
case "struct": {
|
||||||
|
const fieldNames = new Set();
|
||||||
|
item.type.fields.forEach(({ name }) => {
|
||||||
|
if (fieldNames.has(name)) {
|
||||||
|
emitError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
`type ${item.name} has a duplicate field: ${name.name}`,
|
||||||
|
name.span,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
fieldNames.add(name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
type: {
|
||||||
|
kind: "struct",
|
||||||
|
fields: item.type.fields.map((field) => ({ ...field })),
|
||||||
|
},
|
||||||
|
ty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "alias": {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
type: { ...item.type },
|
||||||
|
ty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "mod": {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
contents: item.contents.map((item) => this.item(item)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "extern": {
|
||||||
|
// Nothing to check.
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
case "global": {
|
||||||
|
const ty = typeOfItem(cx, item.id, [], item.span);
|
||||||
|
const { init } = item;
|
||||||
|
|
||||||
|
let initChecked: Expr<Typecked>;
|
||||||
|
if (init.kind !== "literal" || init.value.kind !== "int") {
|
||||||
|
const err: ErrorEmitted = emitError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
"globals must be initialized with an integer literal",
|
||||||
|
init.span,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
initChecked = exprError(err, init.span);
|
||||||
|
} else {
|
||||||
|
const initTy = init.value.type === "I32" ? TY_I32 : TY_INT;
|
||||||
|
const infcx = new InferContext(cx.gcx.error);
|
||||||
|
infcx.assign(ty, initTy, init.span);
|
||||||
|
initChecked = { ...init, ty };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
ty,
|
||||||
|
init: initChecked,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "error": {
|
||||||
|
return { ...item };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expr(_expr) {
|
||||||
|
throw new Error("expressions need to be handled in checkBody");
|
||||||
|
},
|
||||||
|
ident(ident) {
|
||||||
|
return ident;
|
||||||
|
},
|
||||||
|
type(_type) {
|
||||||
|
throw new Error("all types should be typechecked manually");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const typecked = foldAst(ast, checker);
|
||||||
|
|
||||||
|
const main = typecked.rootItems.find((item) => {
|
||||||
|
if (item.kind === "function" && item.name === "main") {
|
||||||
|
if (!tyIsUnit(item.ty!.returnTy)) {
|
||||||
|
emitError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
`\`main\` has an invalid signature. main takes no arguments and returns nothing`,
|
||||||
|
item.span,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ast.id === 0) {
|
||||||
|
// Only the final id=0 crate needs and cares about main.
|
||||||
|
if (!main) {
|
||||||
|
emitError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
`\`main\` function not found`,
|
||||||
|
Span.startOfFile(ast.rootFile),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
typecked.typeckResults = { main: undefined };
|
||||||
|
if (main) {
|
||||||
|
typecked.typeckResults.main = { kind: "item", id: main.id };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return typecked;
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { TY_INT, TY_STRING, TY_UNIT } from "./ast";
|
import { TY_INT, TY_STRING, TY_UNIT } from "../ast";
|
||||||
import { Emitter, ErrorHandler, Span } from "./error";
|
import { Emitter, ErrorHandler, Span } from "../error";
|
||||||
import { InferContext } from "./typeck";
|
import { InferContext } from "./infer";
|
||||||
|
|
||||||
const SPAN: Span = Span.startOfFile({ content: "" });
|
const SPAN: Span = Span.startOfFile({ content: "" });
|
||||||
|
|
||||||
172
src/typeck/infer.ts
Normal file
172
src/typeck/infer.ts
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
import { Ty } from "../ast";
|
||||||
|
import { CompilerError, ErrorHandler, Span } from "../error";
|
||||||
|
import { printTy } from "../printer";
|
||||||
|
|
||||||
|
type TyVarRes =
|
||||||
|
| {
|
||||||
|
kind: "final";
|
||||||
|
ty: Ty;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
kind: "unified";
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
kind: "unknown";
|
||||||
|
};
|
||||||
|
|
||||||
|
export class InferContext {
|
||||||
|
tyVars: TyVarRes[] = [];
|
||||||
|
|
||||||
|
constructor(public error: ErrorHandler) {}
|
||||||
|
|
||||||
|
public newVar(): Ty {
|
||||||
|
const index = this.tyVars.length;
|
||||||
|
this.tyVars.push({ kind: "unknown" });
|
||||||
|
return { kind: "var", index };
|
||||||
|
}
|
||||||
|
|
||||||
|
private tryResolveVar(variable: number): Ty | undefined {
|
||||||
|
const varRes = this.tyVars[variable];
|
||||||
|
switch (varRes.kind) {
|
||||||
|
case "final": {
|
||||||
|
return varRes.ty;
|
||||||
|
}
|
||||||
|
case "unified": {
|
||||||
|
const ty = this.tryResolveVar(varRes.index);
|
||||||
|
if (ty) {
|
||||||
|
this.tyVars[variable] = { kind: "final", ty };
|
||||||
|
return ty;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "unknown": {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private findRoot(variable: number): number {
|
||||||
|
let root = variable;
|
||||||
|
let nextVar;
|
||||||
|
while ((nextVar = this.tyVars[root]).kind === "unified") {
|
||||||
|
root = nextVar.index;
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to constrain a type variable to be of a specific type.
|
||||||
|
* INVARIANT: Both sides must not be of res "final", use resolveIfPossible
|
||||||
|
* before calling this.
|
||||||
|
*/
|
||||||
|
private constrainVar(variable: number, ty: Ty) {
|
||||||
|
const root = this.findRoot(variable);
|
||||||
|
|
||||||
|
if (ty.kind === "var") {
|
||||||
|
// Now we point our root to the other root to unify the two graphs.
|
||||||
|
const otherRoot = this.findRoot(ty.index);
|
||||||
|
|
||||||
|
// If both types have the same root, we don't need to do anything
|
||||||
|
// as they're already part of the same graph.
|
||||||
|
if (root != otherRoot) {
|
||||||
|
this.tyVars[root] = { kind: "unified", index: otherRoot };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.tyVars[root] = { kind: "final", ty };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolveIfPossible(ty: Ty): Ty {
|
||||||
|
// TODO: dont be shallow resolve
|
||||||
|
// note that fixing this will cause cycles. fix those cycles instead using
|
||||||
|
// the fancy occurs check as errs called it.
|
||||||
|
if (ty.kind === "var") {
|
||||||
|
return this.tryResolveVar(ty.index) ?? ty;
|
||||||
|
} else {
|
||||||
|
return ty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public assign(lhs_: Ty, rhs_: Ty, span: Span): void {
|
||||||
|
const lhs = this.resolveIfPossible(lhs_);
|
||||||
|
const rhs = this.resolveIfPossible(rhs_);
|
||||||
|
|
||||||
|
if (lhs.kind === "var") {
|
||||||
|
this.constrainVar(lhs.index, rhs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rhs.kind === "var") {
|
||||||
|
this.constrainVar(rhs.index, lhs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lhs.kind === "error" || rhs.kind === "error") {
|
||||||
|
// This Is Fine 🐶🔥.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rhs.kind === "never") {
|
||||||
|
// not sure whether this is entirely correct wrt inference.. it will work out, probably.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (lhs.kind) {
|
||||||
|
case "string": {
|
||||||
|
if (rhs.kind === "string") return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "int": {
|
||||||
|
if (rhs.kind === "int") return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "i32": {
|
||||||
|
if (rhs.kind === "i32") return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "bool": {
|
||||||
|
if (rhs.kind === "bool") return;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "tuple": {
|
||||||
|
if (rhs.kind === "tuple" && lhs.elems.length === rhs.elems.length) {
|
||||||
|
lhs.elems.forEach((lhs, i) => this.assign(lhs, rhs.elems[i], span));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "fn": {
|
||||||
|
if (rhs.kind === "fn" && lhs.params.length === rhs.params.length) {
|
||||||
|
// swapping because of contravariance in the future maybe
|
||||||
|
lhs.params.forEach((lhs, i) => this.assign(rhs.params[i], lhs, span));
|
||||||
|
|
||||||
|
this.assign(lhs.returnTy, rhs.returnTy, span);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "struct": {
|
||||||
|
if (rhs.kind === "struct" && lhs.itemId === rhs.itemId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "rawptr": {
|
||||||
|
if (rhs.kind === "rawptr") {
|
||||||
|
this.assign(lhs.inner, rhs.inner, span);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.error.emit(
|
||||||
|
new CompilerError(
|
||||||
|
`cannot assign ${printTy(rhs)} to ${printTy(lhs)}`,
|
||||||
|
span,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
304
src/typeck/item.ts
Normal file
304
src/typeck/item.ts
Normal file
|
|
@ -0,0 +1,304 @@
|
||||||
|
import {
|
||||||
|
Crate,
|
||||||
|
ItemId,
|
||||||
|
Resolved,
|
||||||
|
Ty,
|
||||||
|
TY_BOOL,
|
||||||
|
TY_I32,
|
||||||
|
TY_INT,
|
||||||
|
TY_NEVER,
|
||||||
|
TY_STRING,
|
||||||
|
TY_UNIT,
|
||||||
|
Type,
|
||||||
|
substituteTy,
|
||||||
|
} from "../ast";
|
||||||
|
import { GlobalContext } from "../context";
|
||||||
|
import { CompilerError, ErrorEmitted, Span } from "../error";
|
||||||
|
import { printTy } from "../printer";
|
||||||
|
import { ComplexMap } from "../utils";
|
||||||
|
|
||||||
|
export type TypeckCtx = {
|
||||||
|
gcx: GlobalContext;
|
||||||
|
/**
|
||||||
|
* A cache of all item types.
|
||||||
|
* Starts off as undefined, then gets set to null
|
||||||
|
* while computing the type (for cycle detection) and
|
||||||
|
* afterwards, we get the ty.
|
||||||
|
*/
|
||||||
|
itemTys: ComplexMap<ItemId, Ty | null>;
|
||||||
|
ast: Crate<Resolved>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function mkTyFn(params: Ty[], returnTy: Ty): Ty {
|
||||||
|
return { kind: "fn", params, returnTy };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tyError(cx: TypeckCtx, err: CompilerError): Ty {
|
||||||
|
return {
|
||||||
|
kind: "error",
|
||||||
|
err: emitError(cx, err),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tyErrorFrom(prev: { err: ErrorEmitted }): Ty {
|
||||||
|
return { kind: "error", err: prev.err };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emitError(cx: TypeckCtx, err: CompilerError): ErrorEmitted {
|
||||||
|
return cx.gcx.error.emit(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
function builtinAsTy(cx: TypeckCtx, name: string, span: Span): Ty {
|
||||||
|
switch (name) {
|
||||||
|
case "String": {
|
||||||
|
return TY_STRING;
|
||||||
|
}
|
||||||
|
case "Int": {
|
||||||
|
return TY_INT;
|
||||||
|
}
|
||||||
|
case "I32": {
|
||||||
|
return TY_I32;
|
||||||
|
}
|
||||||
|
case "Bool": {
|
||||||
|
return TY_BOOL;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return tyError(cx, new CompilerError(`\`${name}\` is not a type`, span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Cleanup, maybe get the ident switch into this function because typeOfItem is unused.
|
||||||
|
export function lowerAstTy(cx: TypeckCtx, type: Type<Resolved>): Ty {
|
||||||
|
switch (type.kind) {
|
||||||
|
case "ident": {
|
||||||
|
const ident = type.value;
|
||||||
|
const res = ident.res;
|
||||||
|
|
||||||
|
const generics = type.generics.map((type) => lowerAstTy(cx, type));
|
||||||
|
let ty: Ty;
|
||||||
|
switch (res.kind) {
|
||||||
|
case "local": {
|
||||||
|
throw new Error("Item type cannot refer to local variable");
|
||||||
|
}
|
||||||
|
case "item": {
|
||||||
|
ty = typeOfItem(cx, res.id, generics, type.span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "builtin": {
|
||||||
|
ty = builtinAsTy(cx, res.name, ident.span);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "tyParam": {
|
||||||
|
ty = { kind: "param", idx: res.index, name: res.name };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "error": {
|
||||||
|
ty = tyErrorFrom(res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ty.kind === "struct" || ty.kind === "alias") {
|
||||||
|
if (generics.length === ty.params.length) {
|
||||||
|
if (ty.kind === "alias") {
|
||||||
|
return substituteTy(ty.genericArgs, ty.actual);
|
||||||
|
}
|
||||||
|
return { ...ty, genericArgs: generics };
|
||||||
|
} else {
|
||||||
|
return tyError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
`expected ${ty.params.length} generic arguments, found ${generics.length}`,
|
||||||
|
type.span,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (ty.kind !== "error") {
|
||||||
|
if (generics.length > 0) {
|
||||||
|
return tyError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
`type ${printTy(ty)} does not take generic arguments`,
|
||||||
|
type.span,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ty;
|
||||||
|
}
|
||||||
|
case "tuple": {
|
||||||
|
return {
|
||||||
|
kind: "tuple",
|
||||||
|
elems: type.elems.map((type) => lowerAstTy(cx, type)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case "rawptr": {
|
||||||
|
const inner = lowerAstTy(cx, type.inner);
|
||||||
|
if (inner.kind !== "struct") {
|
||||||
|
return tyError(
|
||||||
|
cx,
|
||||||
|
new CompilerError("raw pointers must point to structs", type.span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { kind: "rawptr", inner };
|
||||||
|
}
|
||||||
|
case "never": {
|
||||||
|
return TY_NEVER;
|
||||||
|
}
|
||||||
|
case "error": {
|
||||||
|
return tyErrorFrom(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function typeOfItem(
|
||||||
|
cx: TypeckCtx,
|
||||||
|
itemId: ItemId,
|
||||||
|
genericArgs: Ty[],
|
||||||
|
cause: Span,
|
||||||
|
): Ty {
|
||||||
|
if (itemId.crateId !== cx.ast.id) {
|
||||||
|
// Look up foreign items in the foreign crates, we don't need to lower those
|
||||||
|
// ourselves.
|
||||||
|
const item = cx.gcx.findItem(itemId);
|
||||||
|
|
||||||
|
switch (item.kind) {
|
||||||
|
case "function":
|
||||||
|
case "import":
|
||||||
|
case "type":
|
||||||
|
case "global":
|
||||||
|
return substituteTy(genericArgs, item.ty!);
|
||||||
|
case "mod": {
|
||||||
|
return tyError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
`module ${item.name} cannot be used as a type or value`,
|
||||||
|
cause,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "extern": {
|
||||||
|
return tyError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
`extern declaration ${item.name} cannot be used as a type or value`,
|
||||||
|
cause,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = cx.gcx.findItem(itemId, cx.ast);
|
||||||
|
const cachedTy = cx.itemTys.get(itemId);
|
||||||
|
if (cachedTy) {
|
||||||
|
return cachedTy;
|
||||||
|
}
|
||||||
|
if (cachedTy === null) {
|
||||||
|
return tyError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
`cycle computing type of #G${itemId.toString()}`,
|
||||||
|
item.span,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cx.itemTys.set(itemId, null);
|
||||||
|
|
||||||
|
let ty: Ty;
|
||||||
|
|
||||||
|
switch (item.kind) {
|
||||||
|
case "function":
|
||||||
|
case "import": {
|
||||||
|
const args = item.params.map((arg) => lowerAstTy(cx, arg.type));
|
||||||
|
const returnTy: Ty = item.returnType
|
||||||
|
? lowerAstTy(cx, item.returnType)
|
||||||
|
: TY_UNIT;
|
||||||
|
|
||||||
|
ty = { kind: "fn", params: args, returnTy };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "type": {
|
||||||
|
switch (item.type.kind) {
|
||||||
|
case "struct": {
|
||||||
|
ty = {
|
||||||
|
kind: "struct",
|
||||||
|
genericArgs: item.generics.map(
|
||||||
|
({ name }, idx): Ty => ({
|
||||||
|
kind: "param",
|
||||||
|
name,
|
||||||
|
idx,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
params: item.generics.map((ident) => ident.name),
|
||||||
|
itemId: item.id,
|
||||||
|
_name: item.name,
|
||||||
|
fields_no_subst: [
|
||||||
|
/*dummy*/
|
||||||
|
],
|
||||||
|
};
|
||||||
|
// Set it here already to allow for recursive types.
|
||||||
|
cx.itemTys.set(item.id, ty);
|
||||||
|
|
||||||
|
const fields = item.type.fields.map<[string, Ty]>(
|
||||||
|
({ name, type }) => [name.name, lowerAstTy(cx, type)],
|
||||||
|
);
|
||||||
|
|
||||||
|
ty.fields_no_subst = fields;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "alias": {
|
||||||
|
const actual = lowerAstTy(cx, item.type.type);
|
||||||
|
|
||||||
|
ty = {
|
||||||
|
kind: "alias",
|
||||||
|
actual,
|
||||||
|
genericArgs: item.generics.map(
|
||||||
|
({ name }, idx): Ty => ({
|
||||||
|
kind: "param",
|
||||||
|
name,
|
||||||
|
idx,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
params: item.generics.map((ident) => ident.name),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "mod": {
|
||||||
|
return tyError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
`module ${item.name} cannot be used as a type or value`,
|
||||||
|
cause,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "extern": {
|
||||||
|
return tyError(
|
||||||
|
cx,
|
||||||
|
new CompilerError(
|
||||||
|
`extern declaration ${item.name} cannot be used as a type or value`,
|
||||||
|
cause,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "global": {
|
||||||
|
ty = lowerAstTy(cx, item.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "error": {
|
||||||
|
return tyErrorFrom(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ty = substituteTy(genericArgs, ty);
|
||||||
|
|
||||||
|
cx.itemTys.set(item.id, ty);
|
||||||
|
return ty;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue