diff --git a/src/Escalier.Codegen.Tests/Tests.fs b/src/Escalier.Codegen.Tests/Tests.fs index 76c856a..efc7b2c 100644 --- a/src/Escalier.Codegen.Tests/Tests.fs +++ b/src/Escalier.Codegen.Tests/Tests.fs @@ -400,20 +400,15 @@ let CodegenFunction () = printfn "error = %A" error failwith "ParseError" -// TODO(#349): Make function params that are primitive types immutable when migrating -// the types from .d.ts files. -// TODO(#351): Update getPropType to check for properties on the Extends type of an -// object type. [] let CodegenAsyncFunction () = let res = result { let src = """ - let fetchJSON = async fn (mut url: string) { + let fetchJSON = async fn (url: string) { let res = await fetch(url); - return res; - // return res.json(); + return res.json(); }; """ diff --git a/src/Escalier.Codegen.Tests/snapshots/Tests.CodegenAsyncFunction.verified.txt b/src/Escalier.Codegen.Tests/snapshots/Tests.CodegenAsyncFunction.verified.txt index 83e02b4..394a6d1 100644 --- a/src/Escalier.Codegen.Tests/snapshots/Tests.CodegenAsyncFunction.verified.txt +++ b/src/Escalier.Codegen.Tests/snapshots/Tests.CodegenAsyncFunction.verified.txt @@ -1,12 +1,11 @@ input: - let fetchJSON = async fn (mut url: string) { + let fetchJSON = async fn (url: string) { let res = await fetch(url); - return res; - // return res.json(); + return res.json(); }; output: var fetchJSON = function (url) { var res = await fetch(url); - return res; + return res.json(); }; \ No newline at end of file diff --git a/src/Escalier.Data/Visitor.fs b/src/Escalier.Data/Visitor.fs index c1a4856..38da59b 100644 --- a/src/Escalier.Data/Visitor.fs +++ b/src/Escalier.Data/Visitor.fs @@ -23,8 +23,6 @@ module rec ExprVisitor = | ExprKind.Identifier _ -> () | ExprKind.Literal _ -> () | ExprKind.Function f -> - printfn $"walking function, state = {state}" - for p in f.Sig.ParamList do walkPattern visitor state p.Pattern Option.iter (walkTypeAnn visitor state) p.TypeAnn diff --git a/src/Escalier.Interop.Tests/Migrate.fs b/src/Escalier.Interop.Tests/Migrate.fs index 78f090a..af32510 100644 --- a/src/Escalier.Interop.Tests/Migrate.fs +++ b/src/Escalier.Interop.Tests/Migrate.fs @@ -70,7 +70,7 @@ let ParseAndInferBasicDecls () = Assert.Value(env, "a", "number") Assert.Value(env, "b", "string") Assert.Value(env, "c", "boolean") - Assert.Value(env, "d", "fn (mut x: number[]) -> boolean") + Assert.Value(env, "d", "fn (x: number[]) -> boolean") } Assert.True(Result.isOk res) @@ -134,7 +134,7 @@ let ParseAndInferInterface () = Assert.Type( env, "Foo", - "{bar fn (mut self: Self) -> number, baz fn (mut self: Self, x: string) -> boolean, get qux fn () -> string, set qux fn (x: string) -> undefined, ...}" + "{bar fn (self: Self) -> number, baz fn (self: Self, x: string) -> boolean, get qux fn () -> string, set qux fn (x: string) -> undefined, ...}" ) } @@ -195,7 +195,7 @@ let ParseAndInferUnorderedTypeParams () = Assert.Type( env, "MyObjectConstructor", - "{freeze fn (mut self: Self, mut o: T) -> Readonly, ...}" + "{freeze fn (self: Self, o: T) -> Readonly, ...}" ) } diff --git a/src/Escalier.Interop.Tests/Tests.fs b/src/Escalier.Interop.Tests/Tests.fs index d28e0fd..e71c462 100644 --- a/src/Escalier.Interop.Tests/Tests.fs +++ b/src/Escalier.Interop.Tests/Tests.fs @@ -436,7 +436,7 @@ let InferBasicVarDecls () = Assert.Value(env, "a", "number") Assert.Value(env, "b", "string | undefined") Assert.Value(env, "c", "fn (a: number) -> string") - Assert.Value(env, "d", "fn (mut x: T) -> T") + Assert.Value(env, "d", "fn (x: T) -> T") Assert.Value(env, "e", "[5, \"hello\", true]") } @@ -510,7 +510,7 @@ let InferOverloadedFunctionsFromLibDOM () = Assert.Value( env, "scroll", - "fn (mut options?: ScrollToOptions) -> undefined & fn (x: number, y: number) -> undefined" + "fn (options?: ScrollToOptions) -> undefined & fn (x: number, y: number) -> undefined" ) } diff --git a/src/Escalier.Interop/Migrate.fs b/src/Escalier.Interop/Migrate.fs index 2232d94..99dbd58 100644 --- a/src/Escalier.Interop/Migrate.fs +++ b/src/Escalier.Interop/Migrate.fs @@ -129,10 +129,7 @@ module rec Migrate = self - let migrateTypeElement - (isMutable: bool) - (elem: TsTypeElement) - : Syntax.ObjTypeAnnElem = + let migrateTypeElement (elem: TsTypeElement) : Syntax.ObjTypeAnnElem = match elem with | TsCallSignatureDecl { Params = fnParams TypeAnn = typeAnn @@ -149,8 +146,8 @@ module rec Migrate = let f: FuncSig = { TypeParams = typeParams - Self = Some(makeSelfFuncParam isMutable) - ParamList = List.map (migrateFnParam isMutable) fnParams + Self = Some(makeSelfFuncParam false) + ParamList = List.map migrateFnParam fnParams ReturnType = Some retType Throws = None IsAsync = false } @@ -169,10 +166,12 @@ module rec Migrate = | Some t -> migrateTypeAnn t | None -> failwith "all constructor signatures must have a return type" + let self = makeSelfFuncParam + let f: FuncSig = { TypeParams = typeParams - Self = Some(makeSelfFuncParam isMutable) - ParamList = List.map (migrateFnParam isMutable) fnParams + Self = Some(makeSelfFuncParam false) + ParamList = List.map migrateFnParam fnParams ReturnType = Some retType Throws = None IsAsync = false } @@ -215,8 +214,8 @@ module rec Migrate = let f: FuncSig = { TypeParams = typeParams - Self = Some(makeSelfFuncParam isMutable) - ParamList = List.map (migrateFnParam isMutable) fnParams + Self = Some(makeSelfFuncParam false) + ParamList = List.map migrateFnParam fnParams ReturnType = Some retType Throws = None IsAsync = false } @@ -276,7 +275,7 @@ module rec Migrate = Optional = _optional Computed = _computed } -> // TODO: warn if there's a setter on a Readonly interface - let fnParam = migrateFnParam isMutable fnParam + let fnParam = migrateFnParam fnParam let undefined: Syntax.TypeAnn = { Kind = Syntax.Keyword KeywordTypeAnn.Undefined @@ -336,8 +335,7 @@ module rec Migrate = List.map migrateTypeParam tpd.Params) f.TypeParams - let paramList: list = - List.map (migrateFnParam isMutable) f.Params + let paramList: list = List.map migrateFnParam f.Params let fnType: FuncSig = { TypeParams = typeParams @@ -356,7 +354,7 @@ module rec Migrate = List.map migrateTypeParam tpd.Params) f.TypeParams - let paramList = List.map (migrateFnParam isMutable) f.Params + let paramList = List.map migrateFnParam f.Params let fnType: FuncSig = { TypeParams = typeParams @@ -387,11 +385,7 @@ module rec Migrate = TypeAnnKind.Typeof name | TsType.TsTypeLit { Members = members } -> - // Assumes that all methods on object types are mutable - let isMutable = true - - let elems: list = - List.map (migrateTypeElement isMutable) members + let elems: list = List.map migrateTypeElement members TypeAnnKind.Object { Elems = elems @@ -524,47 +518,26 @@ module rec Migrate = Default = Option.map migrateType typeParam.Default Span = DUMMY_SPAN } - let migrateFnParam - (isMutable: bool) - (fnParam: TypeScript.TsFnParam) - : Syntax.FuncParam = + let migrateFnParam (fnParam: TypeScript.TsFnParam) : Syntax.FuncParam = let typeAnn = match fnParam.TypeAnn with | Some typeAnn -> Some(migrateTypeAnn typeAnn) | None -> failwith "all function parameters must have a type annotation" - let isMutable = - match typeAnn with - | None -> isMutable - | Some typeAnn -> - match typeAnn.Kind with - | TypeAnnKind.Literal _ -> false - | TypeAnnKind.Keyword _ -> false - | TypeAnnKind.Function _ -> false - | TypeAnnKind.Keyof _ -> false - | TypeAnnKind.TemplateLiteral _ -> false - | _ -> isMutable - - { Pattern = migrateFnParamPattern isMutable fnParam.Pat + { Pattern = migrateFnParamPattern fnParam.Pat TypeAnn = match fnParam.TypeAnn with | Some typeAnn -> Some(migrateTypeAnn typeAnn) | None -> failwith "all function parameters must have a type annotation" Optional = fnParam.Optional } - let migrateFnParamPattern - (isMutable: bool) - (pat: TypeScript.TsFnParamPat) - : Pattern = + let migrateFnParamPattern (pat: TypeScript.TsFnParamPat) : Pattern = let kind = match pat with | TsFnParamPat.Ident { Id = ident } -> - if ident.Name = "input" then - printfn $"input is mutable: {isMutable}" - PatternKind.Ident { Name = ident.Name - IsMut = isMutable + IsMut = false Assertion = None } | TsFnParamPat.Object { Props = props } -> let elems: list = @@ -624,12 +597,12 @@ module rec Migrate = | Pat.Ident { Id = ident } -> PatternKind.Ident { Name = ident.Name - IsMut = true + IsMut = false Assertion = None } | Pat.Array { Elems = elems } -> let elems = List.map - (fun (elem) -> + (fun elem -> match elem with | Some pat -> migratePat pat | None -> failwith "TODO: handle sparse array patterns") @@ -1010,23 +983,9 @@ module rec Migrate = typeParams let isReadonly = - ident.Name.StartsWith "Readonly" - || ident.Name.EndsWith "ReadOnly" - || (List.contains - ident.Name - [ "Boolean" - "Number" - "String" - "Symbol" - "BigInt" - // TODO: Track what namespace we're inside of if any - // TODO: Track what node_module we're inside of it any - // TODO: Maintain a full qualified list of interfaces that are - // known to be "readonly" like `Intl.NumberFormatOptions` - "NumberFormat" ]) - - let isMutable = not isReadonly - let elems = List.map (migrateTypeElement isMutable) body.Body + ident.Name.StartsWith "Readonly" || ident.Name.EndsWith "ReadOnly" + + let elems = List.map migrateTypeElement body.Body let extends: option> = match extends with diff --git a/src/Escalier.TypeChecker.Tests/Tests.fs b/src/Escalier.TypeChecker.Tests/Tests.fs index 252eafe..d08ccc1 100644 --- a/src/Escalier.TypeChecker.Tests/Tests.fs +++ b/src/Escalier.TypeChecker.Tests/Tests.fs @@ -2119,3 +2119,97 @@ let InferMappedObjectType () = printfn "result = %A" result Assert.False(Result.isError result) + +[] +let InferGetPropertyFromExtends () = + let result = + result { + let src = + """ + let foo = async fn() { + let mut res = await fetch("https://google.com"); + return res.json(); + }; + let bar = foo(); + """ + + let! ctx, env = inferModule src + + Assert.Empty(ctx.Report.Diagnostics) + Assert.Value(env, "bar", "Promise<_>") + } + + Assert.False(Result.isError result) + +[] +let InferAssignTupleLiteralToArrays () = + let result = + result { + let src = + """ + let foo: number[] = [1, 2, 3]; + let mut bar: number[] = [1, 2, 3]; + """ + + let! ctx, env = inferModule src + + Assert.Empty(ctx.Report.Diagnostics) + } + + Assert.False(Result.isError result) + +[ the same as number[]")>] +let InferAssignTupleLiteralToArrayGenericType () = + let result = + result { + let src = + """ + let foo: Array = [1, 2, 3]; + let mut bar: Array = [1, 2, 3]; + """ + + let! ctx, env = inferModule src + + Assert.Empty(ctx.Report.Diagnostics) + } + + Assert.False(Result.isError result) + +[] +let InferMutateUnion () = + let result = + result { + let src = + """ + declare let mut foo: { type: "a", v: number } | {type: "b", v: string }; + foo.type = "b"; + """ + + let! ctx, env = inferModule src + + Assert.Empty(ctx.Report.Diagnostics) + } + + printfn "result = %A" result + Assert.False(Result.isError result) + +[ to pattern matching")>] +let InferMutateUnionWithMatch () = + let result = + result { + let src = + """ + declare let mut foo: { type: "a", v: number } | {type: "b", v: string }; + match foo { + {type: "a"} as foo => foo.v = 5, + {type: "b"} as foo => foo.v = "hello", + }; + """ + + let! ctx, env = inferModule src + + Assert.Empty(ctx.Report.Diagnostics) + } + + printfn "result = %A" result + Assert.False(Result.isError result) diff --git a/src/Escalier.TypeChecker/Error.fs b/src/Escalier.TypeChecker/Error.fs index 2b7ada3..36baa90 100644 --- a/src/Escalier.TypeChecker/Error.fs +++ b/src/Escalier.TypeChecker/Error.fs @@ -17,7 +17,7 @@ module Error = | NotImplemented s -> $"NotImplemented: {s}" | SemanticError s -> $"SemanticError: {s}" | NotInferred -> "NotInferred" - | TypeMismatch(``type``, type1) -> $"TypeMismatch: {``type``} != {type1}" + | TypeMismatch(t1, t2) -> $"TypeMismatch: {t1} != {t2}" | PropertyMissing propName -> $"Object is missing property {propName}" | RecursiveUnification(``type``, type1) -> $"RecursiveUnification: {``type``} != {type1}" diff --git a/src/Escalier.TypeChecker/Infer.fs b/src/Escalier.TypeChecker/Infer.fs index fe3719d..41848d1 100644 --- a/src/Escalier.TypeChecker/Infer.fs +++ b/src/Escalier.TypeChecker/Infer.fs @@ -942,7 +942,9 @@ module rec Infer = | ExprKind.Await(await) -> let! t = inferExpr ctx env None await.Value - match t.Kind with + match (prune t).Kind with + | TypeKind.TypeRef { Name = QualifiedIdent.Ident "Promise" + TypeArgs = Some([ t ]) } -> return t | TypeKind.TypeRef { Name = QualifiedIdent.Ident "Promise" TypeArgs = Some([ t; e ]) } -> await.Throws <- Some e