Skip to content

Commit

Permalink
Default to immutable methods and params when migrating types from .d.…
Browse files Browse the repository at this point in the history
…ts files (#362)

* Default to immutable methods and params when migrating types from .d.ts files

* add a couple of disable tests for the future
  • Loading branch information
kevinbarabash authored Sep 2, 2024
1 parent 2b8b079 commit 9d1e920
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 83 deletions.
9 changes: 2 additions & 7 deletions src/Escalier.Codegen.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
[<Fact>]
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();
};
"""

Expand Down
Original file line number Diff line number Diff line change
@@ -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();
};
2 changes: 0 additions & 2 deletions src/Escalier.Data/Visitor.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/Escalier.Interop.Tests/Migrate.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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, ...}"
)
}

Expand Down Expand Up @@ -195,7 +195,7 @@ let ParseAndInferUnorderedTypeParams () =
Assert.Type(
env,
"MyObjectConstructor",
"{freeze fn <T: {[idx]+?: U | null | undefined | object for idx in string, ...}, U: string | bigint | number | boolean | symbol>(mut self: Self, mut o: T) -> Readonly<T>, ...}"
"{freeze fn <T: {[idx]+?: U | null | undefined | object for idx in string, ...}, U: string | bigint | number | boolean | symbol>(self: Self, o: T) -> Readonly<T>, ...}"
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/Escalier.Interop.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <T>(mut x: T) -> T")
Assert.Value(env, "d", "fn <T>(x: T) -> T")
Assert.Value(env, "e", "[5, \"hello\", true]")
}

Expand Down Expand Up @@ -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"
)
}

Expand Down
85 changes: 22 additions & 63 deletions src/Escalier.Interop/Migrate.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }
Expand All @@ -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 }
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -336,8 +335,7 @@ module rec Migrate =
List.map migrateTypeParam tpd.Params)
f.TypeParams

let paramList: list<FuncParam> =
List.map (migrateFnParam isMutable) f.Params
let paramList: list<FuncParam> = List.map migrateFnParam f.Params

let fnType: FuncSig =
{ TypeParams = typeParams
Expand All @@ -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
Expand Down Expand Up @@ -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<ObjTypeAnnElem> =
List.map (migrateTypeElement isMutable) members
let elems: list<ObjTypeAnnElem> = List.map migrateTypeElement members

TypeAnnKind.Object
{ Elems = elems
Expand Down Expand Up @@ -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<ObjPatElem> =
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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<list<TypeRef>> =
match extends with
Expand Down
94 changes: 94 additions & 0 deletions src/Escalier.TypeChecker.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2119,3 +2119,97 @@ let InferMappedObjectType () =

printfn "result = %A" result
Assert.False(Result.isError result)

[<Fact>]
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)

[<Fact>]
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)

[<Fact(Skip = "TODO: treat Array<number> the same as number[]")>]
let InferAssignTupleLiteralToArrayGenericType () =
let result =
result {
let src =
"""
let foo: Array<number> = [1, 2, 3];
let mut bar: Array<number> = [1, 2, 3];
"""

let! ctx, env = inferModule src

Assert.Empty(ctx.Report.Diagnostics)
}

Assert.False(Result.isError result)

[<Fact(Skip = "Member access on union types is not allowed")>]
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)

[<Fact(Skip = "TODO(#363): Add support for as <ident> 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)
2 changes: 1 addition & 1 deletion src/Escalier.TypeChecker/Error.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
4 changes: 3 additions & 1 deletion src/Escalier.TypeChecker/Infer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9d1e920

Please sign in to comment.