diff --git a/src/Escalier.Data/Library.fs b/src/Escalier.Data/Library.fs index 98b8fd20..2b700054 100644 --- a/src/Escalier.Data/Library.fs +++ b/src/Escalier.Data/Library.fs @@ -167,6 +167,12 @@ module Syntax = type Stmt = { Kind: StmtKind; Span: Span } + type Property = + { Name: string + TypeAnn: TypeAnn + Optional: bool + Readonly: bool } + // TODO: add location information type ObjTypeAnnElem = | Callable of Function @@ -174,7 +180,7 @@ module Syntax = | Method of name: string * is_mut: bool * type_: Function | Getter of name: string * return_type: TypeAnn * throws: TypeAnn | Setter of name: string * param: FuncParam * throws: TypeAnn - | Property of name: string * typeAnn: TypeAnn // TODO: readonly, optional + | Property of Property type KeywordTypeAnn = | Boolean diff --git a/src/Escalier.Parser.Tests/Tests.fs b/src/Escalier.Parser.Tests/Tests.fs index 38927df6..c9e3b072 100644 --- a/src/Escalier.Parser.Tests/Tests.fs +++ b/src/Escalier.Parser.Tests/Tests.fs @@ -9,7 +9,7 @@ open FParsec open Escalier.Parser let lit = run Parser.lit -let expr = run Parser.expr +let expr = run (Parser.expr .>> eof) let pattern = run Parser.pattern let stmt = run Parser.stmt let typeAnn = run Parser.typeAnn @@ -37,6 +37,19 @@ let ParseString () = Verifier.Verify(result, settings).ToTask() |> Async.AwaitTask +[] +let ParseOtherLiterals () = + let src = + """ + let a = undefined + let b = null + """ + + let ast = script src + let result = $"input: %s{src}\noutput: %A{ast}" + + Verifier.Verify(result, settings).ToTask() |> Async.AwaitTask + [] let ParseTemplateString () = let src = """msg = `foo ${`bar ${baz}`}`""" @@ -93,6 +106,22 @@ let ParseCallThenIndexer () = Verifier.Verify(result, settings).ToTask() |> Async.AwaitTask +[] +let ParseObjProperty () = + let src = "obj.a.b" + let expr = expr src + let result = $"input: %s{src}\noutput: %A{expr}" + + Verifier.Verify(result, settings).ToTask() |> Async.AwaitTask + +[] +let ParseObjPropWithOptChain () = + let src = "obj?.a?.b" + let expr = expr src + let result = $"input: %s{src}\noutput: %A{expr}" + + Verifier.Verify(result, settings).ToTask() |> Async.AwaitTask + [] let ParseFuncDef () = let src = "fn (x, y) { x }" @@ -208,3 +237,27 @@ let ParseObjRestSpread () = let result = $"input: %s{src}\noutput: %A{ast}" Verifier.Verify(result, settings).ToTask() |> Async.AwaitTask + +[] +let ParseOptionalProps () = + let src = + """ + type Obj = {a?: {b?: {c?: number}}} + """ + + let ast = script src + let result = $"input: %s{src}\noutput: %A{ast}" + + Verifier.Verify(result, settings).ToTask() |> Async.AwaitTask + +[] +let ParseOptionalParams () = + let src = + """ + let foo = fn(a?: number, b?: string) => a + """ + + let ast = script src + let result = $"input: %s{src}\noutput: %A{ast}" + + Verifier.Verify(result, settings).ToTask() |> Async.AwaitTask diff --git a/src/Escalier.Parser.Tests/snapshots/Tests.ParseObjLitAndObjPat.verified.txt b/src/Escalier.Parser.Tests/snapshots/Tests.ParseObjLitAndObjPat.verified.txt index b9bb3bad..05d67110 100644 --- a/src/Escalier.Parser.Tests/snapshots/Tests.ParseObjLitAndObjPat.verified.txt +++ b/src/Escalier.Parser.Tests/snapshots/Tests.ParseObjLitAndObjPat.verified.txt @@ -11,14 +11,20 @@ output: Success: [{ Kind = ("Point", { Kind = Object - [Property ("x", { Kind = Keyword Number - Span = { Start = (Ln: 2, Col: 22) - Stop = (Ln: 2, Col: 28) } - InferredType = None }); - Property ("y", { Kind = Keyword Number - Span = { Start = (Ln: 2, Col: 33) - Stop = (Ln: 2, Col: 39) } - InferredType = None })] + [Property { Name = "x" + TypeAnn = { Kind = Keyword Number + Span = { Start = (Ln: 2, Col: 22) + Stop = (Ln: 2, Col: 28) } + InferredType = None } + Optional = false + Readonly = false }; + Property { Name = "y" + TypeAnn = { Kind = Keyword Number + Span = { Start = (Ln: 2, Col: 33) + Stop = (Ln: 2, Col: 39) } + InferredType = None } + Optional = false + Readonly = false }] Span = { Start = (Ln: 2, Col: 18) Stop = (Ln: 3, Col: 5) } InferredType = None }, None) diff --git a/src/Escalier.Parser.Tests/snapshots/Tests.ParseObjPropWithOptChain.verified.txt b/src/Escalier.Parser.Tests/snapshots/Tests.ParseObjPropWithOptChain.verified.txt new file mode 100644 index 00000000..f7de2c09 --- /dev/null +++ b/src/Escalier.Parser.Tests/snapshots/Tests.ParseObjPropWithOptChain.verified.txt @@ -0,0 +1,11 @@ +input: obj?.a?.b +output: Success: { Kind = Member ({ Kind = Member ({ Kind = Identifier "obj" + Span = { Start = (Ln: 1, Col: 1) + Stop = (Ln: 1, Col: 4) } + InferredType = None }, "a", true) + Span = { Start = (Ln: 1, Col: 1) + Stop = (Ln: 1, Col: 7) } + InferredType = None }, "b", true) + Span = { Start = (Ln: 1, Col: 1) + Stop = (Ln: 1, Col: 10) } + InferredType = None } \ No newline at end of file diff --git a/src/Escalier.Parser.Tests/snapshots/Tests.ParseObjProperty.verified.txt b/src/Escalier.Parser.Tests/snapshots/Tests.ParseObjProperty.verified.txt new file mode 100644 index 00000000..dd7052de --- /dev/null +++ b/src/Escalier.Parser.Tests/snapshots/Tests.ParseObjProperty.verified.txt @@ -0,0 +1,11 @@ +input: obj.a.b +output: Success: { Kind = Member ({ Kind = Member ({ Kind = Identifier "obj" + Span = { Start = (Ln: 1, Col: 1) + Stop = (Ln: 1, Col: 4) } + InferredType = None }, "a", false) + Span = { Start = (Ln: 1, Col: 1) + Stop = (Ln: 1, Col: 6) } + InferredType = None }, "b", false) + Span = { Start = (Ln: 1, Col: 1) + Stop = (Ln: 1, Col: 8) } + InferredType = None } \ No newline at end of file diff --git a/src/Escalier.Parser.Tests/snapshots/Tests.ParseOptionalParams.verified.txt b/src/Escalier.Parser.Tests/snapshots/Tests.ParseOptionalParams.verified.txt new file mode 100644 index 00000000..4c770669 --- /dev/null +++ b/src/Escalier.Parser.Tests/snapshots/Tests.ParseOptionalParams.verified.txt @@ -0,0 +1,60 @@ +input: + let foo = fn(a?: number, b?: string) => a + +output: Success: [{ Kind = + Decl + { Kind = + VarDecl + ({ Kind = Identifier { Span = { Start = (Ln: 2, Col: 9) + Stop = (Ln: 2, Col: 13) } + Name = "foo" + IsMut = false } + Span = { Start = (Ln: 2, Col: 9) + Stop = (Ln: 2, Col: 13) } + InferredType = None }, + { Kind = + Function + { Sig = + { TypeParams = None + ParamList = + [{ Pattern = + { Kind = + Identifier { Span = { Start = (Ln: 2, Col: 18) + Stop = (Ln: 2, Col: 19) } + Name = "a" + IsMut = false } + Span = { Start = (Ln: 2, Col: 18) + Stop = (Ln: 2, Col: 19) } + InferredType = None } + TypeAnn = Some { Kind = Keyword Number + Span = { Start = (Ln: 2, Col: 22) + Stop = (Ln: 2, Col: 28) } + InferredType = None } + Optional = true }; + { Pattern = + { Kind = + Identifier { Span = { Start = (Ln: 2, Col: 30) + Stop = (Ln: 2, Col: 31) } + Name = "b" + IsMut = false } + Span = { Start = (Ln: 2, Col: 30) + Stop = (Ln: 2, Col: 31) } + InferredType = None } + TypeAnn = Some { Kind = Keyword String + Span = { Start = (Ln: 2, Col: 34) + Stop = (Ln: 2, Col: 40) } + InferredType = None } + Optional = true }] + ReturnType = None + Throws = None } + Body = Expr { Kind = Identifier "a" + Span = { Start = (Ln: 2, Col: 45) + Stop = (Ln: 3, Col: 3) } + InferredType = None } } + Span = { Start = (Ln: 2, Col: 15) + Stop = (Ln: 3, Col: 3) } + InferredType = None }, None) + Span = { Start = (Ln: 2, Col: 5) + Stop = (Ln: 3, Col: 3) } } + Span = { Start = (Ln: 2, Col: 5) + Stop = (Ln: 3, Col: 3) } }] \ No newline at end of file diff --git a/src/Escalier.Parser.Tests/snapshots/Tests.ParseOptionalProps.verified.txt b/src/Escalier.Parser.Tests/snapshots/Tests.ParseOptionalProps.verified.txt new file mode 100644 index 00000000..1149d788 --- /dev/null +++ b/src/Escalier.Parser.Tests/snapshots/Tests.ParseOptionalProps.verified.txt @@ -0,0 +1,47 @@ +input: + type Obj = {a?: {b?: {c?: number}}} + +output: Success: [{ Kind = + Decl + { Kind = + TypeDecl + ("Obj", + { Kind = + Object + [Property + { Name = "a" + TypeAnn = + { Kind = + Object + [Property + { Name = "b" + TypeAnn = + { Kind = + Object + [Property + { Name = "c" + TypeAnn = + { Kind = Keyword Number + Span = + { Start = (Ln: 2, Col: 31) + Stop = (Ln: 2, Col: 37) } + InferredType = None } + Optional = true + Readonly = false }] + Span = { Start = (Ln: 2, Col: 26) + Stop = (Ln: 2, Col: 38) } + InferredType = None } + Optional = true + Readonly = false }] + Span = { Start = (Ln: 2, Col: 21) + Stop = (Ln: 2, Col: 39) } + InferredType = None } + Optional = true + Readonly = false }] + Span = { Start = (Ln: 2, Col: 16) + Stop = (Ln: 3, Col: 3) } + InferredType = None }, None) + Span = { Start = (Ln: 2, Col: 5) + Stop = (Ln: 3, Col: 3) } } + Span = { Start = (Ln: 2, Col: 5) + Stop = (Ln: 3, Col: 3) } }] \ No newline at end of file diff --git a/src/Escalier.Parser.Tests/snapshots/Tests.ParseOtherLiterals.verified.txt b/src/Escalier.Parser.Tests/snapshots/Tests.ParseOtherLiterals.verified.txt new file mode 100644 index 00000000..4c2e7d44 --- /dev/null +++ b/src/Escalier.Parser.Tests/snapshots/Tests.ParseOtherLiterals.verified.txt @@ -0,0 +1,38 @@ +input: + let a = undefined + let b = null + +output: Success: [{ Kind = + Decl + { Kind = + VarDecl ({ Kind = Identifier { Span = { Start = (Ln: 2, Col: 7) + Stop = (Ln: 2, Col: 9) } + Name = "a" + IsMut = false } + Span = { Start = (Ln: 2, Col: 7) + Stop = (Ln: 2, Col: 9) } + InferredType = None }, { Kind = Literal Undefined + Span = { Start = (Ln: 2, Col: 11) + Stop = (Ln: 2, Col: 20) } + InferredType = None }, None) + Span = { Start = (Ln: 2, Col: 3) + Stop = (Ln: 3, Col: 3) } } + Span = { Start = (Ln: 2, Col: 3) + Stop = (Ln: 3, Col: 3) } }; + { Kind = + Decl + { Kind = + VarDecl ({ Kind = Identifier { Span = { Start = (Ln: 3, Col: 7) + Stop = (Ln: 3, Col: 9) } + Name = "b" + IsMut = false } + Span = { Start = (Ln: 3, Col: 7) + Stop = (Ln: 3, Col: 9) } + InferredType = None }, { Kind = Literal Null + Span = { Start = (Ln: 3, Col: 11) + Stop = (Ln: 3, Col: 15) } + InferredType = None }, None) + Span = { Start = (Ln: 3, Col: 3) + Stop = (Ln: 4, Col: 3) } } + Span = { Start = (Ln: 3, Col: 3) + Stop = (Ln: 4, Col: 3) } }] \ No newline at end of file diff --git a/src/Escalier.Parser/Parser.fs b/src/Escalier.Parser/Parser.fs index 5dfade83..24547778 100644 --- a/src/Escalier.Parser/Parser.fs +++ b/src/Escalier.Parser/Parser.fs @@ -96,7 +96,11 @@ module Parser = (pstring "true" |>> fun _ -> Literal.Boolean true) <|> (pstring "false" |>> fun _ -> Literal.Boolean false) - litRef.Value <- number <|> string <|> boolean + let otherLiterals = + (pstring "undefined" |>> fun _ -> Literal.Undefined) + <|> (pstring "null" |>> fun _ -> Literal.Null) + + litRef.Value <- number <|> string <|> boolean <|> otherLiterals let mergeSpans (x: Span) (y: Span) = { Start = x.Start; Stop = y.Stop } @@ -239,7 +243,26 @@ module Parser = let term = (atom .>> ws) <|> between (strWs "(") (strWs ")") expr - opp.TermParser <- term + let memberOp = + fun (optChain: bool) (x: Expr) (y: Expr) -> + match y.Kind with + | Identifier ident -> + { Expr.Kind = ExprKind.Member(x, ident, optChain) + Span = mergeSpans x.Span y.Span + InferredType = None } + | _ -> failwith "Expected identifier" + + let member': Parser<(Expr -> Expr -> Expr), unit> = + (pstring "?." <|> pstring ".") .>> spaces + >>= fun op -> + match op with + | "." -> preturn (memberOp false) + | "?." -> preturn (memberOp true) + | _ -> failwith "Expected '.' or '?.'" + + let termWithPropSuffix = chainl1 term member' + + opp.TermParser <- termWithPropSuffix type Assoc = Associativity @@ -255,7 +278,7 @@ module Parser = "[", (pipe2 getPosition ((ws >>. expr) .>> (strWs "]")) <| fun p1 expr -> ([ expr ], p1)), // (indices, position) - 18, + 17, true, (), (fun (indices, stop) target -> @@ -272,7 +295,7 @@ module Parser = "(", (pipe2 getPosition (sepBy (ws >>. expr) (strWs ",") .>> (strWs ")")) <| fun p1 args -> (args, p1)), // args - 18, + 17, true, (), (fun (args, stop) callee -> @@ -528,11 +551,21 @@ module Parser = InferredType = None } let private objTypeAnnKeyValue: Parser = - pipe4 getPosition ident (strWs ":" >>. typeAnn) getPosition - <| fun p1 name typeAnn p2 -> + pipe5 + getPosition + ident + (opt (strWs "?")) + (strWs ":" >>. typeAnn) + getPosition + <| fun p1 name optional typeAnn p2 -> // TODO: add location information let span = { Start = p1; Stop = p2 } - ObjTypeAnnElem.Property(name, typeAnn) + + ObjTypeAnnElem.Property + { Name = name + TypeAnn = typeAnn + Optional = optional.IsSome + Readonly = false } let private objTypeAnnElem = choice [ objTypeAnnKeyValue ] diff --git a/src/Escalier.TypeChecker.Tests/NoParseTests.fs b/src/Escalier.TypeChecker.Tests/NoParseTests.fs index 3512ae3f..90cc6efc 100644 --- a/src/Escalier.TypeChecker.Tests/NoParseTests.fs +++ b/src/Escalier.TypeChecker.Tests/NoParseTests.fs @@ -302,7 +302,6 @@ let RecursiveUnification () = [] let InferGenericAndNonGeneric () = result { - printfn "InferGenericAndNonGeneric - start" nextVariableId <- 0 let ast = @@ -330,7 +329,6 @@ let InferGenericAndNonGeneric () = let t = getType "foo" newEnv nonGeneric (* fn g => let f = fn x => g in [f 3, f true] *) Assert.Equal("fn (g: A) -> [A, A]", t.ToString()) - printfn "InferGenericAndNonGeneric - end" } [] diff --git a/src/Escalier.TypeChecker.Tests/Tests.fs b/src/Escalier.TypeChecker.Tests/Tests.fs index 8c4a54b7..0e09b250 100644 --- a/src/Escalier.TypeChecker.Tests/Tests.fs +++ b/src/Escalier.TypeChecker.Tests/Tests.fs @@ -180,7 +180,6 @@ let InferIfElse () = Assert.Equal("5 | \"hello\"", t.ToString()) } - printfn "result = %A" result Assert.False(Result.isError result) [] @@ -488,5 +487,52 @@ let InferObjectRestSpread () = Assert.Value(env, "obj2", "{a: 5} & {b: \"hello\", c: true}") } - printfn "result = %A" result + Assert.False(Result.isError result) + +[] +let InferObjProps () = + let result = + result { + let src = + """ + let obj = {a: {b: 5, c: "hello"}} + let b = obj.a.b + let c = obj.a.c + """ + + let! env = inferScript src + + Assert.Value(env, "b", "5") + Assert.Value(env, "c", "\"hello\"") + } + + Assert.False(Result.isError result) + +[] +let InferOptionalChaining () = + let result = + result { + let src = + """ + type Obj = {a?: {b?: {c: number}}} + let obj: Obj = {a: {b: undefined}} + let a = obj.a + let b = obj.a?.b + let c = obj.a?.b?.c + type Point = {x: number, y: number} + let p: Point = {x: 5, y: 10} + let x = p?.x + """ + + let! env = inferScript src + + Assert.Value(env, "obj", "Obj") + Assert.Value(env, "a", "{b?: {c: number}} | undefined") + Assert.Value(env, "b", "{c: number} | undefined") + Assert.Value(env, "c", "number | undefined") + Assert.Value(env, "p", "Point") + Assert.Value(env, "x", "number") + } + + printf "result = %A" result Assert.False(Result.isError result) diff --git a/src/Escalier.TypeChecker/Library.fs b/src/Escalier.TypeChecker/Library.fs index 8bcd6877..822f9c0b 100644 --- a/src/Escalier.TypeChecker/Library.fs +++ b/src/Escalier.TypeChecker/Library.fs @@ -434,6 +434,14 @@ module rec TypeChecker = | Literal.String _, "string" -> () | Literal.Boolean _, "boolean" -> () | _, _ -> return! Error(TypeError.TypeMismatch(t1, t2)) + | Literal l1, Literal l2 -> + match l1, l2 with + | Literal.Number n1, Literal.Number n2 when n1 = n2 -> () + | Literal.String s1, Literal.String s2 when s1 = s2 -> () + | Literal.Boolean b1, Literal.Boolean b2 when b1 = b2 -> () + | Literal.Null, Literal.Null -> () + | Literal.Undefined, Literal.Undefined -> () + | _, _ -> return! Error(TypeError.TypeMismatch(t1, t2)) | Object elems1, Object elems2 -> let namedProps1 = @@ -456,9 +464,24 @@ module rec TypeChecker = elems2 |> Map.ofList + let undefined = + { Kind = TypeKind.Literal(Literal.Undefined) + Provenance = None } + for KeyValue(name, prop2) in namedProps2 do match namedProps1.TryFind name with - | Some(prop1) -> do! unify env prop1.Type prop2.Type + | Some(prop1) -> + let p1Type = + match prop1.Optional with + | true -> union [ prop1.Type; undefined ] + | false -> prop1.Type + + let p2Type = + match prop2.Optional with + | true -> union [ prop2.Type; undefined ] + | false -> prop2.Type + + do! unify env p1Type p2Type | None -> if not prop2.Optional then return! Error(TypeError.TypeMismatch(t1, t2)) @@ -545,6 +568,15 @@ module rec TypeChecker = do! unify env restType newRestType | _ -> return! Error(TypeError.TypeMismatch(t1, t2)) + | _, TypeKind.Union(types) -> + + let unifier = + List.tryFind (fun t -> unify env t1 t |> Result.isOk) types + + match unifier with + | Some _ -> return () + | _ -> return! Error(TypeError.TypeMismatch(t1, t2)) + | _, _ -> let t1' = expandType env t1 @@ -674,21 +706,15 @@ module rec TypeChecker = | TypeRef { Name = name TypeArgs = typeArgs Scheme = scheme } -> - - // TODO: figure out a better solution for this, we may need to - // reintroduce Primitive and Keyword types - if name = "undefined" then - t - else - let t = - match scheme with + let t = + match scheme with + | Some scheme -> expandScheme env scheme typeArgs + | None -> + match env.Schemes.TryFind name with | Some scheme -> expandScheme env scheme typeArgs - | None -> - match env.Schemes.TryFind name with - | Some scheme -> expandScheme env scheme typeArgs - | None -> failwith $"{name} is not in scope" + | None -> failwith $"{name} is not in scope" - expand t + expand t | _ -> t expand t @@ -766,7 +792,7 @@ module rec TypeChecker = let retExprs = findReturns f let undefined = - { Kind = makePrimitiveKind "undefined" + { Kind = TypeKind.Literal(Literal.Undefined) Provenance = None } let! retType = @@ -823,7 +849,7 @@ module rec TypeChecker = match elseBranchTy with | Some(elseBranchTy) -> union [ thenBranchTy; elseBranchTy ] | None -> - { Kind = makePrimitiveKind "undefined" + { Kind = TypeKind.Literal(Literal.Undefined) Provenance = None } | ExprKind.Object elems -> let mutable spreadTypes = [] @@ -874,8 +900,15 @@ module rec TypeChecker = return { Kind = Intersection([ objType ] @ spreadTypes) Provenance = None } + | Member(obj, prop, optChain) -> + let! objType = inferExpr obj env nonGeneric + // TODO: handle optional chaining + // TODO: lookup properties on object type + return getPropType env objType prop optChain | _ -> + printfn "expr.Kind = %A" expr.Kind + return! Error( TypeError.NotImplemented "TODO: finish implementing infer_expr" @@ -888,6 +921,68 @@ module rec TypeChecker = t) r + let getPropType (env: Env) (t: Type) (name: string) (optChain: bool) : Type = + let t = prune t + + match t.Kind with + | Object elems -> + let elems = + List.choose + (fun (elem: ObjTypeElem) -> + match elem with + | Property p -> Some(p.Name, p) + | _ -> None) + elems + |> Map.ofList + + match elems.TryFind name with + | Some(p) -> + match p.Optional with + | true -> + let undefined = + { Kind = TypeKind.Literal(Literal.Undefined) + Provenance = None } + + union [ p.Type; undefined ] + | false -> p.Type + | None -> failwithf $"Property {name} not found" + | TypeRef { Name = typeRefName + Scheme = scheme + TypeArgs = typeArgs } -> + match scheme with + | Some scheme -> + getPropType env (expandScheme env scheme typeArgs) name optChain + | None -> + match env.Schemes.TryFind typeRefName with + | Some scheme -> + getPropType env (expandScheme env scheme typeArgs) name optChain + | None -> failwithf $"{name} not in scope" + | Union types -> + let undefinedTypes, definedTypes = + List.partition + (fun t -> t.Kind = TypeKind.Literal(Literal.Undefined)) + types + + if undefinedTypes.IsEmpty then + failwith "TODO: lookup member on union type" + else if not optChain then + failwith "Can't lookup property on undefined" + else + match definedTypes with + | [ t ] -> + let t = getPropType env t name optChain + + let undefined = + { Kind = TypeKind.Literal(Literal.Undefined) + Provenance = None } + + union [ t; undefined ] + | _ -> failwith "TODO: lookup member on union type" + + // TODO: intersection types + // TODO: union types + | _ -> failwith $"TODO: lookup member on type - {t}" + let inferBlockOrExpr (env: Env) (nonGeneric: Set) @@ -917,7 +1012,7 @@ module rec TypeChecker = stmts let undefined = - { Kind = makePrimitiveKind "undefined" + { Kind = TypeKind.Literal(Literal.Undefined) Provenance = None } match List.tryLast stmtResults with @@ -973,15 +1068,18 @@ module rec TypeChecker = (fun (elem: ObjTypeAnnElem) -> result { match elem with - | ObjTypeAnnElem.Property(name, typeAnn) -> + | ObjTypeAnnElem.Property { Name = name + TypeAnn = typeAnn + Optional = optional + Readonly = readonly } -> let! t = inferTypeAnn env typeAnn return Property { Name = name - Optional = false - Readonly = false - Type = t } + Type = t + Optional = optional + Readonly = readonly } | ObjTypeAnnElem.Callable ``function`` -> return! Error(TypeError.NotImplemented "todo") | ObjTypeAnnElem.Constructor ``function`` -> @@ -1035,7 +1133,7 @@ module rec TypeChecker = let! paramList = List.traverseResultM - (fun p -> + (fun (p: FuncParam) -> result { let! t = inferTypeAnn env p.TypeAnn let pattern = patternToPattern p.Pattern @@ -1450,7 +1548,10 @@ module rec TypeChecker = | TypeAnnKind.Function f -> walk f.ReturnType Option.iter walk f.Throws - List.iter (fun param -> walk param.TypeAnn) f.ParamList + + List.iter + (fun (param: FuncParam) -> walk param.TypeAnn) + f.ParamList | TypeAnnKind.Keyof target -> walk target | TypeAnnKind.Rest target -> walk target | TypeAnnKind.Typeof target -> walkExpr visitor target @@ -1471,8 +1572,17 @@ module rec TypeChecker = walk typeAnn + + let rec flatten (types: list) : list = + List.collect + (fun t -> + match t.Kind with + | TypeKind.Union ts -> flatten ts + | _ -> [ t ]) + types + let union (types: list) : Type = - match types with + match flatten types with | [] -> { Kind = makePrimitiveKind "never" Provenance = None }