Skip to content

Commit

Permalink
Handle member access on literals and primitives (#339)
Browse files Browse the repository at this point in the history
* Handle member access on literals and primitives

* Handle member access directly on literals instead of just on variables whose type is a literal type
  • Loading branch information
kevinbarabash committed Aug 12, 2024
1 parent ab2767c commit 9e88903
Show file tree
Hide file tree
Showing 4 changed files with 272 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<Compile Include="BuildGraphTests.fs"/>
<Compile Include="Jsx.fs"/>
<Compile Include="Exact.fs"/>
<Compile Include="Primitives.fs"/>
<Compile Include="Program.fs"/>
</ItemGroup>
<ItemGroup>
Expand Down
202 changes: 202 additions & 0 deletions src/Escalier.TypeChecker.Tests/Primitives.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
module Escalier.TypeChecker.Tests.Primitives

open FsToolkit.ErrorHandling
open Xunit

open Escalier.TypeChecker.Error

open TestUtils

[<Fact>]
let StringLiteralProperties () =
let res =
result {
let src =
"""
let msg = "hello";
let len = msg.length;
let upper = msg.toUpperCase();
"""

let! ctx, env = inferModule src

Assert.Empty(ctx.Report.Diagnostics)

Assert.Value(env, "len", "number")
Assert.Value(env, "upper", "string")
}

Assert.False(Result.isError res)

[<Fact>]
let StringPropertyOnDirectLiteral () =
let res =
result {
let src =
"""
let len = "hello".length;
"""

let! ctx, env = inferModule src

Assert.Empty(ctx.Report.Diagnostics)

Assert.Value(env, "len", "number")
}

Assert.False(Result.isError res)


[<Fact>]
let StringPrimitiveProperties () =
let res =
result {
let src =
"""
let msg: string = "hello";
let len = msg.length;
let upper = msg.toUpperCase();
"""

let! ctx, env = inferModule src

Assert.Empty(ctx.Report.Diagnostics)

Assert.Value(env, "len", "number")
Assert.Value(env, "upper", "string")
}

Assert.False(Result.isError res)

[<Fact>]
let NumberLiteralProperties () =
let res =
result {
let src =
"""
let num = 5;
let currency = num.toFixed(2);
"""

let! ctx, env = inferModule src

Assert.Empty(ctx.Report.Diagnostics)

Assert.Value(env, "currency", "string")
}

Assert.False(Result.isError res)

[<Fact>]
let NumberPropertyDirectOnLiteral () =
let res =
result {
let src =
"""
let currency = (5).toFixed(2);
"""

let! ctx, env = inferModule src

Assert.Empty(ctx.Report.Diagnostics)

Assert.Value(env, "currency", "string")
}

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

[<Fact>]
let NumberPrimitiveProperties () =
let res =
result {
let src =
"""
let num: number = 5;
let currency = num.toFixed(2);
"""

let! ctx, env = inferModule src

Assert.Empty(ctx.Report.Diagnostics)

Assert.Value(env, "currency", "string")
}

Assert.False(Result.isError res)

[<Fact>]
let BooleanLiteralProperties () =
let res =
result {
let src =
"""
let flag = true;
let value = flag.valueOf();
"""

let! ctx, env = inferModule src

Assert.Empty(ctx.Report.Diagnostics)

Assert.Value(env, "value", "boolean")
}

Assert.False(Result.isError res)

[<Fact>]
let BooleanPrimitiveProperties () =
let res =
result {
let src =
"""
let flag: boolean = true;
let value = flag.valueOf();
"""

let! ctx, env = inferModule src

Assert.Empty(ctx.Report.Diagnostics)

Assert.Value(env, "value", "boolean")
}

Assert.False(Result.isError res)

[<Fact>]
let SymbolProperties () =
let res =
result {
let src =
"""
let sym = Symbol();
let name = sym.toString();
"""

let! ctx, env = inferModule src

Assert.Empty(ctx.Report.Diagnostics)

Assert.Value(env, "name", "string")
}

Assert.False(Result.isError res)

[<Fact>]
let UniqueSymbolProperties () =
let res =
result {
let src =
"""
declare let sym: unique symbol;
let name = sym.toString();
"""

let! ctx, env = inferModule src

Assert.Empty(ctx.Report.Diagnostics)

Assert.Value(env, "name", "string")
}

Assert.False(Result.isError res)
6 changes: 5 additions & 1 deletion src/Escalier.TypeChecker/Helpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -313,12 +313,16 @@ let rec getIsMut (ctx: Ctx) (env: Env) (expr: Expr) : Result<bool, TypeError> =
| ExprKind.Identifier name ->
let! _, isMut = env.GetBinding name
return isMut
| ExprKind.Literal _ -> return false
| ExprKind.Object _ -> return true
| ExprKind.Tuple _ -> return true
| ExprKind.Index(target, _index, _optChain) ->
return! getIsMut ctx env target
| ExprKind.Member(target, _name, _optChain) ->
return! getIsMut ctx env target
| _ ->
return! Error(TypeError.SemanticError $"{expr} is not a valid lvalue")
return!
Error(TypeError.NotImplemented $"determine the mutability of {expr}")
}

// Return a structure which indicates whether the object represented by the property
Expand Down
65 changes: 64 additions & 1 deletion src/Escalier.TypeChecker/Infer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1654,8 +1654,40 @@ module rec Infer =
instantiateType ctx prop arrayScheme.TypeParams (Some [ elem ])

return prop
// TODO: intersection types
| TypeKind.Literal(Literal.String _)
| TypeKind.Primitive Primitive.String ->
let scheme =
match env.TryFindScheme "String" with
| Some scheme -> scheme
| None -> failwith "String not in scope"

return! getPropType ctx env scheme.Type key optChain valueCategory
| TypeKind.Literal(Literal.Number _)
| TypeKind.Primitive Primitive.Number ->
let scheme =
match env.TryFindScheme "Number" with
| Some scheme -> scheme
| None -> failwith "Number not in scope"

return! getPropType ctx env scheme.Type key optChain valueCategory
| TypeKind.Literal(Literal.Boolean _)
| TypeKind.Primitive Primitive.Boolean ->
let scheme =
match env.TryFindScheme "Boolean" with
| Some scheme -> scheme
| None -> failwith "Boolean not in scope"

return! getPropType ctx env scheme.Type key optChain valueCategory
| TypeKind.Primitive Primitive.Symbol
| TypeKind.UniqueSymbol _ ->
let scheme =
match env.TryFindScheme "Symbol" with
| Some scheme -> scheme
| None -> failwith "Symbol not in scope"

return! getPropType ctx env scheme.Type key optChain valueCategory
| _ ->
// TODO: intersection types
return!
Error(TypeError.NotImplemented $"TODO: lookup member on type - {t}")
}
Expand Down Expand Up @@ -2671,6 +2703,37 @@ module rec Infer =
match callee.Kind with
| TypeKind.Function func ->
return! unifyFuncCall ctx env ips args typeArgs func
| TypeKind.TypeRef { Name = name; Scheme = scheme } ->
let callee =
match scheme with
| Some scheme ->
// TODO: handle typeArgs
scheme.Type
| None ->
match env.GetScheme(name) with
| Result.Ok scheme ->
// TODO: handle typeArgs
scheme.Type
| Result.Error _ -> failwith "'{name}' is not in scope"

return! unifyCall ctx env ips args typeArgs callee
| TypeKind.Object { Elems = elems } ->
let mutable callable = None

for elem in elems do
match elem with
| Callable c ->
callable <-
Some
{ Kind = TypeKind.Function c
Provenance = None }
| _ -> ()

match callable with
| Some c -> return! unifyCall ctx env ips args typeArgs c
| None ->
return!
Error(TypeError.SemanticError $"No callable signature in {callee}")
| TypeKind.Intersection types ->
let mutable result = None

Expand Down

0 comments on commit 9e88903

Please sign in to comment.