feat(infer): add algorithm w type inference
This commit is contained in:
1 parent
b6937f1b30
commit
e3ff306205
1 file changed
+196
@@ -0,0 +1,196 @@
|
||||
module Fml.Core.Infer
|
||||
|
||||
open Fml.Core.Ast
|
||||
open Fml.Core.Types
|
||||
open Fml.Core.Decls
|
||||
open Fml.Core.Match
|
||||
|
||||
type TypeEnv = Map<string, Ty>
|
||||
|
||||
let private ctorFieldType (name: string) : Ty list * Ty =
|
||||
match lookupCtor name with
|
||||
| Some info -> instantiateCtor info
|
||||
| None -> raise (TypeError(sprintf "unknown constructor '%s'" name))
|
||||
|
||||
let private recordTypeOfField (fieldName: string) : string =
|
||||
match lookupField fieldName with
|
||||
| Some info -> info.TypeName
|
||||
| None -> raise (TypeError(sprintf "unknown record field '%s'" fieldName))
|
||||
|
||||
let rec inferPattern (pat: Pattern) : Ty * (string * Ty) list =
|
||||
match pat with
|
||||
| PWildcard -> newVar (), []
|
||||
| PVar x ->
|
||||
let v = newVar ()
|
||||
v, [ (x, v) ]
|
||||
| PInt _ -> TInt, []
|
||||
| PBool _ -> TBool, []
|
||||
| PString _ -> TString, []
|
||||
| PUnit -> TUnit, []
|
||||
| PTuple parts ->
|
||||
let inferred = parts |> List.map inferPattern
|
||||
TTuple(List.map fst inferred), List.collect snd inferred
|
||||
| PNil -> TList(newVar ()), []
|
||||
| PCons(headPat, tailPat) ->
|
||||
let headTy, headBinds = inferPattern headPat
|
||||
let tailTy, tailBinds = inferPattern tailPat
|
||||
unify tailTy (TList headTy)
|
||||
TList headTy, headBinds @ tailBinds
|
||||
| PConstruct(name, argPats) ->
|
||||
let argTys, resultTy = ctorFieldType name
|
||||
if List.length argTys <> List.length argPats then
|
||||
raise (TypeError(sprintf "constructor '%s' expects %d argument(s)" name (List.length argTys)))
|
||||
let inferred = argPats |> List.map inferPattern
|
||||
List.iter2 (fun expected (actual, _) -> unify expected actual) argTys inferred
|
||||
resultTy, List.collect snd inferred
|
||||
| PRecord fields ->
|
||||
match fields with
|
||||
| [] -> raise (TypeError "record pattern must mention at least one field")
|
||||
| (firstName, _) :: _ ->
|
||||
let typeName = recordTypeOfField firstName
|
||||
let fieldTys, recordTy = instantiateRecordType typeName
|
||||
let binds =
|
||||
fields
|
||||
|> List.collect (fun (fieldName, fieldPat) ->
|
||||
match Map.tryFind fieldName fieldTys with
|
||||
| Some expected ->
|
||||
let actual, subBinds = inferPattern fieldPat
|
||||
unify expected actual
|
||||
subBinds
|
||||
| None -> raise (TypeError(sprintf "record type '%s' has no field '%s'" typeName fieldName)))
|
||||
recordTy, binds
|
||||
|
||||
let rec infer (env: TypeEnv) (expr: Expr) : Ty =
|
||||
match expr with
|
||||
| IntLit _ -> TInt
|
||||
| BoolLit _ -> TBool
|
||||
| StringLit _ -> TString
|
||||
| UnitLit -> TUnit
|
||||
| Var x ->
|
||||
match Map.tryFind x env with
|
||||
| Some scheme -> instantiate scheme
|
||||
| None -> raise (TypeError(sprintf "unbound variable '%s'" x))
|
||||
| Lambda(x, body) ->
|
||||
let paramTy = newVar ()
|
||||
let bodyTy = infer (Map.add x paramTy env) body
|
||||
TArrow(paramTy, bodyTy)
|
||||
| App(fn, arg) ->
|
||||
let fnTy = infer env fn
|
||||
let argTy = infer env arg
|
||||
let resultTy = newVar ()
|
||||
unify fnTy (TArrow(argTy, resultTy))
|
||||
resultTy
|
||||
| Let(false, name, rhs, body) ->
|
||||
enterLevel ()
|
||||
let rhsTy = infer env rhs
|
||||
leaveLevel ()
|
||||
generalize rhsTy
|
||||
infer (Map.add name rhsTy env) body
|
||||
| Let(true, name, rhs, body) ->
|
||||
enterLevel ()
|
||||
let nameTy = newVar ()
|
||||
let rhsTy = infer (Map.add name nameTy env) rhs
|
||||
unify nameTy rhsTy
|
||||
leaveLevel ()
|
||||
generalize nameTy
|
||||
infer (Map.add name nameTy env) body
|
||||
| LetGroup(isRec, bindings, body) ->
|
||||
let groupEnv = inferLetGroup env isRec bindings
|
||||
infer groupEnv body
|
||||
| If(cond, thenBranch, elseBranch) ->
|
||||
unify (infer env cond) TBool
|
||||
let thenTy = infer env thenBranch
|
||||
let elseTy = infer env elseBranch
|
||||
unify thenTy elseTy
|
||||
thenTy
|
||||
| Neg inner ->
|
||||
unify (infer env inner) TInt
|
||||
TInt
|
||||
| BinOp(op, l, r) ->
|
||||
let lt = infer env l
|
||||
let rt = infer env r
|
||||
match op with
|
||||
| "+" | "-" | "*" | "/" | "mod" ->
|
||||
unify lt TInt
|
||||
unify rt TInt
|
||||
TInt
|
||||
| "<" | "<=" | ">" | ">=" ->
|
||||
unify lt TInt
|
||||
unify rt TInt
|
||||
TBool
|
||||
| "=" | "<>" ->
|
||||
unify lt rt
|
||||
TBool
|
||||
| "&&" | "||" ->
|
||||
unify lt TBool
|
||||
unify rt TBool
|
||||
TBool
|
||||
| "^" ->
|
||||
unify lt TString
|
||||
unify rt TString
|
||||
TString
|
||||
| _ -> raise (TypeError(sprintf "unknown operator %s" op))
|
||||
| Tuple items -> TTuple(List.map (infer env) items)
|
||||
| Nil -> TList(newVar ())
|
||||
| Cons(headExpr, tailExpr) ->
|
||||
let headTy = infer env headExpr
|
||||
let tailTy = infer env tailExpr
|
||||
unify tailTy (TList headTy)
|
||||
TList headTy
|
||||
| Construct(name, args) ->
|
||||
let argTys, resultTy = ctorFieldType name
|
||||
if List.length argTys <> List.length args then
|
||||
raise (TypeError(sprintf "constructor '%s' expects %d argument(s)" name (List.length argTys)))
|
||||
List.iter2 (fun expected actual -> unify expected (infer env actual)) argTys args
|
||||
resultTy
|
||||
| Record fields ->
|
||||
match fields with
|
||||
| [] -> raise (TypeError "record expression must have at least one field")
|
||||
| (firstName, _) :: _ ->
|
||||
let typeName = recordTypeOfField firstName
|
||||
let fieldTys, recordTy = instantiateRecordType typeName
|
||||
let provided = fields |> List.map fst |> Set.ofList
|
||||
let declared = fieldTys |> Map.toList |> List.map fst |> Set.ofList
|
||||
if provided <> declared then
|
||||
raise (TypeError(sprintf "record of type '%s' must set exactly its fields" typeName))
|
||||
for (fieldName, fieldExpr) in fields do
|
||||
unify fieldTys.[fieldName] (infer env fieldExpr)
|
||||
recordTy
|
||||
| Field(record, fieldName) ->
|
||||
let fieldTy, recordTy =
|
||||
match lookupField fieldName with
|
||||
| Some info -> instantiateField info
|
||||
| None -> raise (TypeError(sprintf "unknown record field '%s'" fieldName))
|
||||
unify (infer env record) recordTy
|
||||
fieldTy
|
||||
| Match(scrut, cases) ->
|
||||
let scrutTy = infer env scrut
|
||||
let resultTy = newVar ()
|
||||
for (pat, body) in cases do
|
||||
let patTy, binds = inferPattern pat
|
||||
unify scrutTy patTy
|
||||
let branchEnv = List.fold (fun e (n, ty) -> Map.add n ty e) env binds
|
||||
unify resultTy (infer branchEnv body)
|
||||
checkMatch (cases |> List.map fst)
|
||||
resultTy
|
||||
|
||||
and inferLetGroup (env: TypeEnv) (isRec: bool) (bindings: (string * Expr) list) : TypeEnv =
|
||||
if isRec then
|
||||
enterLevel ()
|
||||
let placeholders = bindings |> List.map (fun (name, _) -> name, newVar ())
|
||||
let recEnv = List.fold (fun e (n, ty) -> Map.add n ty e) env placeholders
|
||||
for ((_, rhs), (_, nameTy)) in List.zip bindings placeholders do
|
||||
unify nameTy (infer recEnv rhs)
|
||||
leaveLevel ()
|
||||
placeholders |> List.iter (fun (_, ty) -> generalize ty)
|
||||
List.fold (fun e (n, ty) -> Map.add n ty e) env placeholders
|
||||
else
|
||||
let typed =
|
||||
bindings
|
||||
|> List.map (fun (name, rhs) ->
|
||||
enterLevel ()
|
||||
let ty = infer env rhs
|
||||
leaveLevel ()
|
||||
generalize ty
|
||||
name, ty)
|
||||
List.fold (fun e (n, ty) -> Map.add n ty e) env typed
|
||||
Reference in new issue
Block a user