Skip to content

Commit

Permalink
Record spread and never type (#2737)
Browse files Browse the repository at this point in the history
  • Loading branch information
toots committed Nov 17, 2022
1 parent 4eaa650 commit a7d78d2
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 14 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ New:
- `file.replaygain` to compute the replaygain of a file
- Added support for ImageLib to decode images.
- Added support for completion in emacs based on company (#2652).
- Added syntactic sugar for record spread: `let {foo, gni, ..y} = x`
and `y = { foo = 123, gni = "aabb", ...x}` (#2737)

Changed:

Expand Down
24 changes: 24 additions & 0 deletions doc/content/language.md
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,26 @@ the duration with
print("The duration of the song is #{song.duration} seconds")
```

Records can be re-used using _spreads_:

```liquidsoap
song = { filename = "song.mp3", duration = 257., bpm = 132. }
# This is a fresh value with all the fields from `song` and
# a new `id` field:
song_with_id = { id = 1234, ...song }
```

Alternatively, you can also extend a record using the explicit `v.{...}` syntax:

```liquidsoap
song = { filename = "song.mp3", duration = 257., bpm = 132. }
# This is a fresh value with all the fields from `song` and
# a new `id` field:
song_with_id = song.{id = 1234}
```

### Modules

Records are heavily used in Liquidsoap in order to structure the functions of
Expand Down Expand Up @@ -1072,6 +1092,10 @@ Here are some examples:
let {foo, bar} = {foo = 123, bar = "baz", gni = true}
# foo = 123, bar = "baz"
# Record capture with spread
let {foo, bar, ...x} = {foo = 123, bar = "baz", gni = true}
# foo = 123, bar = "baz", x = {gni = true}
# Module capture
let v.{foo, bar} = "aabbcc".{foo = 123, bar = "baz", gni = true}
# v = "aabbcc", foo = 123, bar = "baz"
Expand Down
8 changes: 8 additions & 0 deletions src/lang/evaluation.ml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ let rec eval_pat pat v =
@ env
| PMeth (pat, l), _ ->
let m, v = Value.split_meths v in
let fields = List.map fst l in
let v =
List.fold_left
(fun v (l, m) ->
if List.mem l fields then v
else { v with Value.value = Meth (l, m, v) })
v m
in
let env = match pat with None -> env | Some pat -> aux env pat v in
List.fold_left
(fun env (lbl, pat) ->
Expand Down
11 changes: 11 additions & 0 deletions src/lang/parser.mly
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ expr:
| LPAR RPAR { mk ~pos:$loc (Tuple []) }
| LPAR inner_tuple RPAR { mk ~pos:$loc (Tuple $2) }
| expr DOT LCUR record RCUR { $4 ~pos:$loc $1 }
| LCUR DOTDOTDOT expr RCUR { $3 }
| LCUR record COMMA DOTDOTDOT expr RCUR
{ $2 ~pos:$loc $5 }
| LCUR record RCUR { $2 ~pos:$loc (mk ~pos:$loc (Tuple [])) }
| LCUR RCUR { mk ~pos:$loc (Tuple []) }
| expr QUESTION DOT invoke { mk_invoke ~pos:$loc ~default:(mk ~pos:$loc Null) $1 $4 }
Expand Down Expand Up @@ -428,7 +431,15 @@ meth_pattern_list:
record_pattern:
| LCUR meth_pattern_list RCUR { $2 }

meth_spread_list:
| DOTDOTDOT VAR { Some (PVar [$2]), [] }
| meth_pattern_el COMMA meth_spread_list { fst $3, $1::(snd $3) }

record_spread_pattern:
| LCUR meth_spread_list RCUR { $2 }

meth_pattern:
| record_spread_pattern { PMeth $1 }
| record_pattern { PMeth (None, $1) }
| VAR DOT record_pattern { PMeth (Some (PVar [$1]), $3) }
| UNDERSCORE DOT record_pattern { PMeth (Some (PVar ["_"]), $3) }
Expand Down
2 changes: 0 additions & 2 deletions src/lang/term.ml
Original file line number Diff line number Diff line change
Expand Up @@ -532,8 +532,6 @@ end

module MkAbstract (Def : AbstractDef) = struct
module T = Type.Ground.Make (struct
type t

let name = Def.name
end)

Expand Down
8 changes: 8 additions & 0 deletions src/lang/typechecking.ml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ let rec type_of_pat ~level ~pos = function
| None -> ([], Type.make ?pos (Type.Tuple []))
| Some pat -> type_of_pat ~level ~pos pat
in
Typing.(
ty
<: List.fold_left
(fun ty (label, _) ->
Type.meth ~optional:true label
([], Type.make ?pos Ground_type.never)
ty)
(Type.var ~level ?pos ()) l);
let env, ty =
List.fold_left
(fun (env, ty) (lbl, p) ->
Expand Down
16 changes: 6 additions & 10 deletions src/lang/types/ground_type.ml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
*****************************************************************************)

module type Spec = sig
type t

val name : string
end

Expand Down Expand Up @@ -69,34 +67,32 @@ module Make (S : Spec) = struct
end

module Int = Make (struct
type t = int

let name = "int"
end)

let int = Int.descr

module Float = Make (struct
type t = float

let name = "float"
end)

let float = Float.descr

module String = Make (struct
type t = string

let name = "string"
end)

let string = String.descr

module Bool = Make (struct
type t = bool

let name = "bool"
end)

let bool = Bool.descr

module Never = Make (struct
let name = "never"
end)

let never = Never.descr
let is_ground v = List.mem v !types
6 changes: 4 additions & 2 deletions src/lang/types/ground_type.mli
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
*****************************************************************************)

module type Spec = sig
type t

val name : string
end

Expand All @@ -48,4 +46,8 @@ val string : Type_base.descr
module Bool : Custom

val bool : Type_base.descr

module Never : Custom

val never : Type_base.descr
val is_ground : Type_base.custom -> bool
37 changes: 37 additions & 0 deletions tests/language/record.liq
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,43 @@ def f() =
test.equals(f({ foo = (fun (_) -> { gni = 2 } ) }), 2)
test.equals(f(456.{ foo = (fun (_) -> true.{ gni = 2 } ) }), 2)

# Spread patterns:
x = { foo = 123, gni = "aabb" }
let { foo, ...y } = x
test.equals(foo, 123)
test.equals(y.gni, "aabb")
test.equals(y, ())
test.equals(y?.foo, null())

x = 1.{ foo = 123, gni = "aabb" }
let { foo, ...y } = x
test.equals(foo, 123)
test.equals(y.gni, "aabb")
test.equals(y, 1)
test.equals(y?.foo, null())

x = 1.{ foo = 123, gni = "aabb" }
let y.{ foo } = x
test.equals(foo, 123)
test.equals(y.gni, "aabb")
test.equals(y, 1)
test.equals(y?.foo, null())

x = {foo = 3.14}.{ foo = 123, gni = "aabb" }
let { foo, ...y } = x
test.equals(foo, 123)
test.equals(y.gni, "aabb")
test.equals(y, ())
test.equals(y?.foo, null())

x = { foo = 123, gni = "aabb" }
y = { bla = 3.14, ...x }
test.equals(y, { foo = 123, gni = "aabb", bla = 3.14 })

x = 1.{ foo = 123, gni = "aabb" }
y = { bla = 3.14, ...x }
test.equals(y, 1.{ foo = 123, gni = "aabb", bla = 3.14 })

test.pass()
end

Expand Down
8 changes: 8 additions & 0 deletions tests/language/type_errors.liq
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,14 @@ def f() =
correct("fun (x) -> x.foo.gni?.bla(1,2,3).blo")
correct("fun (x) -> x?.foo(123).gni ?? null()")

section("PATTERNS")
incorrect("
def f(x) =
let y.{foo} = x
y.foo + 1
end
")

test.pass()
end

Expand Down

0 comments on commit a7d78d2

Please sign in to comment.