Skip to content

docs: improve slow types page #452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 29, 2024
Merged
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
145 changes: 130 additions & 15 deletions frontend/docs/about-slow-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@ classes, interfaces, or variables, or type aliases, that are themselves or
reference "slow types". "Slow types" are types that are not explicitly written,
or are so complex that they require extensive inference to be understood.

This inference is too costly to be performed by JSR for these features, so these
kinds of types are not supported in the public API.

This inference requires full type checking of the TypeScript source, which is
not feasible to do in JSR because it would require running the TypeScript
compiler. It is not feasible because `tsc` does not produce stable type
information over time and is too slow to run on every package in the registry on
a regular basis
([read more in this issue](https://github.com/jsr-io/jsr/issues/444#issuecomment-2079772908)).
Because of this, these kinds of types are not supported in the public API.

<!-- https://github.com/dprint/dprint-plugin-markdown/issues/98 -->
<!--deno-fmt-ignore-start-->
<!--https://github.com/dprint/dprint-plugin-markdown/issues/98-->
> :warning: If JSR discovers "slow types" in a package, certain features will
> either not work or degrade in quality. These are:
>
Expand All @@ -33,9 +38,9 @@ kinds of types are not supported in the public API.

## What are slow types?

"Slow types" occur when a function, variable, or interface is exported from a
package, and its type is not explicitly written, or is so complex that it cannot
be simply inferred.
"Slow types" occur when a function, class, const declaration, or let declaration
is exported from a package, and its type is not explicitly written or the type
is more complex that what can [be simply inferred](#simple-inference).

Some examples of "slow types":

Expand All @@ -57,17 +62,32 @@ export class MyClass {
}
```

Slow types that are _internal_ to a package (i.e. are not exported) are not
problematic for JSR and consumers of your package.
Slow types that are _internal_ to a package (i.e. are not exported from a file
that is in the `exports` of the package manifest) are not problematic for JSR
and consumers of your package. So, if you have a slow type that is not exported,
you can keep it as is:

```ts
export add(a: number, b: number): number {
return addInternal(a, b);
}

// It is ok to not explicitly write the return type of this function, because it
// is not exported (it is internal to the package).
function addInternal(a: number, b: number) {
return a + b;
}
```

## TypeScript restrictions

This section lists out all of the restrictions that the "no slow types" policy
imposes on TypeScript code:

1. All exported functions, classes, variables, and types must have explicit
types. For example, functions should have an explicit return type, and
classes should have explicit types for their properties.
1. All exported functions, classes, and variables must have explicit types. For
example, functions should have an explicit return type, and classes should
have explicit types for their properties, and constants should have explicit
type annotations.

1. Module augmentation and global augmentation must not be used. This means that
packages cannot use `declare global`, `declare module`, or
Expand Down Expand Up @@ -112,6 +132,13 @@ Classes should have explicit types for their properties:
}
```

Constants should have explicit type annotations:

```diff
- export const GLOBAL_ID = crypto.randomUUID();
+ export const GLOBAL_ID: string = crypto.randomUUID();
```

### Global augmentation

Module augmentation and global augmentation must not be used. This means that
Expand Down Expand Up @@ -151,9 +178,10 @@ Use ESM syntax instead:

### Types must be simply inferred or explicit

All types in exported functions, classes, variables, and types must be simply
inferred or explicit. If an expression is too complex to be inferred, it's type
should be explicitly assigned to an intermediate type.
All types in exported functions, classes, variables, and types must be
[simply inferred](#simple-inference) or explicit. If an expression is too
complex to be inferred, it's type should be explicitly assigned to an
intermediate type.

For example, in the following case the type of the default export is too complex
to be inferred, so it must be explicitly declared using an intermediate type:
Expand Down Expand Up @@ -228,3 +256,90 @@ allowed.

+ type MyPrivateMember = string;
```

## Simple inference

In a few cases, JSR can infer a type without you needing to specify it
explicitly. These cases are called "simple inference". Types that can be simply
inferred are not considered "slow types".

In general, simple inference is possible if a symbol is does not reference other
symbols. It is also not possible if the TypeScript compiler would perform a type
widening or narrowing operation on the type (for example arrays containing
different shapes of object literals).

Simple inference is possible in two positions:

1. The return type of an arrow function, if the function body is a single simple
expression and not a block.
2. The type of a variable (const or let declaration) or property that is
initialized with a simple expression.

```ts
export const foo = 5; // The type of `foo` is `number`.
```

```ts
export const bar = () => 5; // The type of `bar` is `() => number`.
```

```ts
class MyClass {
prop = 5; // The type of `prop` is `number`.
}
```

This inference can only be performed for a select few simple expressions. These
are:

1. Number literals.
```ts
5;
1.5;
```
2. String literals.
```ts,
"hello";
// Template strings are not supported.
```
3. Boolean literals.
```ts
true;
false;
```
4. `null` and `undefined`.
```ts
null;
undefined;
```
5. BigInt literals.
```ts
5n;
```
6. `as T` assertions
```ts
foo() as MyType; // The type is `MyType`.
```
7. `Symbol()` and `Symbol.for()` expressions.
```ts
Symbol("foo");
Symbol.for("foo");
```
8. Regular expressions.
```ts
/foo/;
```
9. Array literals with simple expressions as properties (excluding object
literals).
```ts
[1, 2, 3];
```
10. Object literals with simple expressions as properties.
```ts
{ foo: 5, bar: "hello" };
```
11. Functions or arrow functions that are fully annotated (i.e. have all
parameters and the return type annotated or simply inferred).
```ts
const x = (a: number, b: number): number => a + b;
```
Loading