diff --git a/Directory.Build.props b/Directory.Build.props index b607c4e9..af6b5b89 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,10 +2,10 @@ $(OtherFlags) --warnon:3559 --warnon:3560 --test:GraphBasedChecking --test:ParallelOptimization --test:ParallelIlxGen - - - + + + + + + diff --git a/src/Escalier.Data/Library.fs b/src/Escalier.Data/Library.fs index 3f1d5919..98b8fd20 100644 --- a/src/Escalier.Data/Library.fs +++ b/src/Escalier.Data/Library.fs @@ -108,8 +108,8 @@ module Syntax = | Null -> "null" | Undefined -> "undefined" - // TODO: add SpreadPat? type ObjPatElem = + // TODO: add isMut | KeyValuePat of span: Span * key: string * @@ -120,6 +120,7 @@ module Syntax = name: string * init: option * isMut: bool + // TODO: rename to RestSpreadPat | RestPat of span: Span * target: Pattern * isMut: bool type BindingIdent = @@ -246,8 +247,8 @@ module Type = Scheme: option } type ObjPatElem = - | KeyValuePat of key: string * value: Pattern - | ShorthandPat of name: string * value: option + | KeyValuePat of key: string * value: Pattern * init: option + | ShorthandPat of name: string * init: option | RestPat of target: Pattern type Pattern = @@ -262,7 +263,33 @@ module Type = override this.ToString() = match this with | Identifier name -> name - | _ -> failwith "TODO: Pattern.ToString()" + | Object elems -> + let elems = + List.map + (fun elem -> + match elem with + | KeyValuePat(key, value, init) -> + match init with + | Some(init) -> $"{key}: {value} = {init}" + | None -> $"{key}: {value}" + | ShorthandPat(name, init) -> + match init with + | Some(value) -> $"{name} = {value}" + | None -> name + | RestPat(target) -> $"...{target}") + elems + + let elems = String.concat ", " elems + $"{{{elems}}}" + | Tuple elems -> + let elems = + List.map (fun item -> item.ToString()) elems |> String.concat ", " + + $"[{elems}]" + | Wildcard -> "_" + | Literal lit -> lit.ToString() + | Is({ Name = name }, id) -> $"{name} is {id}" + | Rest(target) -> $"...{target}" type FuncParam = { Pattern: Pattern @@ -489,7 +516,10 @@ module Type = let elems = String.concat ", " elems $"{{{elems}}}" - | _ -> failwith "TODO: finish implementing Type.ToString" + | Rest t -> $"...{t}" + | _ -> + printfn "this.Kind = %A" this.Kind + failwith "TODO: finish implementing Type.ToString" type Scheme = { TypeParams: option> diff --git a/src/Escalier.TypeChecker.Tests/Tests.fs b/src/Escalier.TypeChecker.Tests/Tests.fs index fbf66737..8c4a54b7 100644 --- a/src/Escalier.TypeChecker.Tests/Tests.fs +++ b/src/Escalier.TypeChecker.Tests/Tests.fs @@ -474,14 +474,19 @@ let InferObjectRestSpread () = result { let src = """ - let obj = {a: 5, b: "hello", c: true} - let {a, ...rest} = obj + let obj1 = {a: 5, b: "hello", c: true} + let {a, ...rest} = obj1 + let obj2 = {a, ...rest} + let foo = fn({a, ...rest}: {a: number, b: string, c: boolean}) => a + foo(obj2) """ let! env = inferScript src Assert.Value(env, "a", "5") Assert.Value(env, "rest", "{b: \"hello\", c: true}") + Assert.Value(env, "obj2", "{a: 5} & {b: \"hello\", c: true}") } + printfn "result = %A" result Assert.False(Result.isError result) diff --git a/src/Escalier.TypeChecker/Library.fs b/src/Escalier.TypeChecker/Library.fs index 06fc7ee9..8bcd6877 100644 --- a/src/Escalier.TypeChecker/Library.fs +++ b/src/Escalier.TypeChecker/Library.fs @@ -137,7 +137,17 @@ module rec TypeChecker = Provenance = None } | Literal _ -> t | Wildcard -> t - | Object _ -> failwith "TODO: foldType - Object" + | Object elems -> + let elems = + List.map + (fun elem -> + match elem with + | Property p -> Property { p with Type = fold p.Type } + | _ -> failwith "TODO: foldType - ObjTypeElem") + elems + + { Kind = Object(elems) + Provenance = None } | Rest t -> { Kind = Rest(fold t) Provenance = None } @@ -371,6 +381,8 @@ module rec TypeChecker = ///Unify the two types t1 and t2. Makes the types t1 and t2 the same. let unify (env: Env) (t1: Type) (t2: Type) : Result = + // printfn $"unify {t1} {t2}" + result { match (prune t1).Kind, (prune t2).Kind with | TypeVar _, _ -> do! bind t1 t2 @@ -465,8 +477,6 @@ module rec TypeChecker = { Kind = TypeKind.Object(combinedElems) Provenance = None } - printfn "restTypes = %A" restTypes - match restTypes with | [] -> do! unify env t1 objType | [ restType ] -> @@ -494,6 +504,47 @@ module rec TypeChecker = do! unify env newRestType restType | _ -> return! Error(TypeError.TypeMismatch(t1, t2)) + | Intersection types, Object allElems -> + let mutable combinedElems = [] + let mutable restTypes = [] + + for t in types do + match t.Kind with + | Object elems -> combinedElems <- combinedElems @ elems + | Rest t -> restTypes <- t :: restTypes + | _ -> return! Error(TypeError.TypeMismatch(t1, t2)) + + let objType = + { Kind = TypeKind.Object(combinedElems) + Provenance = None } + + match restTypes with + | [] -> do! unify env objType t2 + | [ restType ] -> + let objElems, restElems = + List.partition + (fun (ae: ObjTypeElem) -> + List.exists + (fun ce -> + match ae, ce with + | Property ap, Property cp -> ap.Name = cp.Name + | _ -> false) + combinedElems) + allElems + + let newObjType = + { Kind = TypeKind.Object(objElems) + Provenance = None } + + do! unify env objType newObjType + + let newRestType = + { Kind = TypeKind.Object(restElems) + Provenance = None } + + do! unify env restType newRestType + | _ -> return! Error(TypeError.TypeMismatch(t1, t2)) + | _, _ -> let t1' = expandType env t1 @@ -775,6 +826,8 @@ module rec TypeChecker = { Kind = makePrimitiveKind "undefined" Provenance = None } | ExprKind.Object elems -> + let mutable spreadTypes = [] + let! elems = List.traverseResultM (fun (elem: ObjElem) -> @@ -784,32 +837,44 @@ module rec TypeChecker = let! t = inferExpr value env nonGeneric return - Property - { Name = key - Optional = false - Readonly = false - Type = t } + Some( + Property + { Name = key + Optional = false + Readonly = false + Type = t } + ) | ObjElem.Shorthand(_span, key) -> let value = getType key env nonGeneric return - Property - { Name = key - Optional = false - Readonly = false - Type = value } - | ObjElem.Spread(span, value) -> - return! - Error( - TypeError.NotImplemented - "TODO: inferExpr - ObjElem.Spread" + Some( + Property + { Name = key + Optional = false + Readonly = false + Type = value } ) + | ObjElem.Spread(span, value) -> + let! t = inferExpr value env nonGeneric + spreadTypes <- t :: spreadTypes + return None }) elems - return + let elems = elems |> List.choose id + + let objType = { Kind = Object(elems) Provenance = None } + + match spreadTypes with + | [] -> return objType + | _ -> + return + { Kind = Intersection([ objType ] @ spreadTypes) + Provenance = None } + | _ -> return! Error( @@ -1176,11 +1241,10 @@ module rec TypeChecker = (fun (elem: Syntax.ObjPatElem) -> match elem with | Syntax.ObjPatElem.KeyValuePat(span, key, value, init) -> - // TODO: init - ObjPatElem.KeyValuePat(key, patternToPattern value) + ObjPatElem.KeyValuePat(key, patternToPattern value, init) | Syntax.ObjPatElem.ShorthandPat(span, name, init, isMut) -> - // TODO: init, isMut - ObjPatElem.ShorthandPat(name, None) + // TODO: isMut + ObjPatElem.ShorthandPat(name, init) | Syntax.ObjPatElem.RestPat(span, target, isMut) -> ObjPatElem.RestPat(patternToPattern target)) elems