Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/stdlib-variance.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Iterator and generator types are covariant in their yield and return types and contravariant in their resume type, `Array.arrayLike` is covariant, and `taggedTemplate` is contravariant in its parameters and covariant in its output.
3 changes: 2 additions & 1 deletion compiler/ml/predef.ml
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,8 @@ let common_initial_env add_type add_extension empty_env =
decl_abstr with
type_params = [tvar1; tvar2];
type_arity = 2;
type_variance = [Variance.full; Variance.full];
type_variance =
[Variance.conjugate Variance.covariant; Variance.covariant];
}
in

Expand Down
2 changes: 1 addition & 1 deletion packages/@rescript/runtime/Stdlib_Array.res
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
type t<'a> = array<'a>
type arrayLike<'a>
type arrayLike<+'a>

@new external makeUninitializedUnsafe: int => array<'a> = "Array"
@set external truncateToLengthUnsafe: (array<'a>, int) => unit = "length"
Expand Down
2 changes: 1 addition & 1 deletion packages/@rescript/runtime/Stdlib_Array.resi
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Type representing an array of value `'a`.
*/
type t<'a> = array<'a>

type arrayLike<'a>
type arrayLike<+'a>

/**
`asIterable(array)` views `array` as an `Iterable.t`.
Expand Down
2 changes: 1 addition & 1 deletion packages/@rescript/runtime/Stdlib_AsyncGenerator.res
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type t<'yield, 'return, 'next> = Stdlib_AsyncIterable.t<'yield>
type t<+'yield, +'return, -'next> = Stdlib_AsyncIterable.t<'yield>

external asAsyncIterable: t<'yield, 'return, 'next> => Stdlib_AsyncIterable.t<'yield> = "%identity"
external asAsyncIterableIterator: t<'yield, 'return, 'next> => Stdlib_AsyncIterableIterator.t<
Expand Down
2 changes: 1 addition & 1 deletion packages/@rescript/runtime/Stdlib_AsyncGenerator.resi
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ See [`AsyncGenerator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/R
/**
Type representing an async generator object.
*/
type t<'yield, 'return, 'next> = private Stdlib_AsyncIterable.t<'yield>
type t<+'yield, +'return, -'next> = private Stdlib_AsyncIterable.t<'yield>

/**
`asAsyncIterable(generator)` views `generator` as an `AsyncIterable.t`.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type t<'yield, 'return, 'next> = Stdlib_AsyncIterable.t<'yield>
type t<+'yield, +'return, -'next> = Stdlib_AsyncIterable.t<'yield>

let make: (unit => promise<Stdlib_Iterator.result<'yield, 'return>>) => t<
'yield,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ See [`AsyncIterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Re
/**
Type representing an async iterable iterator.
*/
type t<'yield, 'return, 'next> = private Stdlib_AsyncIterable.t<'yield>
type t<+'yield, +'return, -'next> = private Stdlib_AsyncIterable.t<'yield>

/**
`make(nextFn)` creates an async iterable iterator from a function returning the next result.
Expand Down
4 changes: 2 additions & 2 deletions packages/@rescript/runtime/Stdlib_AsyncIterator.res
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@notUndefined
type t<'yield, 'return, 'next>
type t<+'yield, +'return, -'next>

type rawResult<'yield, 'return>
type rawResult<+'yield, +'return>

type result<'yield, 'return> = Stdlib_Iterator.result<'yield, 'return>

Expand Down
4 changes: 2 additions & 2 deletions packages/@rescript/runtime/Stdlib_AsyncIterator.resi
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ Type representing an async iterator protocol object.
- `'next` is the type that can be sent back into `next(value)`
*/
@notUndefined
type t<'yield, 'return, 'next>
type t<+'yield, +'return, -'next>

/**
Result returned by `AsyncIterator.next`.
*/
type result<'yield, 'return> = Stdlib_Iterator.result<'yield, 'return>

type rawResult<'yield, 'return>
type rawResult<+'yield, +'return>

/**
Low-level raw JavaScript async iterator result shape.
Expand Down
2 changes: 1 addition & 1 deletion packages/@rescript/runtime/Stdlib_Generator.res
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type t<'yield, 'return, 'next> = Stdlib_Iterable.t<'yield>
type t<+'yield, +'return, -'next> = Stdlib_Iterable.t<'yield>

external asIterator: t<'yield, 'return, 'next> => Stdlib_Iterator.t<'yield, 'return, 'next> =
"%identity"
Expand Down
2 changes: 1 addition & 1 deletion packages/@rescript/runtime/Stdlib_Generator.resi
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ See [`Generator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refere
/**
Type representing a generator object.
*/
type t<'yield, 'return, 'next> = private Stdlib_Iterable.t<'yield>
type t<+'yield, +'return, -'next> = private Stdlib_Iterable.t<'yield>
Comment thread
cknitt marked this conversation as resolved.

/**
`asIterator(generator)` views `generator` as an `Iterator.t`.
Expand Down
2 changes: 1 addition & 1 deletion packages/@rescript/runtime/Stdlib_IterableIterator.res
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type t<'yield, 'return, 'next> = Stdlib_Iterable.t<'yield>
type t<+'yield, +'return, -'next> = Stdlib_Iterable.t<'yield>

let make: (unit => Stdlib_Iterator.result<'yield, 'return>) => t<
'yield,
Expand Down
2 changes: 1 addition & 1 deletion packages/@rescript/runtime/Stdlib_IterableIterator.resi
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See [Iteration protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScrip
/**
Type representing an iterable iterator.
*/
type t<'yield, 'return, 'next> = private Stdlib_Iterable.t<'yield>
type t<+'yield, +'return, -'next> = private Stdlib_Iterable.t<'yield>

/**
`make(nextFn)` creates an iterable iterator from a function returning the next result.
Expand Down
4 changes: 2 additions & 2 deletions packages/@rescript/runtime/Stdlib_Iterator.res
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@notUndefined
type t<'yield, 'return, 'next>
type t<+'yield, +'return, -'next>

type rawResult<'yield, 'return>
type rawResult<+'yield, +'return>

@tag("done")
type result<'yield, 'return> =
Expand Down
4 changes: 2 additions & 2 deletions packages/@rescript/runtime/Stdlib_Iterator.resi
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Type representing an iterator protocol object.
- `'next` is the type that can be sent back into `next(value)`
*/
@notUndefined
type t<'yield, 'return, 'next>
type t<+'yield, +'return, -'next>

/**
Result returned by `Iterator.next`.
Expand All @@ -25,7 +25,7 @@ type result<'yield, 'return> =
| @as(false) Yield({value: 'yield})
| @as(true) Return({value: 'return})

type rawResult<'yield, 'return>
type rawResult<+'yield, +'return>

/**
Low-level raw JavaScript iterator result shape.
Expand Down
2 changes: 1 addition & 1 deletion packages/@rescript/runtime/Stdlib_IteratorObject.res
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type t<'yield, 'return, 'next> = Stdlib_Iterable.t<'yield>
type t<+'yield, +'return, -'next> = Stdlib_Iterable.t<'yield>

type result<'yield, 'return> = Stdlib_Iterator.result<'yield, 'return>

Expand Down
2 changes: 1 addition & 1 deletion packages/@rescript/runtime/Stdlib_IteratorObject.resi
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ See [`Iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Referen
/**
Type representing an `IteratorObject` instance.
*/
type t<'yield, 'return, 'next> = private Stdlib_Iterable.t<'yield>
type t<+'yield, +'return, -'next> = private Stdlib_Iterable.t<'yield>

/**
Result returned by `IteratorObject.next`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

We've found a bug for you!
/.../fixtures/GeneratorResumeTypeWidening.res:2:51-88

1 │ // A generator resumed with `[#X]` can't be resumed with `[#X | #Y]`.
2 │ let widen = (g: Generator.t<int, unit, [#X]>) => (g :> Generator.t<int, 
│ unit, [#X | #Y]>)
3 │

Type Generator.t<int, unit, [#X]> = Generator.t<int, unit, [#X]>
is not a subtype of
Generator.t<int, unit, [#X | #Y]> = Generator.t<int, unit, [#X | #Y]>

The second polymorphic variant is closed and doesn't include the constructor: #Y.

Possible solutions:
- Either make the second variant open so it can accept additional constructors. To do this, make sure the type starts with [> instead of [
- Or add the missing constructor to it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// A generator resumed with `[#X]` can't be resumed with `[#X | #Y]`.
let widen = (g: Generator.t<int, unit, [#X]>) => (g :> Generator.t<int, unit, [#X | #Y]>)
107 changes: 107 additions & 0 deletions tests/tests/src/stdlib_variance_test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Generated by ReScript, PLEASE EDIT WITH CARE

import * as Mocha from "mocha";
import * as Test_utils from "./test_utils.mjs";
import * as Stdlib_TaggedTemplate from "@rescript/runtime/lib/es6/Stdlib_TaggedTemplate.mjs";

function widenIterator(i) {
return i;
}

function widenAsyncIterator(i) {
return i;
}

function widenRawResult(r) {
return r;
}

function widenAsyncRawResult(r) {
return r;
}

function widenGenerator(g) {
return g;
}

function widenAsyncGenerator(g) {
return g;
}

function widenIterableIterator(i) {
return i;
}

function widenAsyncIterableIterator(i) {
return i;
}

function widenIteratorObject(i) {
return i;
}

function widenArrayLike(a) {
return a;
}

function widenTaggedTemplate(t) {
return t;
}

function fail(param) {
return ((function* () {})());
}

function map(m, param) {
return m;
}

let Effect = {
fail: fail,
map: map
};

let failing = fail("A");

let letters = (function* () {
yield "A"
});

let joinTag = Stdlib_TaggedTemplate.make((strings, params) => strings.join("|") + ":" + params.map(p => p).join(","));

Mocha.describe("Stdlib_variance_test", () => {
Mocha.test("a widened generator yields the same values", () => {
let widened = letters();
let seen = [];
for (let x of widened) {
seen.push(x);
}
Test_utils.eq("File \"stdlib_variance_test.res\", line 64, characters 7-14", ["A"], seen);
});
Mocha.test("a tag accepting more parameters is used where fewer are expected", () => Test_utils.eq("File \"stdlib_variance_test.res\", line 69, characters 7-14", "a|b:x", joinTag`a${"x"}b`));
});

let useWithB = failing;

let useWithC = failing;

export {
widenIterator,
widenAsyncIterator,
widenRawResult,
widenAsyncRawResult,
widenGenerator,
widenAsyncGenerator,
widenIterableIterator,
widenAsyncIterableIterator,
widenIteratorObject,
widenArrayLike,
widenTaggedTemplate,
Effect,
failing,
useWithB,
useWithC,
letters,
joinTag,
}
/* failing Not a pure module */
71 changes: 71 additions & 0 deletions tests/tests/src/stdlib_variance_test.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
open Mocha
open Test_utils

// Iterator-like types are covariant in their yield and return types and
// contravariant in their resume type, so these coercions type-check.
let widenIterator = (i: Iterator.t<[#A], [#R], [#X | #Y]>) =>
(i :> Iterator.t<[#A | #B], [#R | #S], [#X]>)
let widenAsyncIterator = (i: AsyncIterator.t<[#A], [#R], [#X | #Y]>) =>
(i :> AsyncIterator.t<[#A | #B], [#R | #S], [#X]>)
let widenRawResult = (r: Iterator.rawResult<[#A], [#R]>) =>
(r :> Iterator.rawResult<[#A | #B], [#R | #S]>)
let widenAsyncRawResult = (r: AsyncIterator.rawResult<[#A], [#R]>) =>
(r :> AsyncIterator.rawResult<[#A | #B], [#R | #S]>)
let widenGenerator = (g: Generator.t<[#A], [#R], [#X | #Y]>) =>
(g :> Generator.t<[#A | #B], [#R | #S], [#X]>)
let widenAsyncGenerator = (g: AsyncGenerator.t<[#A], [#R], [#X | #Y]>) =>
(g :> AsyncGenerator.t<[#A | #B], [#R | #S], [#X]>)
let widenIterableIterator = (i: IterableIterator.t<[#A], [#R], [#X | #Y]>) =>
(i :> IterableIterator.t<[#A | #B], [#R | #S], [#X]>)
let widenAsyncIterableIterator = (i: AsyncIterableIterator.t<[#A], [#R], [#X | #Y]>) =>
(i :> AsyncIterableIterator.t<[#A | #B], [#R | #S], [#X]>)
let widenIteratorObject = (i: IteratorObject.t<[#A], [#R], [#X | #Y]>) =>
(i :> IteratorObject.t<[#A | #B], [#R | #S], [#X]>)
let widenArrayLike = (a: Array.arrayLike<[#A]>) => (a :> Array.arrayLike<[#A | #B]>)

// A tag is contravariant in its parameters and covariant in its output.
let widenTaggedTemplate = (t: taggedTemplate<[#A | #B], [#R]>) =>
(t :> taggedTemplate<[#A], [#R | #S]>)

// A library type defined as an alias of `Generator.t` stays polymorphic under
// the relaxed value restriction, so one value can be used at several types.
module Effect: {
type step<+'e>
type t<'a, 'e> = Generator.t<step<'e>, 'a, unknown>
let fail: 'e => t<'a, 'e>
let map: (t<'a, 'e>, 'a => 'b) => t<'b, 'e>
} = {
type step<+'e>
type t<'a, 'e> = Generator.t<step<'e>, 'a, unknown>
let fail = _ => %raw(`(function* () {})()`)
let map = (m, _) => Obj.magic(m)
}

let failing = Effect.fail(#A)->Effect.map(x => x + 1)
let useWithB: Effect.t<int, [#A | #B]> = failing
let useWithC: Effect.t<int, [#A | #C]> = failing

let letters: unit => Generator.t<[#A], unit, unit> = %raw(`function* () {
yield "A"
}`)

let joinTag: taggedTemplate<[#x | #y], string> = TaggedTemplate.make((
strings,
params: array<[#x | #y]>,
) => strings->Array.join("|") ++ ":" ++ params->Array.map(p => (p :> string))->Array.join(","))

describe(__MODULE__, () => {
test("a widened generator yields the same values", () => {
let widened = (letters() :> Generator.t<[#A | #B], unit, unit>)
let seen = []
for x of widened {
seen->Array.push(x)
}
eq(__LOC__, [#A], seen)
})

test("a tag accepting more parameters is used where fewer are expected", () => {
let onlyX = (joinTag :> taggedTemplate<[#x], string>)
eq(__LOC__, "a|b:x", onlyX`a${#x}b`)
})
})
Loading