See ./README.md for details about how specification.md
works
const x: number = 2
const y: string = 2
const z: object = 4
- Type 2 is not assignable to type string
- Type 4 is not assignable to type object
let x: number = 3
x = "hello world"
- Type "hello world" is not assignable to type number
const a = 3
const b: string = a
- Type 3 is not assignable to type string
let a = 2
a = "hello world"
let b: boolean = a
- Type "hello world" is not assignable to type boolean
const a = c
- Could not find variable 'c' in scope
a = 3;
let a = 2;
- Cannot assign to 'a' before declaration
doesNotExist = 4;
- Cannot assign to unknown variable 'doesNotExist'
const a = 2
{
const a = 3;
a satisfies 3;
}
a satisfies 2;
const a = 3;
- Cannot redeclare variable 'a'
TODO maybe should test loops, functions, function parameters etc...
const a = 2
{
const a = 3;
a satisfies 2;
}
- Expected 2, found 3
Might be a usage warning at some point
let b;
b satisfies string;
- Expected string, found undefined
let my_obj = { a: 3 }
const a = my_obj.a
const b = my_obj.b
- No property 'b' on { a: 3 }
const my_obj = { a: 2 }
const three: 3 = my_obj.a
- Type 2 is not assignable to type 3
let my_obj = { a: 3 }
my_obj.a = 4
let b: 3 = my_obj.a
- Type 4 is not assignable to type 3
const my_obj: { a: number } = { a: 2 }
my_obj.a = "hello world"
- Type "hello world" does not meet property constraint number
const my_obj: { b: 3 } = { a: 2 }
- Type { a: 2 } is not assignable to type { b: 3 }
let global = 0;
const object = {
// This getter has an impure side effect
get getValue() {
return ++global
},
}
object.getValue satisfies string
object.getValue satisfies boolean
- Expected string, found 1
- Expected boolean, found 2
const obj1 = { a: 2, b: 3 };
const obj2 = { b: 4, ...obj1, a: 6 };
obj2.b satisfies 100;
obj2.a satisfies boolean;
- Expected 100, found 3
- Expected boolean, found 6
const obj = { a: 2 }
function setProperty(key: string, value) {
obj[key] = value;
}
setProperty("b", 6)
obj satisfies string;
- Expected string, found { a: 2, b: 6 }
const x = { a: 2, b: 3 }
delete x.b;
const b = x.b;
- No property 'b' on { a: 2 }
const x: 4 = 2 + 3
const y: 6 = 2 * 3
const z: 8 = (2 * 3) - 2
- Type 5 is not assignable to type 4
- Type 4 is not assignable to type 8
const x: 2 = 2 & 3
const y: 6 = 2 ^ 7
const z: 14 = 8 | 4
- Type 5 is not assignable to type 6
- Type 12 is not assignable to type 14
const x: 2 = 3 && 2
const y: 6 = 3 && false
const z: false = true || 4
Lot of "Expression is always true" here
- Expression is always true
- Expression is always true
- Type false is not assignable to type 6
- Expression is always true
- Type true is not assignable to type false
(4 === 2) satisfies true;
(4 !== 2) satisfies string;
- Expected true, found false
- Expected string, found true
(Math.PI > 3) satisfies true;
(4 < 2) satisfies true;
(4 > 2) satisfies number;
(6 >= 2) satisfies string;
(6 <= 2) satisfies 5;
- Expected true, found false
- Expected number, found true
- Expected string, found true
- Expected 5, found false
"hi".toUpperCase() satisfies number
- Expected number, found "HI"
Math.cos(0) satisfies 0
Math.sqrt(16) satisfies 1
Math.floor(723.22) satisfies 2;
- Expected 0, found 1
- Expected 1, found 4
- Expected 2, found 723
let a = 5, b = 6;
a++;
a satisfies 4;
b *= 4;
b satisfies 23;
- Expected 4, found 6
- Expected 23, found 24
("something"[2]) satisfies number;
- Expected number, found "m"
function func(a: number) {
a satisfies string
}
- Expected string, found number
Thanks to #132
function outer(a: number) {
function inner(b: string = Math.floor(a)) {
}
}
- Cannot use a default value of type number for parameter of type string
function func(): string {
return 2
}
- Cannot return 2 because the function is expected to return string
function func() {
return 2
}
func satisfies () => string
- Expected () => string, found () => 2
function add_property(obj: { prop: number }) {
obj.prop = 2;
(obj.prop satisfies 4);
}
Not number
- Expected 4, found 2
function func(a: string, b: number): boolean {
return true
}
func satisfies (a: string, b: number) => boolean;
func satisfies (a: string, b: number) => string;
func satisfies (a: number, b: number) => boolean;
- Expected (a: string, b: number) => string, found (a: string, b: number) => boolean
- Expected (a: number, b: number) => boolean, found (a: string, b: number) => boolean
function myThrow() {
throw "err!"
}
myThrow satisfies string;
- Expected string, found () => never
function getSecond1<T, U>(p1: T, p2: U): U {
return p1
}
function getSecond2<T, U>(p1: T, p2: U): U {
return p2
}
- Cannot return T because the function is expected to return U
function setFirst1<T, U>(a: T, b: U) {
const a2: T = a;
}
function setFirst2<T, U>(a: T, b: U) {
const a2: U = a;
}
- Type T is not assignable to type U
function createObject1<T, U>(a: T, b: U): { a: T, b: U } {
return { a, b }
}
function createObject2<T, U>(a: T, b: U): { a: U, b: U } {
return { a, b }
}
- Cannot return { a: T, b: U } because the function is expected to return { a: U, b: U }
Technically works with inference but this method should be less overhead + produce better positioned errors
const x: (a: string) => number = a => a.to;
- No property 'to' on string
function map(a: (a: number) => number) {}
// No annotation on `a`. But error comes from body
// (rather than parameter assignment)
map(a => a.t)
- No property 't' on number
function alterParameter(a: number, b: { prop: string }) {
a = 2;
a = "hi";
b.prop = 3;
b.prop = "hello";
// Observed. TODO disabled because of possible impure (getters etc)
// b.prop satisfies "hello";
}
Assigning straight to
a
might be disallowed by an option in the future. Right now it is allowed by JavaScript and so is allowed
- Type "hi" is not assignable to type number
- Type 3 does not meet property constraint string
aka rest parameter type
function variadic(...r: string[]) {
r satisfies boolean;
}
- Expected boolean, found Array<string>
function myFunction({ a }: { a: number }) {
a satisfies boolean;
return a
}
myFunction({ a: 6 }) satisfies string;
- Expected boolean, found number
- Expected string, found 6
While could use the returned type (as done in the second example). Using the annotation prevents other code breaking if the body changes As shown later, this doesn't affect what is returned if called
function getNumber1(): number {
return 4
}
function getNumber2() {
return 6
}
getNumber1 satisfies () => 4;
getNumber2 satisfies () => 6;
getNumber1() satisfies 4;
getNumber2() satisfies 6;
- Expected () => 4, found () => number
function func(a: number) {}
func("hello world")
- Argument of type "hello world" is not assignable to parameter of type number
function id(a) {
return a
}
const d: 3 = id(2)
- Type 2 is not assignable to type 3
function getA(obj: { a: string }) {
return obj.a
}
const d: 3 = getA({ a: "hi" })
- Type "hi" is not assignable to type 3
function func(p1: number, p2: string) {}
func(4)
- Missing argument
function func(p1: number) {}
func(4, "extra")
- Excess argument
const x = "hi"
x()
- Cannot call type "hi"
function addTwoToResult(func: (n: number) => number) {
return func(4) + 2
}
addTwoToResult((a: number) => a * 4) satisfies 5
- Expected 5, found 18
function call(func: (n: number) => number) {
return func(9)
}
call(Math.sqrt) satisfies 2
- Expected 2, found 3
An example of the generic constructor type (namely call and operation)
function floorPlusB(a: number, b: number) {
return Math.floor(a) + b
}
floorPlusB(100.22, 5) satisfies 8
- Expected 8, found 105
const obj = {
a: 4,
getA(this: { a: any }) {
return this.a
}
}
obj.a = 5;
obj.getA() satisfies 6;
- Expected 6, found 5
function getToUpperCase(s: string) {
return s.toUpperCase
}
getToUpperCase("hi")() satisfies "HEY";
- Expected "HEY", found "HI"
function callToUpperCase(s: string) {
return s.toUpperCase()
}
callToUpperCase("hi") satisfies "HEY";
- Expected "HEY", found "HI"
Thanks to
this
checking in #127
const { toUpperCase } = "hi";
toUpperCase();
- The 'this' context of the function is expected to be string, found undefined
function MyClass(value) {
this.value = value
}
new MyClass("hi").value satisfies "hello"
- Expected "hello", found "hi"
function myRestFunction(...r: string[]) {
return r[0] + r[1]
}
myRestFunction("hello ", "world") satisfies number;
- Expected number, found "hello world"
function withDefault(x: number = 1) {
return x
}
withDefault() satisfies 2;
withDefault(3) satisfies 3;
- Expected 2, found 1
let b: number = 0
function doThing(a = (b += 2)) {
return a
}
doThing("hello");
b satisfies 0;
doThing();
b satisfies 1;
- Expected 1, found 2
Effectively optional parameter with the default value being undefined
function optionally(p?: number) {
p satisfies string;
}
- Expected string, found number | undefined
function optionally(p?: number) {
return p
}
// Fine
optionally() satisfies undefined;
optionally(5) satisfies 5;
optionally("hello world");
- Argument of type "hello world" is not assignable to parameter of type number | undefined
function myTag(static_parts: Array<string>, name: string) {
return static_parts[0] + name
}
const name = "Ben";
myTag`${name}Hello ` satisfies "Hi Ben"
- Expected "Hi Ben", found "Hello Ben"
function doThing(a, b = (a += 2)) {
return a
}
doThing(3) satisfies 2;
doThing(6, 1) satisfies 6;
- Expected 2, found 5
Side effects of functions. Registered internally as
Event
s
let a: number = 0
function func() {
a = 4;
// Important that subsequent reads use the
// new value, not the same free variable
a satisfies 4;
}
func()
let b: 2 = a
- Type 4 is not assignable to type 2
let a: number = 0
function func(c: number) {
a = c
}
func(4)
let b: 2 = a
- Type 4 is not assignable to type 2
const obj: { a: number } = { a: 2 }
function func(value: number) {
obj.a = value
}
obj.a satisfies 2
func(4)
obj.a satisfies 3
- Expected 3, found 4
let a: number = 2
function runFunctionTwice(func: () => void) {
func()
func()
}
a satisfies 2
runFunctionTwice(() => { a++ })
a satisfies string
- Expected string, found 4
let a: number = 2
function runFunctionTwice(func: () => void): number {
func()
const b = a
func()
return b;
}
a satisfies 2
const out = runFunctionTwice(() => { a++ });
a satisfies 4
out satisfies string
- Expected string, found 3
function getX() {
return x
}
getX satisfies () => number;
getX();
let x: number = 5;
- Variable 'x' used before declaration
Not shown in the example but thanks to #69 for adding the position of the error
Solves the common subtyping issue between read and write properties
let myObject: { a: number } = { a: 4 }
function readA(someObject: { a: number | string }) {
return someObject.a;
}
function setAtoString(someObject: { a: number | string }) {
someObject.a = "hi";
}
// Allowed
readA(myObject);
setAtoString({ a: 6 });
setAtoString(myObject);
- Invalid assignment to parameter
Error message could be better. Full diagnostic contains labels with more information
readA
is allowed, which is disallowed in Hegel, but here is allowed to preserve TSC compatibility (and because how structural subtyping is implemented) Not shown in the example but thanks to #69 for adding the position of the error
function getObject(condition: boolean) {
const mainObject = { a: 2 };
const object = condition ? mainObject : { b: 3 };
object.c = 4;
mainObject.c satisfies string;
return mainObject
}
- Expected string, found 4
function add_property(obj: { prop: number }) {
obj.prop += 2;
}
const obj = { prop: 4 };
add_property(obj);
obj.prop satisfies 8;
- Expected 8, found 6
function newObject() {
return { prop: 2 }
}
const a = newObject(), b = newObject();
const c = a;
(a === c) satisfies false;
(a === b) satisfies string;
- Expected false, found true
- Expected string, found false
function kestrel(a) {
return function (_b) {
return a
}
}
kestrel(3)(2) satisfies 4
- Expected 4, found 3
function kestrel2(a) {
return _b => _c => a
}
kestrel2(3)(2)(6) satisfies 4
- Expected 4, found 3
function magicNumber(a: number) {
return {
plusOne() { return a + 1 },
doubled() { return 2 * a }
}
}
const myNumber = magicNumber(4);
// Create a one in between to test they don't have a global state
magicNumber(8).doubled() satisfies 16;
myNumber.plusOne() satisfies 5
myNumber.doubled() satisfies 6
- Expected 6, found 8
function myClosure(a) {
return {
getValue() { return a },
setValue(b) { a = b }
}
}
const value = myClosure(4);
value.getValue() satisfies 4;
value.setValue(10);
value.getValue() satisfies 6
- Expected 6, found 10
function isNegative(x: number) {
return x < 0 ? "negative" : "positive"
}
isNegative(-4) satisfies number
isNegative(4) satisfies boolean
- Expected number, found "negative"
- Expected boolean, found "positive"
let a: number = 0
function conditional(v: string) {
if (v === "value") {
a++
}
}
conditional("x")
a satisfies 2
conditional("value")
a satisfies 3
- Expected 2, found 0
- Expected 3, found 1
declare var value: string;
let a: string | number = 0;
function conditional(v: string) {
if (v === "value") {
a = "hi"
}
}
conditional(value);
a satisfies string;
- Expected string, found "hi" | 0
function print_number(value: number) {
if (value === 0) {
return "zero"
} else if (value === 1) {
return "one"
} else {
return "some number"
}
}
print_number(0) satisfies "zero"
print_number(0) satisfies "some number"
print_number(1) satisfies "ONE"
print_number(100) satisfies "100"
print_number(-1) satisfies "TWO"
- Expected "some number", found "zero"
- Expected "ONE", found "one"
- Expected "100", found "some number"
- Expected "TWO", found "some number"
let a: number = 0
const func = condition => condition || ++a;
func(true);
a satisfies 0;
func(false) satisfies 1;
a satisfies 2;
- Expected 2, found 1
let a = false, b = 4;
a ||= b++;
a satisfies 3;
b ||= (b = 10);
b satisfies string;
a is always assigned ('Expression is always false') and b is always true (so the RHS never runs)
- Expression is always false
- Expected 3, found 4
- Expression is always true
- Expected string, found 5
function func(a: boolean) {
if (a) {
return 2
}
}
func satisfies (a: boolean) => 5;
- Expected (a: boolean) => 5, found (a: boolean) => 2 | undefined
let i = 0;
declare let b: boolean;
if (b) {
i = 1
} else {
i = 2
}
i satisfies string;
- Expected string, found 1 | 2
declare let string: string;
function stringIsHi(s: string) {
if (s === "hi") {
return true
}
return false
}
stringIsHi(string) satisfies number;
- Expected number, found boolean
let a = 1;
let i = 0;
while (i < 5) {
a *= 2;
i++;
}
a satisfies 8;
- Expected 8, found 32
let a = 1;
let i = 0;
while (i++ < 5) {
a *= 2;
}
a satisfies 8;
- Expected 8, found 32
let a = 0;
do {
a++
} while (a < 3)
a satisfies 8;
- Expected 8, found 3
let a: string = "";
for (let i: number = 0; i < 10; i++) {
a = a + i;
}
a satisfies number;
- Expected number, found "0123456789"
declare let i: number;
let a: number = 0;
while (a < i) {
a++;
}
a satisfies string;
- Expected string, found number
Important that type is widened to 'number' (think it is an open poly in this case)
let a: number = 0;
while (a++ < 1_000_000) {}
a satisfies string;
The important part is that it doesn't run the loop. Eventually this might be run in a way that is not calling the assign to variable function that evaluates
a = a + 1
a million times. There also should be per project, per module, per loop configuration
- Expected string, found number
function loop(n: number, c: string) {
let a: string = c;
let i: number = 0;
while (i++ < n) {
a += c
}
return a
}
loop(10, "!") satisfies number;
- Expected number, found "!!!!!!!!!!"
let a = 2;
let i = 0;
while (i++ < 10) {
a *= 2;
if (a > 5) {
break;
}
}
a satisfies 2;
- Expected 2, found 8
let a: number = 0;
let result;
top: while (a++ < 10) {
let b: number = 0;
while (b++ < 10) {
if (a === 3 && b === 2) {
result = a * b;
break top
}
}
}
a satisfies string;
result satisfies boolean;
- Expected string, found 3
- Expected boolean, found 6
With the continue the update to
a
only happens on even runs (5 times)
let a = 2;
let i = 0;
while (i++ < 10) {
if (i % 2) {
continue;
}
a *= 2;
}
a satisfies 2;
- Expected 2, found 64
let properties: string = "";
for (const property in { a: 1, b: 2, c: 3 }) {
properties += property;
}
properties satisfies boolean;
- Expected boolean, found "abc"
TypeScript anonymous object annotations do not guarantee ordering and the subtyping rules allow for the RHS to have more properties than defined
declare const myObject: { a: 1, b: 2, c: 3 };
let properties: string = "";
for (const property in myObject) {
properties += property;
}
properties satisfies boolean;
- Expected boolean, found string
Some of these are built of exiting features. But they are important enough in code for them to have their own usage tests
const x = [1]
x.push("hi")
x[1] satisfies 3
x.length satisfies 4;
- Expected 3, found "hi"
- Expected 4, found 2
const myArray = [6, "hi"]
myArray.pop() satisfies 3;
myArray.length satisfies 1;
- Expected 3, found "hi"
const x: Array<number> = [1]
x.push("hi");
- Argument of type "hi" is not assignable to parameter of type number
TODO other arguments (index and
this
)
[6, 8, 10].map(x => x + 1) satisfies [7, 8, 11];
[1, 2, 3].filter(x => x % 2 === 0) satisfies [2];
- Expected [7, 8, 11], found [7, 9, 11]
TODO other arguments (index and
this
). and poly
[1, 2, 3].find(x => x % 2 === 0) satisfies 4;
// [1, 2, 3].includes(6) satisfies string;
- Expected 4, found 2
Some of these are part of synthesis, rather than checking
interface X {
a: string,
b: boolean
}
const x: X = { a: 2, b: false }
- Type { a: 2, b: false } is not assignable to type X
type MyNumber = number;
type MyObj = { a: string };
"hi" satisfies MyNumber;
4 satisfies MyNumber;
declare let obj: MyObj;
obj.a satisfies string;
- Expected MyNumber, found "hi"
declare const global_number: number
const my_number: string = global_number
- Type number is not assignable to type string
Using functions and interface before their position of declaration in the source
getFive() satisfies 4;
function getFive() {
return 5
}
let x: X = { a: 3 }
interface X {
a: 2
}
- Expected 4, found 5
- Type { a: 3 } is not assignable to type X
RegExp = Regular expression In the future, their definition could be considered and evaluated at runtime
const regexp = /hi/ satisfies string;
- Expected string, found /hi/
undefined satisfies null;
null satisfies undefined;
- Expected null, found undefined
- Expected undefined, found null
(void 2) satisfies string;
- Expected string, found undefined
const name = "Ben";
`Hello ${name}` satisfies "Hi Ben"
- Expected "Hi Ben", found "Hello Ben"
const obj = { a: 2 };
("a" in obj) satisfies string;
("b" in obj) satisfies true;
- Expected string, found true
- Expected true, found false
declare var x: number;
(x * 2) satisfies string
- Expected string, found number
declare var x: number;
(x < 4) satisfies string;
(x === 4) satisfies Math;
- Expected string, found boolean
- Expected Math, found boolean
declare var x: number, y: boolean;
(x && y) satisfies string;
- Expected string, found boolean | number
const y = { ["EZNO".toLowerCase()]: 7 }
y.ezno satisfies 3
y.not_a_key
- Expected 3, found 7
- No property 'not_a_key' on { ezno: 7 }
const x = 2
const y = { x }
y.x satisfies 3
- Expected 3, found 2
const object = { a: 1, b: 2 }
const { a, b } = object
a satisfies 1; b satisfies string;
- Expected string, found 2
const object = { a: { b: { c: 2 } } }
const { a: { b: { c: d } } } = object
d satisfies 1;
- Expected 1, found 2
interface X {
a: string,
b: boolean
}
{
interface X {
c: number
}
const x: X = { a: "field", b: false, c: false }
const y: X = { a: "field", b: false, c: 2 }
}
- Type { a: "field", b: false, c: false } is not assignable to type X
type X = { a: string }
{
interface X {
b: number
}
const x: X = { b: 3 } // Don't require 'a' here <-
const y: X = { b: "NaN" }
}
- Type { b: "NaN" } is not assignable to type X
let first = second;
let second = 2;
- Variable 'second' used before declaration
function func() {}
(typeof 5) satisfies "number";
(typeof "hi") satisfies "string";
(typeof func) satisfies "function";
declare let someNumber: number;
(typeof someNumber) satisfies "function";
- Expected "function", found "number"
s satisfies string;
var s = "hello"
s satisfies number;
- Expected string, found undefined
- Expected number, found "hello"
try {
throw 2
} catch (err) {
err satisfies string
}
- Expected string, found 2
function throwType(a) {
throw a
}
try {
throwType(3)
} catch (err) {
err satisfies string
}
- Expected string, found 3
Thanks to #131
try {
throw 3
} catch (err: string) {
console.log(err)
}
- Cannot catch type string because the try block throws 3
Added in #127
const o = { a: 1, b: { c: 3 } };
let a, b, c;
({
c = o.a++,
b: { c: b = 7 },
a,
} = o);
a satisfies string;
b satisfies boolean;
c satisfies 3;
- Expected string, found 2
- Expected boolean, found 3
- Expected 3, found 1
Position of await is not checked (here is fine because top level await)
Uses external
Promise
const resp = await (fetch("/some-endpoint") satisfies string);
resp.ok satisfies number;
- Expected string, found Promise<Response>
- Expected number, found boolean
class X {
constructor(value) {
this.value = value
}
}
const x = new X(4)
x.value satisfies string
- Expected string, found 4
Thanks to
this
checking added in #127
class X {
method() {
return this;
}
}
const { method } = new X();
method();
- The 'this' context of the function is expected to be X, found undefined
Property keys are synthesised once and their effects run once (as opposed to their value)
let global: number = 0;
class X {
[global++] = "b";
}
global satisfies 0;
(new X)[0] satisfies "a";
(new X, new X);
global satisfies string;
- Expected 0, found 1
- Expected "a", found "b"
- Expected string, found 1
let global: number = 0;
class X {
property = ++global;
}
(new X()).property satisfies string;
(new X()).property satisfies 2;
(new X()).property satisfies boolean;
- Expected string, found 1
- Expected boolean, found 3
Property fields are assigned first
let global: number = 0;
class X {
prop1 = ++global;
constructor() {
this.prop2 = ++global;
}
}
const x = new X();
x.prop1 satisfies string;
x.prop2 satisfies boolean;
- Expected string, found 1
- Expected boolean, found 2
class X {
constructor(value) {
this.value = value
}
getObject(this: { value: any }, b) {
return { a: this.value, b }
}
}
const x = new X(4)
x.getObject(2) satisfies string
- Expected string, found { a: 4, b: 2 }
class X {
a = 2
}
(new X).a satisfies 3
- Expected 3, found 2
class X {
static a = 2
}
X.a satisfies 3
- Expected 3, found 2
Declared same as
let
andconst
const x = new X;
class X { }
- Variable 'X' used before declaration
class BaseClass {
b: boolean = false
}
class Class extends BaseClass {
a: number = 2
}
new Class().b satisfies 5
- Expected 5, found false
class X {
static x = 2;
static {
const property: 4 = ++this.x;
}
}
X.x satisfies 3;
- Type 3 is not assignable to type 4
function doThingWithClass(instance: Class) {
instance.prop satisfies string;
instance.parent_prop satisfies boolean;
instance.method(4);
}
class BaseClass {
parent_prop: number
}
class Class extends BaseClass {
prop: number
method(s: string) {}
}
- Expected string, found number
- Expected boolean, found number
- Argument of type 4 is not assignable to parameter of type string
function doThingWithClass(instance: Class) {
instance.a satisfies number;
instance.b satisfies string;
}
class BaseClass {
b: boolean
}
class Class extends BaseClass {
a: number
}
- Expected string, found boolean
type X = number;
const a: Y = 2;
- Cannot find type Y
TODO maybe should test loops, functions, function parameters etc...
type X = string;
{
type X = number;
const a: X = "hello world";
}
- Type "hello world" is not assignable to type X
type X = number;
const a: X<number> = 2;
- Type 'X' has no generic parameters
type X<T> = T;
2 satisfies X<string>;
- Expected string, found 2
type X<T> = T;
const b: X = 2;
- Type X requires type arguments
function getProp(obj: { prop: 3 } | { prop: 2 }) {
return obj.prop
}
getProp satisfies string
- Expected string, found (obj: { prop: 3 } | { prop: 2 }) => 3 | 2
function getA<T extends { a: string }>(p: T) {
return p.a
}
getA({ p: 2 })
- Argument of type { p: 2 } is not assignable to parameter of type T
I think reasons contains more information
// Perfectly fine
const x: (a: number) => string = (p: string | number) => "hi"
// Bad
const y: (a: number | string) => string = (p: number) => "hi"
- Type (p: number) => "hi" is not assignable to type (a: number | string) => string
I think reasons contains more information
const x: (a: number) => number = p => 4
const y: (a: number) => number = p => "a number"
- Type (p: number) => "a number" is not assignable to type (a: number) => number
This works similarly to undefined except that it accepts any function return type
function runWithCallback(cb: () => void): void {
cb() satisfies string;
return 5;
}
runWithCallback(() => 3)
Here argument is fine. In the body the return type is
any
(inferred constraint, but doesn't matter)
- Expected string, found void
- Cannot return 5 because the function is expected to return void
interface ThePrimitives {
a: number,
b: string,
c: boolean
}
2 satisfies ThePrimitives["b"];
- Expected string, found 2
function getProp<T extends { prop: string, other: string }>(t: T): T["prop"] {
return t.other
}
function getOther<T extends { prop: string, other: string }>(t: T): T["other"] {
return t.other
}
- Cannot return T["other"] because the function is expected to return T["prop"]
function getFirst(array: number[]) {
return array[0]
}
getFirst satisfies boolean;
- Expected boolean, found (array: Array<number>) => number | undefined
function getSecondCharacter(s: string) {
return s[1]
}
getSecondCharacter satisfies boolean;
getSecondCharacter("string") satisfies "b";
- Expected boolean, found (s: string) => string | undefined
- Expected "b", found "t"
Disabled normally, allowed for these tests. Provides TSC compatibility and because narrowing not implemented (including secret feature)
declare let global: any;
5 as boolean;
global satisfies boolean;
(global as string) satisfies number;
- Cannot cast 5 to boolean
- Expected boolean, found any
- Expected number, found string
function or1<T, U>(obj: T | U): U | T { return obj }
function or2(obj: string | number): number | string { return obj }
// Lack of symmetry
function or3(obj: string | number): number { return obj }
- Cannot return string | number because the function is expected to return number
function and1<T, U>(obj: T & U): U & T { return obj }
// Lack of symmetry
function and2<T, U>(obj: T): U & T { return obj }
- Cannot return T because the function is expected to return U & T
function distribute1<T, U, V>(obj: (T | U) & V): (T & V) | (U & V) { return obj }
function distribute2<T, U, V>(obj: V & (T | U)): (T & V) | (U & V) { return obj }
// bad!
function distribute3<T, U, V>(obj: (T | U) & V): (T & U) | (U & V) { return obj }
- Cannot return T & V | U & V because the function is expected to return T & U | U & V
function get(obj: {a: 2} | { b: 3 }) {
return obj.a
}
Cannot read property "a" from { b: 3 }
- No property 'a' on { a: 2 } | { b: 3 }
interface Optional {
a?: "hi"
}
const op1: Optional = {}
const op2: Optional = { a: "hello" }
- Type { a: "hello" } is not assignable to type Optional
type X = 2 & "hi";
type Y = string & number;
- No intersection between types 2 and "hi"
- No intersection between types string and number
interface X {
a: string
}
interface Y {
b: string
}
interface Z extends X, Y {
c: string
}
({ a: "", b: "", c: "hello" }) satisfies Z;
({ a: "", b: 4, c: "hello" }) satisfies Z;
({ c: "hi" }) satisfies Z;
- Expected Z, found { a: "", b: 4, c: "hello" }
- Expected Z, found { c: "hi" }
declare function id<T>(a: T): T;
declare function box<T>(a: T): { item: T };
declare let someNumber: number;
id(someNumber) satisfies string;
box(someNumber) satisfies boolean;
- Expected string, found number
- Expected boolean, found { item: number }
TODO dynamic restriction
type Name = "Ben"
"test" satisfies `Hello ${Name}`;
- Expected "Hello Ben", found "test"
Uses
+
logic behind the scenes
declare function Concat<T extends string, U extends string>(a: T, b: U): `${T}, ${U}`;
Concat("test", "something") satisfies boolean
- Expected boolean, found "test, something"
interface Wrapper<T> {
internal: T
}
const my_wrapped: Wrapper<number> = { internal: "hi" }
- Type { internal: "hi" } is not assignable to type Wrapper<number>
const numbers1: Array<number> = [1, 2, "3"],
numbers2: Array<string> = ["hi", "3"],
numbers3: Array<string> = 4;
- Type [1, 2, "3"] is not assignable to type Array<number>
- Type 4 is not assignable to type Array<string>
function func<T>(a: T) {}
func<number>("hello world")
- Argument of type "hello world" is not assignable to parameter of type number
Too many generics here, doesn't get caught for some reason?
let c: Array<number> = []
function add() {
c.push("hi")
}
- Argument of type "hi" is not assignable to parameter of type number
declare function isNumber<T>(t: T): T extends number ? true : false;
isNumber(5) satisfies true;
isNumber("5") satisfies number;
- Expected number, found false
declare function unwrap<T>(a: T | { item: T }): T;
unwrap({ item: 5 }) satisfies string;
- Expected string, found 5
type WithLabel<T> = { label: string, item: T };
declare function getItem<T>(a: WithLabel<T>): T;
getItem({ label: "item 1", item: 5 }) satisfies string;
- Expected string, found 5
Really want to only have one covariant and one contravariant but want to keep TSC semantics
declare function what<T>(a: T, b: T): T;
what(2, 3) satisfies string;
- Expected string, found 2 | 3
Aka generic property keys
type Record2<K extends string, T> = { [P in K]: T }
declare let myRecord: Record2<"hi", number>;
myRecord.hi satisfies string;
myRecord.hello;
- Expected string, found number
- No property 'hello' on { ["hi"]: number }
type Record2<K extends string, T> = { [P in K]: T }
const x: Record2<"test", boolean> = { no: false },
y: Record2<"test", boolean> = { test: 6 },
z: Record2<"test", boolean> = { test: false };
- Type { no: false } is not assignable to type { ["test"]: boolean }
- Type { test: 6 } is not assignable to type { ["test"]: boolean }
type Record2<K extends string, T> = { [P in K]: T }
declare let obj1: Record2<"hi" | "hello", boolean>;
obj1.hi satisfies boolean;
obj1.hello satisfies boolean;
obj1.bye;
declare let obj2: Record2<string, boolean>;
obj2.fine satisfies boolean;
obj2[2];
- No property 'bye' on { ["hi" | "hello"]: boolean }
- No property '2' on { [string]: boolean }
This is where usage a parameter gets a type via a type (on some variable or parameter somewhere). Aka from above or the usage of the function
Constraint inference is where the parameter gets it from below. Usage of the parameter value
interface MyObject {
a(b: string): any;
}
const obj: MyObject = {
a(b) {
b satisfies number;
}
}
- Expected number, found string
function callFunction<T>(fn: (p: T) => void) {
// ...
}
callFunction<string>(a => {
a satisfies number;
})
- Expected number, found string
const x = [1, 2, 3];
x.map(a => (a satisfies string, 2))
- Expected string, found 1 | 2 | 3
Any references to a annotated variable must be within its LHS type. These test that it carries down to objects.
const obj1 = { a: 5 };
const obj2: { prop: { a: number } } = { prop: obj1 }
obj1.a = 6;
obj1.a = "hello";
- Type "hello" does not meet property constraint number
{
const obj = { a: true, b: false };
const x: { a: boolean } = obj, y: { b: boolean } = obj;
obj.a = "yo";
obj.b = "wassup";
}
{
// and in the same assignment through a cycle
const obj = { a: 2, b: 3 }; obj.c = obj;
const something: { a: number, c: { b: number } } = obj;
obj.a = "hi";
obj.b = "hello";
}
- Type "yo" does not meet property constraint boolean
- Type "wassup" does not meet property constraint boolean
- Type "hi" does not meet property constraint number
- Type "hello" does not meet property constraint number
const obj1 = { a: 5 };
const obj2: { prop: { a: number } } = { prop: obj1 }
obj1.a = 6;
obj1.a = "hello";
- Type "hello" does not meet property constraint number
As would violate any usage of
obj2
interface X {
a: number
b: X
}
const myObject = { a: 2 };
myObject satisfies X;
myObject.b = myObject;
myObject satisfies X;
- Expected X, found { a: 2 }
const x = { a: 3 };
Object.setPrototypeOf(x, { a: 5, b: 2 });
x.a satisfies 3;
x.b satisfies string;
- Expected string, found 2
const x = { a: 3 };
const p = { b: 2 }
Object.setPrototypeOf(x, p);
const p_of_x = Object.getPrototypeOf(x);
// ('a' in p_of_x.a) satisfies false;
(p === p_of_x) satisfies string;
- Expected string, found true
also imports work with and without extensions
import { PI } from "./constants.ts";
import { PI as otherPI, "non identifier" as a } from "./other";
PI satisfies string;
otherPI satisfies boolean;
a satisfies 8;
// in constants.ts
export const PI = 4;
// in other.ts
export const PI = 22 / 7;
const private = 2;
export { private as "non identifier" }
- Expected string, found 4
- Expected boolean, found 3.142857142857143
- Expected 8, found 2
import { PI } from "./constants";
PI += 2;
// in constants.ts
export let PI = 4;
- Cannot assign to constant
import PI from "./pi";
PI satisfies string;
// in pi.ts
export default 4;
- Expected string, found 4
import { MyNumber } from "./types";
2 satisfies MyNumber;
// in types.ts
export type MyNumber = string;
- Expected MyNumber, found 2
import { MyNumber } from "./types";
2 satisfies MyNumber;
MyNumber satisfies boolean;
// in types.ts
export type MyNumber = string;
export const MyNumber = 6;
- Expected MyNumber, found 2
- Expected boolean, found 6
import { counter, incrementCounter } from "./mutable";
counter satisfies string;
incrementCounter();
counter satisfies 3;
incrementCounter();
counter satisfies string;
// in mutable.ts
export let counter = 2;
export function incrementCounter() {
counter++
}
- Expected string, found 2
- Expected string, found 4
import * as the from "./many";
the satisfies string;
// in many.ts
export const a = 2, b = 3, c = 4;
- Expected string, found { a: 2, b: 3, c: 4 }
import { a } from "./two";
console.log(a.prop);
// in one.ts
export const a = 2;
- Cannot find file
import { a } from "./export";
console.log(a.prop);
// in export.ts
export const b = 2;
- a not exported from ./export
import { a } from "./export";
console.log(a.prop);
// in export.ts
export default const;
- Found reserved identifier
import { a } from "./export1";
import { b } from "./export2";
(a === b) satisfies string;
// in export1.ts
export { the as a } from "./base"
// in export2.ts
export { the as b } from "./base"
// in base.ts
export const the = ((4 satisfies 1),3);
- Expected 1, found 4
- Expected string, found true
The fact the
Expected 1, found 4
only occurs once means that the module was only synthesised once
export const x = 2;
x satisfies 3;
- Expected 3, found 2
import { x } from "./exports"
console.log(y)
// in exports.ts
export const x = 2;
const y = "122LH"
- Could not find variable 'y' in scope
Don't take this as permission to do this
import { x } from "./export";
import "./side_effect";
x satisfies number;
// in side_effect.ts
import { x } from "./export";
x satisfies string;
x.b = x.a + 2;
// in export.ts
export const x = { a: 2 };
- Expected string, found { a: 2 }
- Expected number, found { a: 2, b: 4 }
Yay doesn't require type definition to be shipped!!
import { mean_gravity } from "earth";
mean_gravity satisfies 2;
// in node_modules/earth/package.json
{
"main": "constants.js"
}
// in node_modules/earth/constants.js
export const mean_gravity = 9.806;
- Expected 2, found 9.806
This contains new features. Most are WIP
function JSXH(tag_name: string, attributes: any, children: any) {
return { tag_name, attributes, children }
}
const x = <h1 title="Example text">Hello World</h1> satisfies string;
- Expected string, found { tag_name: "h1", attributes: { title: "Example text" }, children: ["Hello World"] }
function x(a /** string */) {
a satisfies number
}
const c /** number */ = "hello"
- Expected number, found string
- Type "hello" is not assignable to type number
function register(a: Literal<string>) {
// ...
}
register("something")
// `document.title` is an unknown string, non-literal
register(document.title)
- Argument of type string is not assignable to parameter of type Literal<string>