mlscript icon indicating copy to clipboard operation
mlscript copied to clipboard

Support argument list and array splices `...xs`

Open LPTK opened this issue 3 years ago • 6 comments
trafficstars

Support typing and type inference for argument list and array splices.

Examples: f(a, b, ...c, d, ...e) and [a, b, ...c, d, ...e].

Type inference should do its best to infer the most precise types, but it may sometimes hit ambiguities, such as when constraining (a, b) <: (...?s, ...?t). In such cases, reporting a type error will be fine, prompting users to write a type annotation in order to remove the ambiguity.

LPTK avatar Apr 21 '22 09:04 LPTK

The way I'm thinking to represent that is by using a new type form:

case class Splice(elems: Ls[SimpleType])(val prov: TypeProvenance) extends BaseType

For example, (a, b, ...c, d) would be represented as Splice(TupleType(a :: b :: Nil), c, TupleType(d :: Nil)).

A complication will arise when computing the Conjunct normal form of two such splices. For now it's fine to raise a type error when this happens. (Eventually, we may have to add a BaseType form for arbitrary intersections of splices...)

LPTK avatar Apr 21 '22 11:04 LPTK

I don't quite understand this part: Splice(TupleType(a :: b :: Nil), c, TupleType(d :: Nil)), is using TupleType here purely for convenience? I wonder if it would be cleaner to use something like Splice(Left(a), Left(b), Right(c), Left(d)) @_@

Meowcolm024 avatar Apr 22 '22 06:04 Meowcolm024

Oh yeah you're actually right, my bad. Better use a clean Either here rather than subtypes. My example actually does not make sense (c could later also turn out to be a TupleType, which would mean we'd represent the wrong splice).

LPTK avatar Apr 22 '22 08:04 LPTK

Basically what I found that in TypeScript:

  1. in functions: the rest argument ...rest: any[], can only be the last argument
  2. in arrays: there can only exist 1 rest element

So, if we want to follow TypeScript, the SpliceType could be:

case class SpliceType(left: Ls[SimpleType], rep: Option[SimpleType], right: Ls[SimpleType])(val prov: TypeProvenance) extends BaseType

We can also continue with the original one and reject the ones that are ambiguous... I wonder what to do next @_@

Meowcolm024 avatar May 02 '22 12:05 Meowcolm024

1. in functions: the rest argument ...rest: any[], can only be the last argument

I don't think that's true. Here's an example valid JS program:

function myFunction(v, w, x, y, z) { }
let args = [0, 1];
myFunction(-1, ...args, 2, ...[3]);

The TS version needs some more type annotations, but also works:

function myFunction(v: number, w: number, x: number, y: number, z: number) { }
let args: [0, 1] = [0, 1];
myFunction(-1, ...args, 2, ...([3] as [3]));

2. in arrays: there can only exist 1 rest element

I don't think that's true either. Here's an example valid TS program:

let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];

arr1 = [...arr2, ...arr1];

Maybe here you're talking about patterns and parameter lists, which should indeed have such restrictions. We can add a check for this restriction later. In any case, the internal type representations should assume the general form.

LPTK avatar May 02 '22 14:05 LPTK

Oh yes, I think I'm talking patterns when defining functions and type annotations 😂 Because we cannot do the following:

let ys: [number, number, ...number[], string, ...boolean[]]
function fx(a: string, ...b: string[], c: number) {}
function fy(a: string, ...b: string[], ...c: number[]) {}

So only patterns have restrictions, but spreading lists is not restricted...

Meowcolm024 avatar May 02 '22 14:05 Meowcolm024