Skip to content

Commit

Permalink
Handle object spreads
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinbarabash committed Nov 19, 2023
1 parent d04cdb9 commit 379e01d
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 36 deletions.
12 changes: 6 additions & 6 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<PropertyGroup>
<OtherFlags>$(OtherFlags) --warnon:3559 --warnon:3560 --test:GraphBasedChecking --test:ParallelOptimization --test:ParallelIlxGen</OtherFlags>
</PropertyGroup>
<Target Name="FSharpLint" AfterTargets="BeforeBuild">
<Exec
Command="dotnet fsharplint -f msbuild lint $(MSBuildProjectFullPath)"
IgnoreExitCode="true"
/>
</Target>
<!-- <Target Name="FSharpLint" AfterTargets="BeforeBuild">-->
<!-- <Exec-->
<!-- Command="dotnet fsharplint -f msbuild lint $(MSBuildProjectFullPath)"-->
<!-- IgnoreExitCode="true"-->
<!-- />-->
<!-- </Target>-->
</Project>
40 changes: 35 additions & 5 deletions src/Escalier.Data/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ module Syntax =
| Null -> "null"
| Undefined -> "undefined"

// TODO: add SpreadPat?
type ObjPatElem =
// TODO: add isMut
| KeyValuePat of
span: Span *
key: string *
Expand All @@ -120,6 +120,7 @@ module Syntax =
name: string *
init: option<Expr> *
isMut: bool
// TODO: rename to RestSpreadPat
| RestPat of span: Span * target: Pattern * isMut: bool

type BindingIdent =
Expand Down Expand Up @@ -246,8 +247,8 @@ module Type =
Scheme: option<Scheme> }

type ObjPatElem =
| KeyValuePat of key: string * value: Pattern
| ShorthandPat of name: string * value: option<Syntax.Expr>
| KeyValuePat of key: string * value: Pattern * init: option<Syntax.Expr>
| ShorthandPat of name: string * init: option<Syntax.Expr>
| RestPat of target: Pattern

type Pattern =
Expand All @@ -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
Expand Down Expand Up @@ -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<list<string>>
Expand Down
9 changes: 7 additions & 2 deletions src/Escalier.TypeChecker.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
110 changes: 87 additions & 23 deletions src/Escalier.TypeChecker/Library.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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<unit, TypeError> =
// printfn $"unify {t1} {t2}"

result {
match (prune t1).Kind, (prune t2).Kind with
| TypeVar _, _ -> do! bind t1 t2
Expand Down Expand Up @@ -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 ] ->
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) ->
Expand All @@ -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(
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 379e01d

Please sign in to comment.