?ue/p1-90`This
The system is deliberately close to the value-type discipline of systems languages. It adds sized
This document is the normative specification. It is written against the
The specification is under active construction. The clauses below establish the
This clause and its subclauses define the runtime type system as a
Each addition is expressed as an insertion into or revision of the edition of ECMAScript that
This document is a diff. It is written against ECMA-262, 16th edition (ES2025), which is the pinned edition, and it should be re-pinned and re-diffed at each subsequent edition rather than left to drift. Within a grammar block, a production shown with inserted text is that edition's production with the marked additions; a production shown entirely as an insertion is new to this
The design is the collection of documents in the ecmascript-types repository: a README specifying the core and a
Three conventions govern the semantics. The relations of the type system,
The editorial state of this document is worth stating plainly. Its structure, cross-references, and grammar are complete and it builds clean, and every obligation the
This clause defines the machinery the rest of the specification builds on: the representation of a type, the structural identity that interns types into unique values, the subtyping and assignability relations, the static type of an expression, compile-time evaluation of a type expression, and the reporting of type errors. It introduces no user-facing syntax; the productions and library that use this machinery are added by later clauses.
ECMAScript defines runtime behaviour through
A Type Record is a
| [[Kind]] | Additional Fields | Description |
|---|---|---|
| (none) | The dynamic type. Any value is assignable to it, and a value of the | |
| (none) | The type of the absence of a value. | |
| [[Name]] (a String), [[Arguments]] (a | A built-in uint8 or float64, together with any type or value arguments, for example the width of int.<N>. The primitive types are enumerated in the type universe clause. | |
| [[Value]] (a String, Number, Boolean, BigInt, or Symbol), [[Base]] (a | A literal type: the type whose one value is [[Value]], a subtype of its [[Base]] ( | |
| [[Base]] (a | A metadata parameterization of any base type, such as float32.<{ m: 1 }> or a brand: a refinement of its [[Base]] ( | |
| [[Declaration]] (a | A class, interface, or | |
| [[Members]] (a | A union type. A value belongs to it if it belongs to any member. | |
| [[Members]] (a | An intersection type. A value belongs to it if it belongs to every member. | |
| [[Elements]] (a | A tuple type of position-wise element types, each element carrying its spread flag and its default. | |
| [[Element]] (a | An array type. [[Extent]] is the fixed length of a [N].<T>, or [].<T>. | |
| [[Properties]] (a | A structural object type or interface type. | |
| [[Signatures]] (a | A function type. It has more than one signature when the function is overloaded. | |
| [[Target]] (a | A reference to a storage location holding a value of the target type, as introduced by ref. | |
| [[Target]] (a | A shared ( | |
| [[Declaration]] (a TypeParameter | A generic parameter standing for the type or value an application will bind, within the body and signatures of its declaration ( | |
| [[Operator]] (a core type operator, one of | A deferred computation over a generic parameter, carried as a type until keyof T (T[K] ( |
The structured fields referenced above, a
A Type Object is an ECMAScript object that
Type Objects are interned.
The observable behaviour of a
Two types are the same when they have the same structure, with one exception: a
The abstract operation SameType takes arguments s (a
SameType is MUTUAL SUBTYPING. It is therefore reflexive, symmetric, and transitive wherever
It was previously defined as "a", string) answered string, "a") answered string from "a" | string while leaving string | "a" intact, so the same type written two ways admitted different values.
The refinement those steps provided is not lost. It is what
The abstract operation SameTypeWithAssumptions takes arguments s (a
The abstract operation SameTypeStructural takes arguments s (a
It performs the following steps when called:
This is a STRICTER relation than
It is the relation
The abstract operation SameTypeList takes arguments a (a
The abstract operation SameArgumentList takes arguments a (a
The abstract operation AreDisjoint takes arguments s (a
This is the judgment
An intersection of exactly two meet hook returned (uint8<{ bounds: 1..=10 }> & uint8<{ bounds: 5..=20 }> is uint8<{ bounds: 5..=10 }>, and the two spellings intern to one ===.
The hook runs USER CODE and this operation does not, so the meet is computed by the meet, or returning
The operation is CONSERVATIVE: where the values of a type are not yet known it answers P & Q of two unrelated classes and a narrowing to the same pair therefore receive one answer.
Disjointness is decided on the BASE of a string.<{ brand: 'E' }> & string.<{ brand: 'V' }> is two refinements of one string, inhabited by every String granted both, while string.<{ brand: 'E' }> & uint8 is empty because the bases are. A rule phrased over "two value types" rather than over their bases would have destroyed the first along with the second.
The walk to the base is a LOOP and not a single step, because parameterizing an already-parameterized type nests: the outer [[Base]] is the inner type, so string.<{ brand: 'E' }>.<{ brand: 'N' }> reaches string only by walking through the layer between (
The base a walk arrives at need not be a 'a' and a brand on string may share a value while a brand on 'a' and a brand on 'b' may not. An string, and a brand over an object is disjoint from a
The abstract operation CompositeShapesAreDisjoint takes arguments s (a
The abstract operation CanonicalizeType takes argument t (a
never type; see never type).[...[uint8, string], T] being [uint8, string, T], which is what lets a spread of a bound never, return never.never, return never.never, return never.never, return never.never, return never.never, return never.An earlier draft forbade absorption, on the ground that folding by
The circularity does not arise, because the fold runs on members ALREADY canonicalized by the loop above it: "a" | string and string | "a" are one "a" & string and string & "a" — and so does termination through recursive and MUTUALLY recursive members, where the relation simply declines to relate the pair and both members stay.
The unabsorbed form does cost something, and the cost is not in the judgments. Membership and assignability do answer identically, but a union with a subsumed arm still has that arm, and an implementation lays out and TAGS what a type says it holds. string | (number & bigint) describes one inhabited arm and would be given a tagged two-arm representation; absorbed, it is string, and a
An intersection every member of which is an never. A member REQUIRED by any one arm is required, an intersection requiring all of them, and one declared readonly anywhere is readonly for the same reason.
Distribution reaches the case whole-object subsumption cannot. { a: number } and { a: 5 } are already related, { a: 5 } being a subtype, so their intersection folded to the narrower before this step existed; in { a: uint32 } & { a: string } neither object is a subtype of the other, so the pair stood as an intersection whose emptiness was discovered at every use of the annotation rather than stated once in the type. It also removes a disagreement between a type and its own identity: { a: number } & { a: 5 } interns as { a: 5 }, and an operation that read the members as written could accept a value at one spelling and refuse it at the other.
The step is gated on EVERY member being an Iterable, so [].<uint8> & { length: uint32 } is inhabited and is not the object type the properties alone would give — and such an intersection is left standing.
An never has no values, since a value of it would have to hold a value of a type that has none, and is never. An OPTIONAL property of never does not empty the type: it declares a member that may never be present, which every value omitting it satisfies. The test is one level deep and on the CANONICAL property types, which is what makes it terminate without an inhabitation judgment or a fixpoint: canonicalization is bottom-up, so { a: { b: never } } reduces because the inner record reduced first. A recursive type closes its cycle through a reference position — an array element, a nullable union — and neither propagates, so a cycle cannot drive a type to never through this step.
A ref names a location and there is no location of the empty type, a
A type Pair<A: type, B: type> = [A, B] and type Result<T: type, E: type> = { value: T, error: E } are one
A REST position does not empty a tuple, and neither a variable-length array nor a zero-extent one is emptied by its
What the reduction costs is that a type-level list cannot be a tuple type when one of its positions is the empty type. It costs nothing in practice: the type-programming extension's list currency is an array of types rather than a tuple type — elementTypes returns one and tupleOf consumes one — so a program that filters never out of a list reads the array and builds the tuple from the result, which has no such position left in it. TypeScript keeps such a tuple, but keeps { a: never } as an object too, so it is not evidence for treating the two products differently; Rust reduces both, (i32, !, char) and a struct with an uninhabited
Flattening merges a nested union into its parent, and likewise for intersection; de-duplication and the fixed order make member order insignificant, so A | B and B | A canonicalize to one never member from a union: the member is an empty union, so it contributes nothing, and T | never canonicalizes to T with no rule naming the case. The first intersection step above is the one algebraic fact about never that does not fall out this way (never the ONLY empty type rather than one of many: an intersection two of whose members are disjoint has no values, and a type with no values is that type. Without it number & bigint interned as a never nor assignable to it, so never, so string | (number & bigint) is string: a "a" | string is string, and an intersection the NARROWER, so "a" & string is "a". A single rule for both would fix one and break the other.
The abstract operation GetTypeObject takes argument t (a
Subtyping relates a more specific type to a more general one. Assignability is the relation the type checker applies at an assignment, an argument, or a return: it is subtyping, widened at the boundary with the Meter is a float32.<{ m: 1 }> and is a subtype of float32, which is what makes m instanceof float32
The abstract operation IsSubtype takes arguments s (a
A refinement is a subtype of what it { a: "x" } was a subtype of { a: string }, and the write that clause warns of - "a bare float32 could be written through the supertype view into a Meter" - was admitted.
A type parameter is OPAQUE within the declaration that binds it: it is a subtype of itself and of its constraint, and nothing else is a subtype of IT. The asymmetry is the point. T: string says every value of T T may be returned where a String is wanted — but it does not say a String is a T, because T may be bound to a literal type: f.<"abc"> satisfies that constraint, and "abc". So function f<T: string>(x: T): string { return x; } is well function f<T: string>(x: T) { let v: T = "s"; } is not.
Without the [[Constraint]] step a constrained parameter would be LESS usable than an unconstrained one, since writing the constraint would buy its author nothing inside the declaration while still restricting the caller. With it, the constraint is what it reads as: a statement about every binding the parameter can take, available wherever the parameter is.
A
Three of the steps above are one relation. A type refines another when it is a
A 'on' usable where a string is required while string is not usable where 'on' is. A tuple's defaults take part in identity but not in subtyping, and tuples relate by the arity window: s is a subtype of t when every length s admits, t admits, and every position s can supply is a subtype of the position of t that receives it. Where s has several rest elements a position may be supplied by more than one of them, so the type s can put at that position is their union, and requiring each to be within t's position is what makes the rule sound.
Where t has more than one rest element, its positions are not determined by their index and the exact relation is inclusion between two regular languages, which a product construction decides and which a subtyping check cannot afford to run at every use. The rule above is conservative there: it requires the two element lists to correspond one for one, which is sound and is exact whenever they do. A program wanting a relation the conservative rule refuses writes the two tuple types in corresponding shapes, and the case is narrow, since the target of a subtyping check is a declared type and a declared type with two rests is already an unusual thing to write.
A any type, and of its operator's BOUND where the operator has one: the key universe string | symbol | uint32 for T[keyof T] for an keyof T, and the declared return type of a function builder. A builder with no return annotation, like an operator over operands nothing is known about, has no bound and is a subtype only of itself and any. Before any.
A generic class is invariant in its arguments, so Map.<string, uint8> is a subtype of no other instantiation of Map. This is the conservative choice and the only one that is sound for a mutable container, since a Map.<string, uint8> used as a Map.<string, number> would accept a Number into storage uint8. A parameter may be
A position declared covariant admits an argument the position's own type is ASSIGNABLE FROM, not merely a subtype of. The two differ for any, which is assignable in both directions — let e: Error = a for an any a is admitted, and so is the reverse — while being a subtype only of itself and upward. Reading a covariant position by subtyping alone therefore answers one way at the top level and another one position in, for the same pair; that is one relation asked two ways rather than two rules, and the assignable reading is the one that agrees with every other position a value passes through.
This does not reach an INVARIANT parameter, which is what a mutable container has and what the paragraph above requires of it: Map.<string, uint8> remains usable as no other instantiation, Map.<string, any> included, since its storage is written THROUGH the view and an any there would admit a write the storage does not hold. A covariant parameter is one whose values the type only ever PRODUCES, so a wider reading of it can be wrong about a value but cannot corrupt one.
A consequence worth stating, because it decides how a generic's statics are written: a type argument fixed to a CONCRETE type in a signature's return admits only that argument. Promise.resolve written (value: R): Promise.<R, any> would produce a value assignable to Promise.<R, any> and to no other instantiation — not because any is special, but because invariance makes every instantiation unrelated. A static whose result is meant to satisfy a caller's declared type states that position as a PARAMETER and lets the Promise.reject does with its E.
[ ] . < any > is the top of the array family: an array of any is already the type of which every value is a value, and this is that reading carried to the array types; the extent rules are unchanged, so a fixed-length target still fixes the length and admits any
Element invariance is untouched for every other [ ] . < uint8 > used as a [ ] . < number > would accept a Number into storage uint8. What makes any admissible where a general covariance is not is that a store to an element is checked against the array's own
Without this the bound a generic writes over the array family, T extends [ ], is satisfied by an array whose any and by nothing else, since no array is declared that way.
The keyed collections have the same top, by the same reasoning one step along: Set.<any> is a Map.<any, any> a map of any admissible here is what makes it admissible above - a store into a collection is checked against the receiver's own declared types at run time (
A collection's top is decided ARGUMENT BY ARGUMENT rather than all at once, so Map.<string, any> is the top of the maps keyed by String and a Map.<uint8, uint8> is not a subtype of it. The array has one type argument and so never had to say this.
A BARE type name is not a top. Set is the unparameterized type - a collection carrying no type arguments, whose behaviour is Set.<uint8> is not a subtype of it, exactly as a [ ] . < uint8 > is not a subtype of a bare [ ]. The top has a spelling, and it is the one that composes: Map.<string, Set.<any>> says what a bare Set in that position could not.
This resolves an inconsistency rather than adding a rule. A Set.<uint8> reached Set and G.<uint8> did not reach G - two readings of one rule, with nothing in this specification choosing between them. The array chooses: a bare name is the unparameterized type, and the erased form is written with any.
The kind-specific steps record the variance chosen by this Span.<T>, the one type that accepts either, and instanceof), with the array whose element is any a supertype of every [].<any> a supertype of every array, and makes Set.<any> and Map.<any, any> supertypes of every instantiation of theirs; a readonly one;
The abstract operation IsAssignable takes arguments s (a
When s is the
A Script whose own body contains a
The trigger is syntactic and includes an annotation denoting this value, unmapped arguments and
The annotation makes f strict, so the eval-local var make does not replace the outer function. The call returns 1, as it would with an explicit use-strict directive. Replacing the eval source with make = () => 2 instead assigns to the outer binding and the call returns 2. In both cases the ordinary initializer is evaluated when its declaration executes; checking does not substitute a cached callee or initializer result.
function make() { return 1; }
function f() {
eval("var make = () => 2");
const result: any = make();
return result;
}
f();
A unit is a
Outside in and instanceof; a call of a value that is not callable; and overload sets formed from function declarations. Function declarations of one name form an overload
A rule about a declared type, or about a binding or global this specification introduces, applies everywhere. A program written for the
match without an annotation is
A resolved super property read this property read does. This applies to an established computed key as well as a dotted name. The inherited member supplies its readable type without executing a getter. Extracting a method preserves its signature and receiver requirement; storing it in a binding does not bind its receiver. Unknown members,
The Static Type of an expression is the
Established evidence for an unannotated immutable String constant is preserved through an unannotated immutable copy, including parentheses and
When a source text uses an already initialized immutable binding, retain its established declared or inferred contract and any eligible constant evidence by that binding's identity in its declaration environment. A nearer declaration blocks fallback even when it supplies no evidence. Follow an import to its exporting binding; local import immutability does not establish that the target is immutable. Reading retained evidence does not invoke an environment method, ordinary getter or initializer, inspect a runtime value to manufacture a type, or execute earlier statements to obtain future evidence. Checking alone does not publish a program binding. A successfully initialized binding remains eligible after a later
An initialized immutable binding whose value symbol or
Where source analysis establishes that an immutable local declaration will receive a fresh Symbol, repeated visits to that declaration within the same checking
Apply those retained facts in the environment the source text uses: an ordinary Script and a dynamically constructed function use their target
When a separately parsed source references a lexically enclosing generic parameter whose argument has already been bound, retain the selected argument by that parameter's declaration and binding instance in its actual lexical environment. Use that evidence when checking written annotations, including annotations within a contextually
| Expression form | |
|---|---|
| A numeric literal | The "number". |
| A string literal | The "string". |
A typeof expression | The union of the literal String types of the tags its operand's typeof being unchanged ( |
The "boolean". | |
The type named "null". | |
| A reference to a binding | The declared or inferred type of that binding, or the |
| A type name or type expression in expression position | The "type". |
new.target | The |
new C | The class's type ( |
An null and undefined from the base type and apply the ordinary member-access or call judgment to the remaining type and the subsequent chain segments. If that boundary can short-circuit, canonicalize the union of the undefined. An entirely nullish base contributes only undefined; a known non-nullish base introduces no additional absent result; a result whose (o?.a).b is an ordinary access to the optional result, whereas o?.a.b keeps the latter access on the
The permission for redundant optional access applies in value positions, conditions, equality tests and switches alike: a known non-nullish receiver leaves the written continuation reachable. An always-nullish receiver retains the dead-chain diagnostic of
A tagged template uses the ordinary call judgment with its template-strings object as the first argument and its substitution expressions as the remaining arguments. Each known first-argument position contributes the exact cooked String value produced by undefined. The position count and cooked literal facts participate in overload selection. The raw positions remain Strings. This judgment changes neither
Computed names of object methods and accessors, and class methods and fields, are checked using the ordinary expression and property-key conversion judgments. This includes generator, async and async-generator methods. Check each name in its enclosing evaluation scope before entering the member parameters, body or instance/static this scope, retaining the class self-name and type-parameter scopes. The expressions are checked even in an unused body; no key is evaluated during this traversal. Assignment, reference permissions and liveness retain their ordinary rules.
A setter body is checked with its
A member read whose key is a statically established Symbol identity uses the declared member's type, just as a read with a known String key does. The identity is resolved in the expression's lexical scope; a shadowing declaration does not inherit an outer constant's key. This judgment includes inherited members and substitutes the receiver's type arguments. An optional member contributes the union of its declared type with undefined, including when combining the member types of a union receiver. An unknown key or a key no alternative declares remains governed by the ordinary dynamic read rules; in particular, an array's Symbol property is not an indexed element.
A write through a union receiver must satisfy the write permissions of every reachable known alternative at that operation, after narrowing. It is a
A store through a union receiver or a statically established
A call through a union of function types is checked separately against every reachable known function alternative, after narrowing and optional-chain short-circuiting. Argument mapping, ref requirements, generic inference and overload resolution are performed within each alternative, using that alternative's contextual types. It is a
new.target is Reflect.construct(_target_, _args_, _newTarget_) supplies an arbitrary one, so a type naming the enclosing class or a subclass of it would be unsound. The honest type is therefore a union of a callable type with new.target ? new.target.name : *undefined* requires and which this specification defines only for references: a binding, and a member access through one. new.target is a meta-property rather than a reference, so no narrowing applies to it.
A union type here would refuse a program that ECMAScript admits, which is worse than saying nothing: import.meta has the same
Reading a member of a UNION gives the union of that member's type across the union's members, and is a ANY of them does not declare the keySOME of them declare the key and others do not.
The accessible keys are therefore the INTERSECTION of the members' keys, and the type of a read is the UNION of what each declares for it. _c_.x at { x: int32 } | { x: int8 } is int32 | int8, so reading it AS an int32 is an error: the program cannot know which member _c_ holds. _c_.x at { x: int32 } | { y: string } is an error outright, and a null member - which declares nothing - makes every read of a nullable object one.
Narrowing is how a program reads a member a single member declares (
A key NO member declares is not this case either, and the distinction is the reasoning above: uncertainty about which member is held decides nothing where every member agrees the key is absent. Such a read is an ordinary absent read, governed by the note in if (u.maybe), typeof u.absent and _u_.absent === are as ordinary here as anywhere. Reading u.toString() is the same case: no member's structure declares it, and it is reachable on every value the union admits.
An INTERSECTION is not this case. Every value of one satisfies all its members, so each member's type is known and a read of it needs nothing.
An element of a destructuring pattern is a binding, and its annotation is a boundary like any other: let [a: uint8] = [300] throws, and let [a: uint8] = [1] binds a uint8 rather than a Number. The a is.
A plain formal parameter is a different case with its own entry above: its type is enforced once, where the arguments are bound and a generic call's type arguments are known. A pattern ELEMENT inside a parameter has no such entry, which is why it needs this one.
A destructuring pattern preserves the known read const patterns retain it under their existing inference rules. This does not infer a type for an otherwise untyped let binding, impose a new missing-key rule, or establish a property
Where those existing rules give an extracted non-reference binding an inferred contract, initialization of that binding is a boundary at that contract, as for a plain inferred binding. Read the property or
A compile-time constant is a literal, a name denoting a constant of literal or enumerator type, or a compile-time numeric constant expression over those, so its value is fixed by the source alone; every clause that names the term reads this definition.
The 0 in a uint8 position is a uint8, this, are given by the clauses that specify those forms; until a form is specified, its let or var binding has the const preserves a type supplied by a any; an initializer whose const whose initializer is a compile-time numeric constant expression, which produces the value the initializer would have produced had it been written at that position: const K = 3.14 yields a Number where nothing requires a type, a float64 in a float64 position, and a float32 in a float32 one. This infers no type for the binding, and adds none: it decides which VALUE a use produces, which is the question propagation already answers one position earlier. A let is excluded, because a binding that may be reassigned must have a type its assignments are checked against; an annotation on a const excludes it for the same reason. The constants of the Math object named by const does, since none of them can be written as a literal that
Because a type is a value, a type annotation may be an expression that computes one (
No operation of this specification evaluates such an expression before every generic parameter it reads is bound. The invariant this serves is stated exactly: the inference fixpoint is builder-free, and every checker-visible effect of user type code is declarative data, a forward-verified proposal, or a per-instantiation-checked contract. Those three are the whole of what user code may contribute to checking, and each is closed: the declarative data are a signature's [[ThisType]] and [[Narrows]] (T runs after T is bound and before the parameter it constrains is, which is after everything it reads.
The blunt form of this invariant, that a builder never runs during inference at all, is what a reader expects and is not quite what holds, so it is worth being precise about which part is load-bearing. A trial evaluates a builder while a parameter is being inferred; what it does not do is let the builder into the fixpoint. Each evaluation is of a builder whose arguments are already bound, to a candidate rather than to an inference variable, and its result is checked rather than solved against. Nothing runs backwards, nothing is unified, and no builder observes a partially-solved state. That is the difference between a proposer the checker verifies and a participant the checker trusts, and this
The abstract operation IsCompileTimeEvaluable takes argument expr (a
eval, and the declared function is A compile-time-evaluable function is one whose body reads only its parameters, its own local bindings, immutable bindings whose initializers are await, no yield, no direct or indirect eval, no use of the Function
A function declaration creates a mutable binding, yet it is the callee this operation exists to admit: the callee of a builder call resolves to a function declaration. Its binding is therefore treated as immutable here where the source text contains no assignment to it and no direct eval. A function the program reassigns is not the function the checker would read, so a call through its name is not evaluable, and a let or var binding is not immutable whether or not the program writes to it.
An import's local immutability does not make the exported binding immutable. This judgment follows the resolved export, including re-exports; a read of an exported let or var remains a read of mutable state. A module namespace member that resolves to an exported binding is subject to the same judgment. The declarations and initializers consulted by this judgment retain their original lexical scopes. In particular, free references in a builder and in its helper functions resolve in the environments of those functions, not in the importing source text or a temporary environment reconstructed from their signatures. A known
This is the same notion the where clauses and metadata annotations of the extensions require, and it is what lets a switch over a generic parameter be evaluated to a type during checking. A function that is not Set is how a walk over a type is written; shared module-level mutable state is not evaluable and is rejected statically, which also disposes of the hand-rolled memo cache, since the memoization of
A
A call whose arguments involve an unbound generic parameter cannot evaluate yet, and is carried as a type: the
Identity is by that key. partial(T) in one generic body are one T. At
Two compound compile-time constants, arrays or plain objects of compile-time constants, are structurally equivalent when they have the same source and flags. A compound constant argument contributes to an application's identity by structural equivalence rather than by reference, so pick(User, ['id', 'name']) written twice is one application although the array is a fresh object at each occurrence.
An unbound parameter is represented by its declaring
The abstract operation EvaluateBuilderCall takes arguments f (a
The evaluation memo is a specification-level map from canonical
The fixpoint is what makes a builder work on a recursive type with no API surface: a builder mapping over a type that references itself re-enters itself with the same key, receives the placeholder, and the placeholder resolves at return into the same kind of named-back-edge cyclic type that a recursive alias produces from syntax, validated by the same cycle rule (
The budget bounds a computation, which either completes or is abandoned and reported. A rule requiring a SEARCH — over the types an argument could take, rather than over a fixed evaluation — is not metered by it and is refused where it would otherwise arise (
A limit must be one the implementation can actually reach. An implementation whose own resources — a native stack, a heap — are exhausted before the limit is has not met this clause, whatever the limit's value: the evaluation ends in a way no program can observe and no diagnostic names, which is the outcome this clause exists to prevent. Where an implementation cannot raise its own capacity, it lowers the limit instead. A limit above what an implementation can reach is not a limit.
This bears on any
Lowering the STEP limit is the wrong remedy, and a
Exhaustion is not an try statement within the evaluation handles it; the evaluation is abandoned, and
"Not an where predicate or a builder a try that catches it, and the evaluation then continues on a budget it has already overrun. Recording exhaustion as a state of the evaluation, so that every metered operation after it does nothing and the containing evaluation fails whatever the evaluated code does with what it caught, has the behaviour this clause describes. The diagnostic belongs at the containing evaluation for the same reason: the operation that spends the last step is wherever the budget happened to run out, which is not the outermost call this clause asks to name.
Making exhaustion catchable inside evaluation would let a program's types depend on the
The type computation includes propagating retained
A repeated active obligation with the same declaration-bound inputs may stop propagation of that same obligation. It supplies no completed type, admission proof or waiver of independent obligations. Repeating a declaration with different determining inputs is not the same obligation. If propagation exhausts its allowance, the containing source check reports evaluation-limit exhaustion in the phase that performed the work and publishes no successful checking result. Discard unfinished propagation state on every exit; a later independent check starts without that state. This failure is distinct from return-inference exhaustion and from a proved invalid recursive type.
A type computation includes the complete evaluation it invokes: builder and declared inverse bodies, their callbacks, generic arguments and defaults, object member and tuple element defaults, array extents, refinement predicates, and metadata hooks. Charging only the entry to a user function does not bound that computation. Nested evaluation shares the enclosing computation's resource state and cannot reset its remaining allowance. When an implementation imposes the nesting limit of
A type-construction request is an invocation of
Each counter begins at zero. A request whose charge is no greater than the remaining allowance succeeds and reduces that allowance by its charge; a larger request marks the computation exhausted before the requested evaluation proceeds. Exactly consuming the allowance is permitted. Exhaustion is sticky until the outermost computation ends: unwinding a nested call, handling an incidental exception, or returning a fallback does not clear it. A result obtained after exhaustion must not be
The enclosing checking boundary tests exhaustion before interpreting the computation's result. Exhaustion takes precedence over an incidental hook exception, an absent result, an assignability mismatch, or a candidate-selection failure produced while abandoning that computation. The diagnostic identifies evaluation-limit exhaustion as specified in
These rules distinguish logical requests from physical allocation and define containment of exhaustion. The
The abstract operation EvaluateToTypeObject takes argument expr (a
A successful result retains its
It is a
switch used to map a value to a type
Type errors fall into three families (
Implicit protocol calls check their actual receiver against each applicable signature's [[ThisType]], alongside that signature's argument and result obligations. The receiver is the resource for disposal, the next or return, the original object for primitive conversion and the
A reached optional call applies the same argument-spread eligibility rules as an ordinary call. Short-circuiting alternatives do not evaluate or check an otherwise skipped argument operation. A possibly reached call still checks an established invalid spread;
A type error is a violation of the type system detected by the type checker: an assignment whose source is not assignable to its target, a call whose arguments match no signature, a type annotation that is not
A any, reassignment, decorators or an unresolved origin prevent transporting a contract without further evidence. No
A eval is a Script and is checked as one. The source text meta declarations, the implicit cast operators of its primitive blocks (inverse decorations of its compile-time-evaluable functions (
A check that cannot be resolved statically, because a value reaches a
The uint32 to a uint8 binding, as a “
A source class declaration's type denotation and the current
The prescribed transparent cases are class creation, initialized unannotated lexical
For a condition established by the prescribed rules, transfer effects only along the selected evaluation edge. At an unresolved join, retain an origin only if every normally completing incoming edge establishes that same origin. Retain the value
A pattern or iteration write withdraws the origin of each destination binding unless its
Potential calls, getters, setters, conversions, eval may still invoke the intrinsic
Defining a callable does not invoke its body or parameter defaults. Analyze its possible invocation with only the
A mutable Script-global
Calling a generator creates an
An established violation follows the existing type-error boundary, including a receiving, inherited-member or
class Original {}
class Other {}
let Selected = Original;
Selected = Other;
const value: Original = new Selected(); // established mismatch: Early Error
Conversely, an immutable alias
At a binding initialization, assignment, argument or return boundary receiving a source-class nominal type, distinguish an established end of the source's superclass chain from an unavailable superclass origin. A class with no heritage, or with an established
This is a rule about the evidence supporting a receiving judgment, not a change to
For this judgment, consider source union members independently: a member established to be incompatible still requires the existing
Where ancestry alone prevents resolving the receiving judgment, perform the original evaluation and apply the existing runtime admission to its actual result. Do not select an implicit conversion on the premise that unavailable ancestry proves nominal incompatibility. Preserve lookup, callee
Unavailable ancestry never licenses check elision, overload selection or a positive subtype proof. Successful runtime admission retains the ordinary nominal prototype obligations of
This initialization succeeds. The presence of the later harmless eval must not turn missing ancestry evidence into a proof that W is incompatible with V. Replacing W with an unrelated class that has no superclass gives an established incompatibility and retains the Early Error.
class V {}
class W extends V {}
const value: V = new W();
eval("1");
A proved Map.groupBy result retains the established callback key contract and the source iteration Object.groupBy result retains the same group contract and the callback result after
At proved intrinsic String.prototype.replace and replaceAll operations, a callable
At an established implicit Math functions and Number convert their arguments with Math.max fails as surely as one given to parseInt. This includes a string search operand of String.prototype.includes and an Array.prototype.join separator when their preceding stages are established. The examples are not a list: an operation is proved by its origin, so every proved intrinsic that converts an operand with startsWith, endsWith, indexOf, lastIndexOf and localeCompare, each argument of String.prototype.concat, the filler of padStart and padEnd where the target length exceeds the receiver's, and the first argument of parseInt, parseFloat and the URI functions. Array.prototype.join and toString also convert each element after the separator; that stage is established where the receiver's type fixes a length of at least one, as a tuple or an array of fixed extent does, and not for an array that may be empty. Do not replace String function's Symbol conversion is unchanged. A proved Object.is is a test whose answer the types may already know: it is a Array.isArray declares a narrowing of its argument (~array~ and ~tuple~ members of its type, whose every value is an Array and stays one, and away from its primitive members, none of which is one; a proved Number static declares a narrowing to the Number-valued members, answering Number.isInteger. Publish an argument fact only to its preserved Object.is supplies only
At a proved set operation, read the source as an
At proved Object.create or Object.setPrototypeOf, a participating prototype argument necessarily outside Object or Object.setPrototypeOf's preceding
A proved Array.from result carries the element contract established by the same operation, source and callback proof used for its input checks. With a supplied mapper, use its established result contract; with an omitted or Array.fromAsync.
At a proved intrinsic Reflect.apply, check the selected target's callability, the supplied receiver and the forwarded argument list at its ordinary callable contract. Build the list using the operation's array-like positional reads, not iteration; establish the relevant count, entries and effects. Preserve ordinary overload selection, defaults, rest binding and conversions. Reference Values decay under the built-in rule; forwarding does not manufacture references for reference parameters.
At a proved intrinsic Reflect.construct, check the construction capabilities of the target and of an explicitly supplied newTarget independently. Check the established array-like argument list against the target's construct signatures, not newTarget's parameters. Preserve overload selection and ordinary binding. The restriction on directly instantiating an abstract class is not a prohibition on its use as a constructible newTarget or base. A different newTarget does not establish that the result has the target's ordinary instance type. Unknown capabilities, argument reads, specializations or effects remain dynamic. These judgments allocate no instance and change no reference-liveness rule.
A proved selected function contributes its declared generic capability independently of its result signature. Applying explicit type arguments to a function declared nongeneric, such as the original Object.keys, is a Object.values, Object.entries, Array.from and Array.of retain their declared parameters. A mutable property's spelling does not establish this capability; aliases require the same origin and effect proof.
At a proved intrinsic Promise.all, Promise.race, Promise.any or Promise.allSettled, it is a
At proved Array.from, Array.fromAsync, Object.groupBy and Map.groupBy calls, check the supplied callback's callability and each established invocation against its ordinary value-parameter and receiver boundaries, including omission, defaults, rest binding and permitted conversions. An omitted or Array.fromAsync awaits the values it supplies to the mapper and its mapped results. Do not infer iteration from mutable indexed storage. Immutable tuple Composites can establish
The judgments in this clause require establishing the actual selected intrinsic or descriptor and the relevant argument, receiver and storage facts. A spelling or nominal receiver type is insufficient. Account for prior source texts, lexical shadows, own properties, prototype changes, argument evaluation, implicit conversions and other effects that could invalidate a dependency, including concurrent interference where possible. An annotation's boundary is such an effect only where it can run user code for the value that reaches it: checking a construction of the annotated class, a keyed collection constructed empty against type arguments whose checks run no user code, or a literal of plain scalar data against an object type whose members' checks run none, runs no user code. A construction is such an effect unless it runs no user code, as for an intrinsic Map or Set constructed without arguments, or a class of the source text with no heritage, decorators, type parameters or
It is a capacity or collection size accessor. A fresh receiver with established descriptors may justify the proof; a receiver type alone does not exclude a writable own shadow or a
At a proved intrinsic Function.prototype.call or apply, check the forwarded arguments and receiver at the selected target's ordinary callable contract. apply uses array-like positional reads, not iteration; count, entries and their effects must be established independently. At a proved bind, check the fixed prefix and receiver, without demanding the arguments left for the eventual call, and retain a residual signature only where the remaining mapping is established. Reference Values decay on entry to these built-ins as specified by
At a proved shared is not an eligibility requirement. The first reference argument retains the Atomics exception to built-in decay. A conditional compareExchange, is not established solely by the presence of its argument; a possibly skipped conversion prevents that failure proof. No memory-order, blocking or liveness rule is changed.
At a proved callThread, apply
At a proved Object.assign, Reflect.set, Object.defineProperty, Reflect.defineProperty or Object.defineProperties, an established write to Object.assign({ (x: uint8): 0 }, { x: "s" }) has a statically incompatible write. A later source cannot erase an earlier failed write, while duplicate definitions within one source obey ordinary final-property semantics. Structural membership alone does not enumerate copied properties, and a distinct Reflect.set receiver is not
Descriptor adapters preserve the selected operation's staging. Reflect.defineProperty does not bypass a Object.defineProperties collects and validates the complete descriptor list before applying any definition; a malformed later descriptor or an unknown descriptor getter prevents a proof that an earlier store is reached. Final duplicate-property semantics and each applied definition's order remain unchanged.
This clause defines the common inputs, applicability and composition of the type judgments in this specification. A judgment uses only the evidence admitted by its defining clause. An implementation must not reject an additional program by using a stronger proof procedure, runtime feedback, speculative object shapes, or an optional optimization. It may issue an informative diagnostic for such a program without changing its acceptance or evaluation. A judgment that lacks an admitted proof is unresolved; it is not a proof of failure.
The checked
| Syntax | |
|---|---|
| A | That |
| A method's computed name | The |
| Decorators on a | The |
| A nested | The nested |
| Other syntax | The innermost enclosing |
To classify a parsed source, first determine every node's
The classification does not evaluate annotations, decorators, computed keys, or any other expression. Moving
Each type-checking obligation identifies its rule, responsible source node, and, where relevant, the contract from which it arises. The checked status used by a rule is the status of that source node's owning
The following precedence resolves the three families in
A diagnostic family can describe several obligations with different applicability. For example, an inadmissible new, or a
For an implicit Symbol.hasInstance call, distinguish the existing operator's callability requirement from the selected callable's declared receiver and parameter contracts. A proof of non-callability (including the nullish-hook fallback) is checked-only. A proof that every selected callable violates its declared receiver or parameter contract applies everywhere. Outside
Applicability does not erase analysis. Outside
Existing built-in contextual signatures and overload formation require earlier applicability decisions. Outside
A checking state consists of the declaration environment, current value facts, origin facts, reference facts, and the reachable completion alternatives. These are specification data, not properties of program objects. Lexical bindings are identified by their declarations, not by the spelling of their names. A property
| Component | Meaning and invariant |
|---|---|
| Declaration environment | The declared store types, callable contracts, lexical binding identities, resolved type declarations, and available imported bindings. A narrower current value never changes a declared store contract. |
| Current value facts | The domains, exclusions, numeric categories, and value relations established by the specified transfers. Missing evidence is distinct from the declared type |
| Origin facts | The |
| Reference facts | The location, permissions and liveness facts admitted by |
| Normal, return, throw, break and continue alternatives, including their values, target identities and states. An impossible predecessor contributes no alternative. |
Joining reachable predecessors retains a fact only to the extent it holds on every predecessor contributing to that result. Value domains are joined by the specified type union; exclusions and relations retain only common evidence about the same surviving identities. An absent fact on a reachable predecessor is not an impossible predecessor. Joining no reachable predecessors produces an impossible result. Abrupt alternatives are joined separately by completion kind and target. Leaving a scope projects away its local binding identities; it does not erase a
The checker must use the defined
The cycle rules of
When checking establishes either violation, the responsible source is the declaration that contains the cycle. The source check fails without evaluating its candidate body, including when the declaration occurs in an uncalled nested function. The invalid graph is not an input on which subsequent contextual typing, canonicalization, member lookup, or narrowing may establish a successful checking result. An implementation may stop after that diagnostic or recover to collect further diagnostics, but recovery supplies neither an accepted program nor a fallback language type.
Completing declaration information may precede this judgment. An unresolved generic parameter or unavailable computed type is not proof of a cycle violation. The rule is applied when its inputs are established under the existing declaration and
Each invocation of source checking has its own declaration-preparation state. A group of type aliases and interfaces prepared together in one lexical declaration scope identifies the bindings created for those declarations in that scope. These are binding identities, not identifier spellings. Resolving a name still follows the applicable lexical-environment and module-import rules. A same-spelled binding in another scope,
When preparation encounters an uninitialized binding read while resolving a type reference, that read is eligible for deferred recursive resolution only if the binding read is the binding of a declaration in the currently active preparation group. An unresolvable name, an uninitialized assignment, a read of an unrelated value binding, or an arbitrary thrown value does not establish such eligibility. An indirect module binding identifies the binding actually read in the exporting module; the local import spelling does not establish membership. An exception's class, properties, or formatted message is not evidence of binding identity or recursive resolution.
Eligibility for deferred resolution is not proof that a recursive type is valid. The declaration's type must still be determined under the applicable declaration rules, including
Nested source checking has a distinct active preparation group while the enclosing invocation is suspended. On every exit from the nested invocation, the enclosing state is restored. On every exit from an outermost invocation, its temporary preparation evidence is discarded. These requirements apply to success, rejection,
This state creates no program reference, extends no reference lifetime, and adds no observable property to an exception or
Interface formation preserves every declared member contract across self-reference, mutual reference and forward reference. The rule applies in every lexical declaration scope, including a block, switch statement, function body or other nested scope, as well as during source checking. Moving an otherwise valid recursive declaration into such a scope does not make its type references ordinary uninitialized value reads.
Resolving a forward declaration in type position does not itself initialize that declaration's ordinary value binding or mark its ordinary
The reference retained during graph formation is specification data, not an ECMAScript Reference or a program value. It introduces no storage, object property, reference permission or extended lifetime. Implementations may use shared graph nodes, handles or an equivalent representation, but a successfully formed recursive contract cannot lose information because one declaration was visited before another.
These rules do not authorize evaluating a genuinely runtime-dependent input before its environment exists, entering an uncalled function, or reading a required cyclic import through a fabricated local declaration. Imported binding availability and checking failure still follow
For the declaration preparation required by export prefix. The export wrapper introduces neither a separate preparation group nor a later checking phase.
Every preparation stage that consults these declarations uses this selection, including establishing the preparation group, resolving recursive references and computed types, making metadata meta declarations, and completing a type whose result depends on the registered metadata protocol. The declaration retains its original binding identity, lexical environment and source location. Its exported binding is the same binding used by ordinary module resolution; preparation does not create a second declaration or a snapshot of that binding.
A named export or re-export clause contributes no additional local type declaration to preparation. In particular, exporting the name of a declaration already selected does not process it twice, and a re-export does not process a dependency's declarations in the importing module's preparation group. Nested declarations remain subject to their own scope and checking rules; the export wrapper does not move them into the enclosing group.
The declaration's existing success, failure, runtime-dependence and
This selection does not perform
Parsing and linking apply the rules assigned to those phases before module evaluation. For the remaining judgments that require type evaluation, each
In
A module with no top-level await of its own can still have pending asynchronous dependencies. Its body and pre-body checking wait until the ordinary dependency prerequisites are satisfied. A checking failure follows the existing module evaluation rejection paths and prevents bodies whose execution depends on the failed evaluation from starting. Effects of dependency bodies that already ran are retained. Failure does not restart a dependency or undo its effects.
Within a cycle, a required imported binding may still be uninitialized when this boundary is reached. If preparation of a type declaration or another required pre-evaluation type judgment attempts to read that imported binding and cannot complete for that reason, checking fails with a
The required-input rule also
Before accepting a computed result or publishing successful checking or
A closed type reference to an imported interface that contributes a required pre-evaluation judgment retains the requirement to read the resolved exporting binding even when its structural contract is already known. Availability of that binding is distinct from completion of the interface application. In particular, postponing application formation because a local class argument, including a recursive self argument, has not yet been
The existing
The abstract operation EvaluateModuleBodyWithTypeChecking takes argument code (a
Transfer follows the language's evaluation order. It captures a value before subsequent effects where evaluation does so, and invalidates facts about a mutable location when the specified effect can change that location. A definite lexical write invalidates the binding's current-value facts and dependent relations, then installs only the facts established by the new value. A write through a known alias has the same effect on the aliased location. Replacing a
An unknown call, accessor, conversion, proxy operation, callback,
Suspension,
An expression analysis has a value result, a state after each reachable normal result, and abrupt alternatives. A deciding analysis additionally separates true and false results, or nullish and present results for the nullish operators. Reporting an impossible deciding alternative and computing the alternatives are distinct operations. The exemptions for explicitly written constant tests remain those of
| Form | Required composition |
|---|---|
| Parentheses and negation | Parentheses preserve the result. Negation exchanges the true and false alternatives without re-evaluating the operand. |
| Logical AND and OR | Analyze the left operand first. Analyze the right only under the selecting left alternative. Join the skipped left value with the reached right result; do not equate that value join with the deciding environments. |
| Nullish coalescing and optional chains | Use nullish/present alternatives, not truthy/falsy alternatives. Skipped chain results contribute undefined. Each reached key, argument and call applies its effects before the next link. |
| Conditional expressions | Analyze the condition, then each arm under its own incoming alternative. Join reached normal results and preserve abrupt alternatives separately. |
| Assignments and updates | |
| Comparisons, predicates, patterns and assertions | Use the applicable row of |
| Calls, construction and other operators | Resolve only contracts and identities admitted by the relevant rule. Analyze receiver and arguments in order, apply their effects, enforce applicable contracts, and use only the specified result facts. A signature's return type alone supplies no effect summary. |
| Comma, do, match and pipeline results | Sequence operand effects and transport only each construct's specified completion value. Match guards and patterns use their defined scopes and transfers; a pipeline |
Statement analysis sequences only normal predecessors into the next statement and transports abrupt alternatives to their actual targets. An if joins its reached arms. A switch separates label selection from body fallthrough, uses the
Loops use the bounded write-discovery and widening rules of
A catch receives only the throw alternatives its filter admits and preserves its declared binding contract. A finalizer is analyzed for each pending completion alternative; a normal finalizer preserves that pending completion with the finalizer's resulting facts, while an abrupt finalizer replaces it. Return, throw, break and continue retain their values and targets, with
The initial value of the StaticTypeError property is the intrinsic object
The %StaticTypeError% intrinsic is a built-in name is length is prototype property has value
The %StaticTypeError.prototype% intrinsic is an constructor, name, and message properties have values
A StaticTypeError instance has an [[ErrorData]] internal StaticTypeError binding.
Normative rejection predicates are the rules of this specification, with applicability determined by
Diagnostic text, presentation order, number of reported consequences, and
The registry's index links rule identities to their defining clauses. Several rule identities may share a diagnostic family, including rules with different applicability. Implementations must determine the applicable rule and its source/contract provenance before rendering a diagnostic. Changing diagnostic wording must not change program acceptance, required facts, or the phase of rejection.
This informative index identifies rule families and links to their normative clauses. It does not define an additional rejection predicate. Provisional identifiers may be refined;
| Rule | Diagnostic family | Applicability | Meaning and normative source |
|---|---|---|---|
| rt-default-evaluation | RT_DEFAULT_EVALUATION (provisional) | Everywhere | A required default cannot be evaluated. Supply an evaluable default or an explicit initializer. |
| rt-default-conversion | RT_DEFAULT_CONVERSION (provisional) | Everywhere | The default cannot undergo the declared conversion. |
| rt-where-unsatisfied | RT_WHERE_UNSATISFIED (provisional) | Everywhere | The application fails a declared constraint. |
| rt-unclaimed-metadata | RT_UNCLAIMED_METADATA (provisional) | Everywhere | A metadata key has no governing meta declaration. |
| rt-enum-initializer | RT_ENUM_INITIALIZER (provisional) | Everywhere | An |
| rt-specialization | RT_SPECIALIZATION (provisional) | Everywhere | The call cannot be specialized under the declared constraints. |
| rt-type-evaluation | RT_TYPE_EVALUATION (provisional) | Everywhere | A required type computation cannot complete normally, or a closed type expression does not produce a |
| rt-impossible-test | RT_IMPOSSIBLE_TEST (provisional) | The deciding test necessarily succeeds or necessarily fails under the specified | |
| rt-empty-intersection | RT_EMPTY_INTERSECTION (provisional) | Everywhere | The declared intersection has no inhabitant under the specified |
| rt-assignability | RT_ASSIGNABILITY (provisional) | Everywhere | The established source cannot satisfy the declared destination contract. |
| rt-uninhabited-declaration | RT_UNINHABITED_DECLARATION (provisional) | Everywhere | The declared type cannot have an initialized value. |
| rt-missing-default | RT_MISSING_DEFAULT (provisional) | Everywhere | The declaration needs an initializer because its type has no default. |
| rt-inherited-contract | RT_INHERITED_CONTRACT (provisional) | Everywhere | An implementation or override fails the inherited callable or member contract. |
| rt-generic-binding-order | RT_GENERIC_BINDING_ORDER (provisional) | Everywhere | A parameter constraint or default reads a binding before it is available. |
| rt-variadic-contract | RT_VARIADIC_CONTRACT (provisional) | Everywhere | The variadic declaration or rest boundary does not meet its required type or separation rule. |
| rt-generic-constraint | RT_GENERIC_CONSTRAINT (provisional) | Everywhere | The argument does not satisfy the type parameter constraint. |
| rt-return-contract | RT_RETURN_CONTRACT (provisional) | Everywhere | An established return value cannot satisfy the function return contract. |
| rt-void-use | RT_VOID_USE (provisional) | Everywhere | A void result is used where a value is required. |
| rt-ambiguous-conversion | RT_AMBIGUOUS_CONVERSION (provisional) | Everywhere | More than one applicable conversion is maximal; provide a uniquely applicable conversion. |
| rt-undeclared-member | RT_UNDECLARED_MEMBER (provisional) | Everywhere | The required member is not declared for the established receiver alternatives. |
| rt-immutable-pattern-binding | RT_IMMUTABLE_PATTERN_BINDING (provisional) | Everywhere | The pattern binding is immutable. |
| rt-readonly-store | RT_READONLY_STORE (provisional) | Everywhere | The operation writes a declared readonly member. |
| rt-readonly-layout | RT_READONLY_LAYOUT (provisional) | Everywhere | The operation writes an immutable type-layout property. |
| rt-missing-setter | RT_MISSING_SETTER (provisional) | The established existing built-in accessor has no setter for the reached store. | |
| rt-index-store | RT_INDEX_STORE (provisional) | Everywhere | The index operation supplies neither a setter nor a reference location. |
| rt-string-store | RT_STRING_STORE (provisional) | Everywhere | The operation writes a fixed string position. |
| rt-vector-lane-store | RT_VECTOR_LANE_STORE (provisional) | Everywhere | A vector store names a component more than once. |
| rt-duplicate-member | RT_DUPLICATE_MEMBER (provisional) | Everywhere | A repeated member conflicts with the declared storage contract. |
| rt-index-signature | RT_INDEX_SIGNATURE (provisional) | Everywhere | An index signature, indexed type, or index operand does not meet its declared key contract. |
| rt-partial-class | RT_PARTIAL_CLASS (provisional) | Everywhere | The partial class |
| rt-meta-declaration | RT_META_DECLARATION (provisional) | Everywhere | The meta declaration cannot be completed: its |
| rt-computed-member-name | RT_COMPUTED_MEMBER_NAME (provisional) | Everywhere | The computed name is not an admitted constant member key. |
| rt-narrowing-declaration | RT_NARROWING_DECLARATION (provisional) | Everywhere | The declared narrowing target does not name a parameter of this callable. |
| rt-layout | RT_LAYOUT (provisional) | Everywhere | The declared storage cannot have the required |
| rt-overload-declaration | RT_OVERLOAD_DECLARATION (provisional) | Everywhere | The declared overloads overlap in a prohibited or ambiguous way. |
| rt-implements-contract | RT_IMPLEMENTS_CONTRACT (provisional) | Everywhere | The implements target is invalid, or an implemented member is missing or does not satisfy the target's member contract. |
| rt-generic-argument | RT_GENERIC_ARGUMENT (provisional) | Everywhere | The application does not supply the required kind, count, or inferable generic arguments. |
| rt-protected-member | RT_PROTECTED_MEMBER (provisional) | Everywhere | The member is protected at this access. |
| rt-promise-protocol | RT_PROMISE_PROTOCOL (provisional) | Everywhere | The established then contract cannot accept the assimilation callbacks. |
| rt-iterable-required | RT_ITERABLE_REQUIRED (provisional) | Everywhere | The required |
| rt-compile-time-evaluability | RT_COMPILE_TIME_EVALUABILITY (provisional) | Everywhere | The required type computation is outside the compile-time-evaluable fragment. |
| rt-parameter-order | RT_PARAMETER_ORDER (provisional) | Everywhere | The required positional parameter cannot follow this default without an unambiguous binding. |
| rt-member-default | RT_MEMBER_DEFAULT (provisional) | Everywhere | A member with a default must be optional. |
| rt-type-required | RT_TYPE_REQUIRED (provisional) | Everywhere | The type position or annotation does not denote the required type category. |
| rt-integer-width | RT_INTEGER_WIDTH (provisional) | Everywhere | The |
| rt-array-extent | RT_ARRAY_EXTENT (provisional) | Everywhere | The array extent or type argument list is invalid. |
| rt-composite-type | RT_COMPOSITE_TYPE (provisional) | Everywhere | The requested composite has an inadmissible component type or key. |
| rt-weak-target | RT_WEAK_TARGET (provisional) | Everywhere | The declared weak collection cannot hold this value category. |
| rt-explicit-conversion | RT_EXPLICIT_CONVERSION (provisional) | Everywhere | The explicit conversion is not defined for these types. |
| rt-constructor-required | RT_CONSTRUCTOR_REQUIRED (provisional) | The established target cannot be constructed. | |
| rt-range-value | RT_RANGE_VALUE (provisional) | Everywhere | The range has an inadmissible endpoint or produces a value outside its declared domain. |
| rt-numeric-range | RT_NUMERIC_RANGE (provisional) | Everywhere | The established value is outside the required numeric or literal domain. |
| rt-numeric-operator | RT_NUMERIC_OPERATOR (provisional) | Everywhere | The numeric categories do not admit this operator together. |
| rt-disjoint-comparison | RT_DISJOINT_COMPARISON (provisional) | The established operands share no value for this comparison. | |
| rt-zero-divisor | RT_ZERO_DIVISOR (provisional) | Everywhere | The literal divisor is zero where the |
| rt-shift-distance | RT_SHIFT_DISTANCE (provisional) | Everywhere | The |
| rt-required-member | RT_REQUIRED_MEMBER (provisional) | Everywhere | The value omits a required declared member. |
| rt-constant-condition | RT_CONSTANT_CONDITION (provisional) | The deciding value has only truthy or only falsy alternatives. | |
| rt-impossible-assertion | RT_IMPOSSIBLE_ASSERTION (provisional) | The asserted | |
| rt-unreachable-operand | RT_UNREACHABLE_OPERAND (provisional) | The deciding left operand necessarily skips the right operand. | |
| rt-pattern-domain | RT_PATTERN_DOMAIN (provisional) | Everywhere | The pattern cannot match or unambiguously describe the required position. |
| rt-matcher-contract | RT_MATCHER_CONTRACT (provisional) | Everywhere | The custom matcher is absent or has an incompatible callable/result contract. |
| rt-unreachable-pattern | RT_UNREACHABLE_PATTERN (provisional) | No remaining value can reach this pattern clause. | |
| rt-nonexhaustive-match | RT_NONEXHAUSTIVE_MATCH (provisional) | Everywhere | The match omits a required case or |
| rt-unreachable-default | RT_UNREACHABLE_DEFAULT (provisional) | Earlier cases cover every admitted value, so the default cannot be selected. | |
| rt-pattern-scope | RT_PATTERN_SCOPE (provisional) | Everywhere | The binding-carrying pattern is outside its governing position or scope. |
| rt-return-inference | RT_RETURN_INFERENCE (provisional) | Everywhere | Return inference is proved not to converge to a |
| rt-inference-limit | RT_INFERENCE_LIMIT (provisional) | Everywhere | Return inference exhausted a |
| rt-unproductive-type | RT_UNPRODUCTIVE_TYPE (provisional) | Everywhere | The alias only defines itself and produces no type. |
| rt-reference-location | RT_REFERENCE_LOCATION (provisional) | Everywhere | The operation requires a referenceable location, not merely a value. |
| rt-fixed-extent-store | RT_FIXED_EXTENT_STORE (provisional) | Everywhere | The operation changes a fixed sequence extent. |
| rt-property-receiver | RT_PROPERTY_RECEIVER (provisional) | The established receiver cannot support the required property operation. | |
| rt-conversion-protocol | RT_CONVERSION_PROTOCOL (provisional) | Everywhere | The selected |
| rt-iterator-close | RT_ITERATOR_CLOSE (provisional) | Everywhere | The reached |
| rt-named-argument | RT_NAMED_ARGUMENT (provisional) | Everywhere | Named and positional arguments cannot bind the signature as written. |
| rt-builtin-argument | RT_BUILTIN_ARGUMENT (provisional) | The established arguments cannot satisfy the added contract of the existing built-in operation. | |
| rt-atomic-argument | RT_ATOMIC_ARGUMENT (provisional) | Everywhere | The |
| rt-typed-array-store | RT_TYPED_ARRAY_STORE (provisional) | Everywhere | The established intrinsic array store cannot convert to the declared |
| rt-call-argument | RT_CALL_ARGUMENT (provisional) | Everywhere | No argument binding can satisfy the required callable contract. |
| rt-ambiguous-call | RT_AMBIGUOUS_CALL (provisional) | Everywhere | Multiple declared signatures remain applicable without a unique selection. |
| rt-not-callable | RT_NOT_CALLABLE (provisional) | The established callee is not callable. | |
| rt-decorator-argument | RT_DECORATOR_ARGUMENT (provisional) | Everywhere | The decorator cannot accept its written arguments and object context. |
| rt-reference-argument | RT_REFERENCE_ARGUMENT (provisional) | Everywhere | The argument does not supply the required reference or |
| rt-proxy-storage | RT_PROXY_STORAGE (provisional) | Everywhere | A Proxy cannot wrap the specified protected |
| rt-collection-seed | RT_COLLECTION_SEED (provisional) | Everywhere | A surviving seed entry cannot satisfy its declared collection element contract. |
| rt-this-required | RT_THIS_REQUIRED (provisional) | Everywhere | The call lacks the receiver required by the declared method contract. |
| rt-duplicate-operator | RT_DUPLICATE_OPERATOR (provisional) | Everywhere | The operator overlaps an already declared primitive operation. |
| rt-empty-iteration | RT_EMPTY_ITERATION (provisional) | The established source has no element, so the loop body cannot execute. | |
| rt-reference-iteration | RT_REFERENCE_ITERATION (provisional) | Everywhere | The iteration cannot supply references to its elements. |
| rt-disposal-contract | RT_DISPOSAL_CONTRACT (provisional) | Everywhere | The selected disposal hook cannot satisfy the required call contract. |
| rt-instanceof-protocol | RT_INSTANCEOF_PROTOCOL (provisional) | An added instanceof protocol requirement cannot be satisfied in | |
| rt-instanceof-declared-call | RT_INSTANCEOF_PROTOCOL (provisional) | Everywhere | Every selected callable instanceof hook violates its declared receiver or parameter contract. |
| rt-object-operand | RT_OBJECT_OPERAND (provisional) | The operator requires an Object right operand. | |
| rt-catch-order | RT_CATCH_ORDER (provisional) | Everywhere | An untyped catch must be the final handler. |
| rt-unreachable-catch | RT_UNREACHABLE_CATCH (provisional) | Previous catch filters admit every value this handler could accept. | |
| rt-enum-case | RT_ENUM_CASE (provisional) | Everywhere | The |
| rt-unreachable-range-case | RT_UNREACHABLE_RANGE_CASE (provisional) | Earlier range labels cover every value this label could select. | |
| rt-declaration-inference | RT_DECLARATION_INFERENCE (provisional) | Everywhere | The declaration requires a static type that its initializer does not establish. |
| rt-reference-rebinding | RT_REFERENCE_REBINDING (provisional) | Everywhere | The destination is not a rebindable reference binding. |
| rt-typed-delete | RT_TYPED_DELETE (provisional) | Everywhere | The operation deletes protected |
| rt-abstract-construction | RT_ABSTRACT_CONSTRUCTION (provisional) | Everywhere | An abstract class cannot be constructed. |
| rt-declared-zero | RT_DECLARED_ZERO (provisional) | Everywhere | The |
| rt-superclass-contract | RT_SUPERCLASS_CONTRACT (provisional) | Everywhere | The |
| rt-ambiguous-result | RT_AMBIGUOUS_RESULT (provisional) | Everywhere | Several comparison result forms remain possible; supply a result context. |
| rt-operator-argument | RT_OPERATOR_ARGUMENT (provisional) | Everywhere | No declared operator signature accepts these operands. |
| rt-ambiguous-operator | RT_AMBIGUOUS_OPERATOR (provisional) | Everywhere | More than one declared operator signature remains applicable. |
| rt-parameter-kind | RT_PARAMETER_KIND (provisional) | Everywhere | The written parameter domain does not determine an admitted parameter kind. |
| rt-specialization-contract | RT_SPECIALIZATION_CONTRACT (provisional) | Everywhere | The specialized declaration does not preserve its |
| rt-type-default-identity | RT_TYPE_DEFAULT_IDENTITY (provisional) | Everywhere | A shared type default must have value semantics. |
| rt-reference-permission | RT_REFERENCE_PERMISSION (provisional) | Everywhere | The reference operation violates its access or lifetime permission. |
| rt-vector-argument | RT_VECTOR_ARGUMENT (provisional) | Everywhere | The vector application does not meet its element, extent, or lane contract. |
| rt-type-evaluation-limit | RT_TYPE_EVALUATION_LIMIT (provisional) | Everywhere | The specified semantic work limit is exhausted; the computation supplies no fallback type. |
| rt-proposal-construction | RT_CONSTRUCTOR_REQUIRED (provisional) | Everywhere | The established target cannot be constructed. |
| rt-declared-call | RT_NOT_CALLABLE (provisional) | Everywhere | The established callee is not callable. |
This clause adds the syntax for writing a type. It is grammar only: it defines what may be written and where, while the meaning of each form is given by the clauses that follow. Every production here is
Two punctuators are added. Neither
.< introduces a type argument list (. must be followed by an identifier name, a private name, or a numeric part of a literal, so .< is never produced by valid source text. The leading . is what distinguishes generic application from a comparison: a.<b>(c) cannot be read as a < b > (c), which is the ambiguity that prevents the bare angle-bracket form. Because a numeric literal is lexed by longest match, 1.<2 still lexes as the literal 1. followed by <, and so continues to mean a comparison.
:= introduces a : immediately followed by = does not occur in valid source text.
A type argument list is delimited by < and >, and type arguments nest, so a source text may end one list immediately inside another. Under the ordinary lexical goals the two closing punctuators would be lexed as a single >> token. A > is always a single punctuator.
The .< or < and its closing >. When this >>, >>>, >=, >>=, and >>>= are not recognized; a > is lexed as the single-character punctuator >.
This is the same device the specification already uses to lex / differently where a regular expression may appear. It resolves the nesting problem lexically rather than by a parser rule, so Map.<string, [].<uint8>> closes both lists. A shift operator inside a type argument list must be parenthesized, which is only relevant to a value argument, since a shift cannot otherwise appear in a type.
An imaginary literal is a numeric literal with the suffix i, denoting a value on the imaginary axis. The suffix occupies the n suffix of a BigInt literal already uses.
Unlike the BigInt suffix, which attaches only to an 4i, 2.5i, and 1e3i are all imaginary literals. The rule that the source character following a numeric literal must not be an identifier start continues to apply, so 4if remains a syntax error. The type of an imaginary literal is defined by the complex numbers extension.
A
The optional | and & above are a leading SEPARATOR and carry no meaning: | _T_ is the type T, whether or not T is itself a union, and likewise for &. They exist so that a type written across several lines can align its members, as the type Shape = followed by a | on each line. Being on the base case of each production rather than a production of their own, they may appear at most once and only before the first member, so _A_ | | _B_ has no parse. A leading token with no type after it has none either, the token being a separator that happens to come first rather than a prefix operator.
(string) => uint32 | null returns uint32 | null. A function type is therefore not by itself a member of a union or an intersection, and putting one in a union requires parenthesizing it. That is true on either side, so the order of a union's members never matters.
void and null are undefined is not a undefined in type position always names the undefined type rather than depending on the global binding being in scope, matching the treatment of null. The other built-in type names, uint8 and the rest, are ordinary identifiers and are reached through uint8 is unaffected.
ref uint8 | null is a union of ref uint8 and null. ref is a ref followed by a token that cannot begin a type is an identifier.
keyof is likewise a keyof A | B is the union of keyof A and B. Its result is the union of the operand's key types, one literal type per named member. The full definition over each kind is given by
An intersection with callable constituents retains their signatures for calls, including optional calls and tagged templates. Applicable signatures use ordinary signature selection and argument, reference-parameter and result checks. Non-callable constituents still constrain the same value through their members and membership rules; they do not erase the callable contract, and the intersection is not treated as a union of possible callees.
never.never, and the two property types are disjoint (never, and neither is a subtype of the other (meet hook (never. A meet, or whose meet returns The second rule reaches THROUGH a member, and is the same rule as the first rather than an addition to it. Canonicalization distributes an all-
The exemptions are the first rule's, for the first rule's reasons. A member written never states the emptiness deliberately, so { a: never } & { a: never } is the annihilation identity of { a?: uint32 } & { a: string } is inhabited by every value with a string a and by none with a uint32 one, which is a narrowing rather than a contradiction.
The message names the property as well as the two types, since a reader given only the types has to find which of the arms' members they came from, and an intersection of wide object types may declare many.
The third rule decides what Symbol.hasInstance may be overridden so that instanceof answers otherwise, which does not bear on this: the override does not reach Reflect.typeOf or the is operator, which are what the type system reads.
It is decided HERE and not in that operation, and the reason is the cost that clause names.
The rule is restricted to CLASS declarations, and must be: I & J is inhabited by any value with the members of both; a class may implement any of them, so A & J is inhabited by an instance of a class that does; and a class intersected with an
Canonicalization already reduces such a type to never (& that made it - which is how an erasure checker reports it and is the least useful place to say it. The members are written right there, and the message names them.
This is the call & written where | was meant is the mistake this catches, and it is the common one.
The rule is on the SYNTAX, so a never a computation reaches is untouched. Reflect.makeType (never and does not throw, because canonicalization is total and the kit computes with the empty type: union(« ») and an exclude that removes every member are never, and a builder that threw instead would put a guard in every kit function. A generic body is likewise not rejected: in type F<T: type> = T & string; the member T is a number reduces to never silently.
The reduction and this rule are decided over the same types, but only where the members have been RESOLVED. Canonicalization reduces every disjoint intersection whatever the never either way, so the failure mode is a MISSED diagnostic and never a wrong one: such a program hears about its mistake from the use
An implementation should resolve as far as it can, and the reach is easy to underestimate. A parameterization is resolved by its BASE, and the base may be named rather than written — a nested brand, a brand over an object, an array or a literal — so an implementation that consulted only the built-in names diagnosed string.<{ brand: *"E"* }> & uint8 and missed E.<{ brand: *"N"* }> & uint8, two annotations differing in nothing but what named the base. What an implementation may not do is report on a member it has NOT resolved, an unresolved type being one that may still turn out to be anything: a base that declares type parameters is being applied rather than refined (
A member that never is exempt because writing it says what the rule is for saying. uint8 & never is the annihilation identity of never itself, which is admitted as a type name on the same terms as every other. The exemption is on a member DENOTING never, so an alias for an already-reported empty intersection is reported once at its own declaration and not again at each mention of it.
A type reference names a type, optionally applying type arguments to it. Generic application always uses .<, in a type and in an expression alike.
A source class declaration's denotation in a type reference follows
Because .< is a single punctuator, a qualified name and an application are told apart lexically: in Temporal.Unit the . is followed by an identifier, while in Map.<string, uint8> the .< opens an argument list, so a
A type argument is written as a int.<8> and the lane count in vector.<float32, 4> are. A numeric or string literal argument is a where clause (
A Box.<Element: uint8>, which lets an application skip a parameter that has a default rather than repeat it: given Grid<T = float64, Rows = 4, Cols = 4>, Grid.<Cols: 8> says what differs where Grid.<float64, 4, 8> repeats what does not. The separator is : rather than = to match the named ARGUMENT form of a call, and because = already means "the default if none is supplied" in the declaration - the opposite sense at the same position.
Arguments bind to parameters by swizzle.<...Pair> for type Pair = [0, 1] is swizzle.<0, 1> and names the same
It is a [4].<uint8>, whose extent and element the grammar does not name - so a named argument there names nothing, and the form alone shows it. The first is a
It is a Syntax Error for two q.<I: 0, 1, N: 8> binds I to [0, 1], since a name that could supply a pack only one element would not be able to supply it at all. Both need the applied declaration, the second to know whether the name is variadic, and so are type errors for the reason the unknown name is; only the duplicate name is visible in the list alone.
The standard library's generic types carry the parameter names their signatures are written with, listed in Map.<V: uint8, K: string> means what it says and Map.<Z: uint8> is the error a user declaration's unknown name is.
The table is the names the clauses cited already write, collected so an application can use them; a generic whose signature names nothing, such as an
A dependent built-in application retains its family-specific formation obligations. When substitution closes an application, validate it before publishing the resulting type, including through nested aliases, object members, container arguments and function signatures in unused declarations. In particular, the Composite and the lane and count restrictions on vector apply equally to direct and substituted applications. An open application remains deferred; it does not authorize a closed invalid application. Traverse recursive type graphs with cycle detection and distinguish parameters by their binding identities. These checks form types without evaluating a value
Explicit applications of the following library declarations use these parameter lists. Every parameter has domain type. A blank default entry means that an explicit application must supply the parameter. Bare names and constructions without
| Declaration | Parameters in order | Defaults |
|---|---|---|
Map, WeakMap | K, V | None |
Set, WeakSet, WeakRef, FinalizationRegistry, Proxy | T | None |
Promise | R, E | R = any, E = any |
Generator, AsyncGenerator | Y, R, N | R = void, N = void |
Expand stated-extent spreads, bind positional and named arguments and fill declared defaults before interning a type or consuming its arguments. Excess arguments, a missing required argument, an unknown label, a parameter supplied positionally and by name, or a positional argument after a non-variadic named argument is a Iterator's wrapper parameter.
A type position admits a call. The callee is written as a type reference and the arguments as ordinary expressions, so partial(User) and pick(User, ['id', 'name']) are types, as is a call of a call where a function returns a function that computes one.
A
The form is ( derived nothing before this production, so no program written against the earlier grammar changes meaning either. Disambiguation costs one token: after a type reference, .< opens type arguments, ( opens a call, and any other token ends the reference.
Only a call is admitted, deliberately. The
The same call in expression position is an ordinary call and needs nothing from this clause: a builder is an ordinary function, and calling it at run time yields the same interned
A
The abstract operation KeyTypesOf takes argument t (a never where t has no keys. It performs the following steps when called:
The first step is the one that makes K: keyof T a constraint with content: within the declaration that binds T nothing is known about its keys, so they are UNKNOWN rather than absent, and the operator is carried as a T[K] and keyof T was the empty type, which made the constraint empty and every body written against it unwritable. These are the rules of the operator, and the only statement of them: keyof is a primitive of the core ('x' survive against an arm whose keys come from a string-keyed index signature. An intersection's keys are the union of its members' keys. A Symbol key contributes its own literal type (keyof sees through a brand and through the where of a dependent record type (keyof and the property mappers agree about a refined object. An interface has the keys of its never, the empty uint8 is empty, which is a thing this operation can say, and saying it is what keeps keyof T usable in a generic whose T is later bound to such a type — the alternative reported the fault inside the generic's own declaration rather than at the call that chose T. keyof never is the same answer by a different route: never is the empty keyof written directly on a type with no keys is almost always a mistake and is worth flagging as one, but it is not a
A deferred indexed access retains its formation obligation through aliases, nested types and generic substitution. When substitution closes its operands, apply
An
It is stated as an abstract operation, as indexed (function pluck<T: type, K: keyof T>(o: T, key: K): T[K] — a type operator, type T["n"]. What the builder gives is what any kit function gives: a value to pass, compose, and map with, which syntax cannot be.
Reading a property that a declaration marked optional yields the union of its type with undefined, which is what reading an absent property gives. { a?: string }["a"] therefore admits a String and *undefined*, and { a: string }["a"] admits only a String.
The form is POSTFIX, so it binds tighter than the prefix operators and than union and intersection: keyof T["a"] is keyof (T["a"]), and T["a"]["b"] chains left to right. That is why it sits at its own level between
Only a String literal key is admitted in this edition. A numeric key, an index signature, and a tuple index are each a T[number] is told that this edition does not cover it rather than discovering an implementation's guess. The restriction is a
That restriction is also what keeps this form clear of the INDEX ACCESSORS (
Where a later edition admits a numeric key here, it meets the accessor's domain exactly, and the question it must answer is what Grid[uint8] Grid declaring operator [](i: uint8): string — the accessor's RETURN type is the only reading that makes the two spellings describe one operation, as T["a"] and a property read already do. Deciding it then rather than now is the point of stating the restriction as an error: nothing has to be un-said.
The abstract operation IndexedTypeOf takes arguments t (a
undefined type » }) to results.Where a type parameter is involved the access is DEFERRED rather than computed or refused - IndexedTypeOf returns the T, nothing is known about it, so T[K] names an access that will be performed at each any, and to its operator's bound T[keyof T], and nothing concrete relates to it - which is what makes function pluck<T: type, K: keyof T>(o: T, key: K): T[K] { return o[key]; } well return 5 from the same signature is not. The EXPRESSION o[key] reaches this operation too, so both spellings of one access answer one type; that agreement is the whole reason the deferred form is stated here rather than left to each resolver.
Returning indexed reports which of them it was. A class instance type is not reached: it is C["n"] is I["n"] for an interface is not.
A declared tuple default is evaluated and converted to its
For a tuple access with a proven singleton numeric key, a required position before any rest has that position's declared const (any, and preserves exact
An array type is a fixed or dynamic count of elements of one type, and a tuple type is a fixed count of individually
[] alone is the EMPTY TUPLE. The array of any is written [].<any>, and it is the bound of the array and tuple family, so a type parameter constrained by it is satisfied by any array or tuple. Each structural family has such a bound and each is written in that family's own syntax: {} for objects, [].<any> here, and (...a: [].<any>) => any for functions. A bound admits every member of its family and nothing outside it, so a function is not assignable to {} and an array is not assignable to the function bound; and each composes, (...a: [].<any>) => string being every function that returns a String, which a bare name for the family could not express. [] denoted that bound in an earlier draft, which left the empty tuple with no spelling at all and gave the terse form to the role that turned out never to be written: across this [] in bound position appears zero times, [] meaning the empty tuple about thirty, and the builder call that was the only way to denote an empty tuple zero. The reassignment also removes a silent misreading rather than a diagnostic - [] was not an error under the old reading, it was a DIFFERENT type, so a program written by a reader who expected the empty tuple compiled and meant something else. [].<T> is a dynamic array, [N].<T> a fixed one, and [T1, T2] a tuple. What the brackets carry is the EXTENT — whether it is stated, and what it is. Whether the value owns its storage is a different question and is not asked here: Span.<T> (
The first three alternatives and the fourth are distinguished only after the closing ], by whether a [uint8] therefore cannot be resolved by bounded lookahead. This specification resolves it with a cover grammar, as it does for parenthesized expressions and arrow parameters: the bracketed text is parsed under a cover production and refined to an extent expression or a tuple element list once the token after the closing ] is known, an extent when a
Determine default placement in the effective [number = 7, ...[].<string>] admits an empty input but does not admit a one-element String input. The latter supplies position zero and cannot skip it to reach the rest. A
A tuple element's [[2].<uint8> = [1, 2]] is therefore a default and [any = []] is not.
For a ... ...[].<T>. A union canonicalizing to a single array or tuple is judged as that record.
Expanding a tuple rest preserves every element's formed [[Initial]] value. A still-pending initializer obligation retains its original source and lexical/generic environment through expansion, canonicalization and substitution. A same-spelled parameter in the spreading or receiving context does not rebind it; a parameter's constraint is not its argument value. Validate each closed default's evaluability and conversion at type formation even if uses supply the position. Receiving construction consumes the already formed values under the existing fragment-budget, value-copying and admission rules; spreading itself does not re-evaluate the initializer. A pending generic input is identified by its parameter declaration; a consumer's same-spelled parameter does not supply that input. Source nodes and checking environments are not additional public
An array in a tuple position is converted position-wise, as one in an array position is converted element-wise: each element is converted to the type of the position it occupies, and an element past the declared positions is converted to the
A fixed extent is part of the type and does not change. An operation that would give an array of such a type a length other than its extent is a length, or a method that appends or removes. A request for capacity past the extent is refused for the same reason although it changes no length: the storage of a fixed extent holds exactly that many elements for the life of the array, so reserve past the extent is a reserve within it is a no-op, and the capacity of such an array is its extent rather than any allocation's. Stating the rule in terms of length alone would miss this, reserve being exactly the operation a length comparison cannot see (byteLength from it and an array view's byteElementLength defaults from it, so an extent that an ordinary store could change would not be constant for either. A growable [ ] . < T > has no extent to hold and grows as an Array does.
One type describes every count a CONTAINER reports or accepts: an array's length, its capacity (size of a keyed collection (uint64. Its full range is ALLOCATABLE AND USABLE: an array type is not bounded by the maximum length of an ordinary Array, since a count type wider than what the container can hold would describe lengths no program could reach. An instance is an Array in its methods and its element behaviour and not in its extent limit. The width is uint64 rather than uint32 because a view's length comes from its buffer and is not bounded by the maximum length of an Array (%TypedArray% is unaffected: its counts are Numbers and remain so (uint32 at each capacity comparable with length without a conversion — the invariant that a capacity is at least a length is unstateable if the two are not one type.
A store to the length property of an array carrying an array or tuple type is a
The fixed-extent judgment also applies to a built-in update or compound assignment whose result can be determined from the exact storage extent and its operands. For example, a.length++, --a.length and a.length += 1 on a [2].<uint8> are type errors, whereas a.length += 0 preserves the extent. The computation uses the length a literal type. A tuple whose positions are all required and have no rest similarly supplies an exact length. Where the current length or an operand is unknown, or a declared operator supplies different semantics, the corresponding judgment is deferred; a successful count judgment never removes a reference-liveness check.
For such a
The uint64-over-uint32 argument above is about a VIEW, and does not reach the keyed collections, which have no view form. The other property does, and is why their size is this type rather than one of its own: counts have to be comparable ACROSS containers as well as within one. _map_.size < _array_.length is a sentence a program wants to write, and it is unwriteable if the two are different types, exactly as "a capacity is at least a length" is unwriteable if those two are. A per-container count type would make that comparison impossible by construction, which is the defect naming the type once exists to prevent, and no language that has been surveyed has one: Rust reports usize from both Vec::len and HashMap::len, and C++, Java, Swift and C# each use a single
An array type may be written where an expression is expected, and there new [100].<uint8>() constructs an array of that type, and class G extends [16].<uint8> { } derives a class from it. A construction of a fixed extent has that many elements, each the default value of the Array.prototype apply, and a class deriving from an array type has instances that are Arrays in the same way. Its [[Prototype]] is not %Array.prototype% itself but an object that INHERITS from it and adds the capacity operations (Array.prototype reaches an instance unchanged while capacity, reserve, and shrinkToFit reach only an array that has an Array.prototype would put three members on every array in the language that exist to refuse the receiver they will usually have: 'capacity' in [] would be
The bracketed form is an array literal where an expression is expected, so the two readings meet at [100].<uint8>`: an array literal cannot be followed by a type argument list, which is what tells them apart. The reading as a type is taken before the brackets are evaluated as a literal, since evaluating them is the reading being replaced.
shared before a type marks a type whose storage is shared between the threads of one heap, as let a: shared [4].<uint8>, a fixed-length array so placed. It is a shared is itself shared. Which types admit the modifier, what it places where, and what a checker may assume of a
For a proved intrinsic tuple mutation, check the values stored at each established destination reverse exchanges paired positions, copyWithin copies in its prescribed direction, fill writes only its selected range, and splice checks its established shifts and inserted positions. Account for permitted value conversions and subsequent reads of converted values. A no-op range performs no stores. Unknown indices, effects or source values retain runtime checks; a comparator's mere presence does not establish a sort permutation. These store checks do not prove fixed extent, deletion permission or reference liveness.
At a proved intrinsic push, pop, shift, unshift or splice on a fixed-extent array or a tuple, it is a push or unshift, a pop or shift on an empty array, or a splice with equal inserted and removed counts is not rejected by this extent judgment. Unknown counts, including the count a spread argument supplies, and replacements remain dynamic. Equal extent does not waive element, storage, bounds or reference-liveness checks.
The judgments in this clause apply only when the selected operation is established to be the corresponding Array intrinsic. A receiver's array type and a familiar member name alone do not establish that fact: own methods, prototypes, accessors, argument evaluation and user conversions may change the selected operation. A bounded proof may use engine-owned intrinsic identities, lexical origins and source effects; an unresolved dependency defers the judgment. A lexical origin follows let binding qualifies while no assignment, assignment pattern, eval can replace it, and a declaration without an
For with, the
A supplied comparator for sort or toSorted must be callable and accept the two element values supplied by the intrinsic. Omission or undefined selects the default comparison. The callback result follows the base language's numeric coercion; it is not required to have a declared number return type, and no ordering law is proved. A callback for reduce or reduceRight must accept the accumulator, element, index and receiver actually supplied. The initial value and callback results determine the accumulator contract independently of the
An intrinsic that creates an ordinary array copy, including slice, filter, toSorted, toReversed, toSpliced, with, splice and concat, does not give that copy the receiver's fixed storage extent. A homogeneous copy has a dynamic array type with the element contract that the operation establishes, and an item the operation inserts, as with and toSpliced do, is converted to that contract and checked against it where it is established. Knowledge of its initial length is a value fact, not a fixed storage type. Mutating sort and reverse return the receiver. A tuple result retains positional information only where the operation guarantees the resulting positions; otherwise no positional
A computed member read whose key is a statically established
Every closed declared property default, including one in an interface, is evaluated and converted to its declared property type at type formation, under the same rules as a tuple default. Failure is a readonly modifier is admitted only on named properties; readonly before an readonly is unaffected.
At a fresh object literal, a property not declared by name must satisfy every index signature admitting its established key. Its one produced value has one representation; contextualize at the conjunction of those value types, rather than converting independently for each signature. It is a
An object type describes a value by its members. It is the inline form of an interface.
It is an { n: int32, n: string } and { n: int32, n: int32 } are both refused, the second although its halves agree.
The rule is about the DECLARATION and not about inhabitability: a repeated member is a mistake at the point it is written, and refusing only the contradictory spelling would accept the redundant one while a duplicate BINDING name a scope out is refused whatever it is bound to.
Merging two declarations is a separate question with its own answer (
The relationship runs both ways, and the reverse direction needs a name. The structural form of an interface type is the
This is what lets a value satisfy an interface by having its members, which f({ a: 'a' }) for interface IExample { a: string; }, since an interface is a
Members are separated by either , or ;, and a trailing separator is permitted, so both { x: float32, y: float32 } and { x: float32; y: float32; } are written. A ? marks an optional member.
A { (uint32): uint32 } is (uint32) => uint32 written with braces, { (uint32): uint32; (string): string } is the overloaded function type the arrow form cannot spell, and { <T: type>(x: T): T } is a generic one (
A member may be declared readonly, which sets the [[Readonly]] readonly member is a readonly member. readonly is shallow here as it is for a
This is what makes the depth covariance of readonly member "since a value read from it and never written through it need only be of the required type", and the second half of that sentence is a rule rather than an observation.
The guarantee is weaker than a readonly is a property of the OBJECT: the declaring class says so, the instance carries it, and a write is refused however the instance was reached. An object type's readonly is a property of the REFERENCE, since one object may be viewed through both a readonly and a writable type and the two views are the same object. The refusal is therefore what the type of the base expression supports, and a write through a base of type any is not refused.
An { [key: string]: uint32 } is the type whose remaining string-keyed properties, beyond those declared, are uint32. It is a ?, since a default belongs to an optional member, the c?: uint8 = 0. A member default's value copies, under the rule and for the reason c?: any = [] is a let v: S = { id: 1 } fills a declared default and let o = { id: 1 }; let v: S = o; does not, which is the same distinction freshness already makes about excess members and about literal types, rather than a second rule to learn. A default is therefore never written onto an object a program can already observe: the only object it reaches is one coming into existence at the type. It is a string, symbol, uint32, or a union of these. A key domain dependent on an unbound parameter retains this well-formedness obligation. Recheck the resolved domain when substitution closes it, including through nested aliases and interface applications, before publishing the resulting type. An open parameter is not evidence that a closed invalid domain is admissible. A member the
An earlier draft of this clause required a declared member to be assignable to the signature's { name: *"n"*, x: 1 } IS of { name: string, [k: string]: number }. A rule refusing that annotation would leave a type no program can write whose membership predicate nonetheless accepts values, and
What TypeScript buys with its rule is a sound type for a read through a computed key, o[k] for a k of the key type, which under this reading may land on a declared member of another type. This specification does not need it: a general non-
Calling a new, or as the target or newTarget of a proved Reflect.construct. Preserve the actual value's capability through established aliases and specializations. A class type; that annotation alone proves neither constructibility nor non-constructibility. Explicit array construction and placement construction retain their own rules. Unknown identities and explicit
A function type is a ref and rest modifiers a declaration's parameters carry.
The collected-container well-formedness rule of
A signature does not establish whether its function value can be constructed. It is a new, including through an optional call or a tagged template. Ordinary function construction remains admitted. A function
An optional call applies the invocation-kind judgment to the selected callee value on its executing edge. A nullish split does not erase an established class-
A conditional selection is a ||, && or ?? operator, and its possible selected origins are the operands it can yield: each of these yields one of its operands unchanged, except that && yields its left operand only when that operand is falsy. The left operand of && therefore contributes the
Identification uses the function or class expression itself, or a lexical binding whose value origin is established. Parentheses and immutable aliases preserve that origin. A mutable origin is usable only while eval withdraw an otherwise unproved origin. Shadowing uses the binding's lexical identity. An open property, an unknown function value, a signature alone, an overload dispatcher of unknown invocation kind, or a decorator that can replace a class supplies no negative capability proof. These facts are separate from signature identity and subtyping. A class
These are new (() => 1)() retains its runtime exception, as do unknown values and values deliberately exposed through
A (a: uint8 = 1) => void, or in an interface of call signatures (string = '5', named: uint32). The ? follows one with an (string = '5', named: uint32) relies on. A parameter without a name is reached only by position, and a call that reaches it has supplied the defaulted position too, so that default could never be taken; this is the reason
When the production ?, or a ... is not a ref
(uint8) is a parenthesized type and (uint8) => uint8 is a function type, and the two are told apart by the token after the closing parenthesis, exactly as a parenthesized expression is told from arrow parameters. The cover grammar is the same device the specification already uses there.
A function type may declare type parameters, <T: type>(x: T) => T, and is then the type of a generic function: its where clause: a where belongs to a declaration and is not part of a signature's identity, so it is checked at the
An established upper bound supplies operational contracts inside a generic body: member reads, store types and permissions, callable signatures, primitive operations and narrowing tests (
A generic declaration introduces its parameters in angle brackets. Declaration uses <, while application uses .<, so the ambiguity that application avoids does not arise at a declaration, where no expression may appear. The same brackets after a declared name hold the PARAMETERS of a primary declaration or the ARGUMENTS of a : declares a parameter, and every other entry is an argument. Whether a name is in scope never decides it.
Every parameter states its domain. T: type declares a type parameter, since type is the domain of Type Objects and a type parameter is a V: uint32 declares a value parameter, and N: uint32 = 4 gives it a default. A bound follows the domain, T: type extends Ordered.<T>. A ...I: [].<uint32> for a pack of uint32 constants and ...Ts: [].<type> for a pack of types, and a bound on a pack of types bounds the tuple, ...Cs: [].<type> extends [].<Component>. A W<_>: type, and the domain describes what an application of it yields (_ of a _.
Earlier drafts declared a type parameter with a bare name, <T> or <T extends B>, and treated a name that resolved to a type as a <T> nor <T extends B> declares anything: the first is a reference to whatever T is in scope, and the second is a Syntax Error, since extends does not follow an argument. An implementation's diagnostic for either offers T: type or T: type extends B.
A out declares it covariant and in contravariant, and a parameter carrying neither is invariant. The words name the positions the parameter may occupy, out for the output positions a covariant parameter is restricted to and in for the input positions of a contravariant one, which is the spelling of C# and Kotlin whose variance this is.
in is a out is not: it remains an ordinary identifier everywhere, INCLUDING as a type parameter's own name, and is a <out: type> and <out: type = T> each declare a parameter NAMED out, while <out T: type> declares a covariant T, and <out out: type> declares a covariant parameter named out. One token of lookahead separates the two readings, and no program that does not write a modifier changes meaning.
A parameter written with ... is a variadic parameter, collecting any number of arguments as a rest parameter collects any number of values, and its ...I: [].<uint32> for a pack of uint32 values, ...Ts: [].<type> extends [].<Bound> for a pack of types, ...I: [4].<uint8> for exactly four — the habit the rest-parameter annotation of ... is only the collection marker at the application, so <...Ts: [].<type>> applied .<uint8, string> and <Ts: type extends [].<any>> applied .<[uint8, string]> bind the same thing.
A <>, and a mixed list otherwise.
... form, a ... form and the first has no [].<type>.... partial class or interface declaration or to a primitive operator block (type nor, for a ... form, [].<type>.The last rule is the rule ECMAScript gives an ordinary parameter, function f(a) { let a; }, closed over the one case it leaves open. A var of a parameter's name aliases the parameter's binding, which is why ECMAScript permits it; a type parameter has no binding to alias, so a var of its name, hoisting to the top of the body from any block, would replace it for the whole body, lines above the var included. A scope nested in the body may shadow a type parameter as it may shadow an ordinary parameter: { let T = 5; }, a callback (V, K) => V inside class M<K: type, V: type>, or a nested function's own declarations. A type written in the shadowing scope reads the shadowing binding too, since a type name resolves lexically, so a name keeps one meaning at each level. A class's type parameters need no such rule, since a class body binds no names.
The rule on a constraint states what where clause instead (
The first rule is the trailing-defaults rule stated per run: a <...A: [].<uint32>, N: uint32> is legal with N required — the parameter after a pack is what stops the pack's run positionally, and is otherwise reached by name. The second is the syntactic half of the rest-parameter sentence "two rests with nothing [].<any> admits everything too — is the
The mixed-list rules place selector-prefixed overloads. A function, method, or operator may mix fixed arguments with parameters, write<float32, maximum: float32, bits: uint32>(value: float32), and the list is then its own overload whose fixed positions are unnamed; a class, interface, alias, partial, or primitive block has one primary per family and may not, so class Box<uint32, N: 8> {} is refused where class Box<uint32, 8> {} was meant. A
A _, or a capture that binds the argument standing in its position.
The additional Map.<string, const Element>, or under the nested Map.<K: string, V: const Element>. The fixed extent of an array type within a [const N].<const Element>, and so may an element of a tuple type, [const Head, ...const Tail], where a ... _ is the wildcard, which matches any argument and binds nothing; any in the same position is the exact argument any.
Within a generic class's body, meaning its instance and static members but not its heritage or its own this does: begin(): PacketWriter { return this; }. A static member's are those of the
In a value position within the body the bare name keeps its JavaScript meaning, the class's inner binding, and each new B(args) there constructs the B<T: type>, new B(x) for an x of type uint16 is therefore an error, since the body is checked for every T, and a parameter the arguments do not determine takes the class's own value rather than its default. new B.<X>(args) names its
An intrinsic family follows the declared-default rules of complex complex.<number>, and bare int, uint, rational, and vector lack required arguments. A family-wide constraint uses positional anonymous arguments, such as T: type extends uint.<_>, as specified in
A primitive's value parameter, the width of int.<N> and uint.<N> and the lane count of vector.<T, N>, has the domain uint32; the range its type imposes, from 1 to 216 for a width, is a bound apart from that domain. A uint32 when it writes one. The intrinsic argument of rational instead admits either such a width or the type bigint; this intrinsic domain does not introduce mixed type/value domains for user-declared parameters.
A float32.<const D: Dimensions>. A
A primary binds before its specializations are matched (partial class or interface target, to stand in a primary position that could not bind it: a type where the parameter is a value parameter, a value its domain cannot represent, or a type its bound does not admit where that bound is closed. This is the test partial to admit the same applications, compared with captures canonicalized by binding identity and an omitted entry read as its position's default. Two such declarations could never be told apart by an application, and the
A Map.<V: T, K: const T> meaning Map.<K: const T, V: T>.
A builder is evaluated forward and never run backwards, so a class S<Make(const T)> {} is refused before any name is resolved. A class S<const T, Make(T)> {}, since it reads a
A function's, method's, or class operator's generic list declares an overload contract, so its declaration group may hold several same-named declarations whose lists differ; a class operator's group is keyed by its operator and whether it is unary, and it is static or not as a method's is. An interface's operator, and a primitive block's, declares parameters only. An owner is one whose list holds parameters only. A declaration whose list holds only fixed arguments, captures, and _ attaches to the _ takes the parameter's domain. It borrows the
An attached declaration whose
For both the parameter-list comparison and the
An abstract, a bodyless method with no attached declaration is an abstract member instead.
An explicit application of a group's name to type arguments selects, in order: the most specific attached declaration whose list matches the arguments, by
The fixed type fragments of a
Resolve each name at its source position to the binding selected by lexical resolution before obtaining that binding's type or declaration. A same-named declaration in a sibling function, a completed inner block or another unrelated source environment supplies no type and does not shadow an intrinsic or outer binding at this position. This applies equally to a bare name, an applied generic name and a name nested in a fixed fragment. Forward references follow the existing declaration and initialization rules; checking a type does not initialize a binding.
When following an alias, resolve its body in that alias's declaration environment, preserving recursive declaration identity and the applicable parameter bindings. A caller's same-named alias cannot replace it. Retain the selected source bindings and declaring environments for deferred obligations, including builder dependencies and imported or previously established bindings. An index keyed only by identifier spelling does not establish visibility, availability or reference liveness. Apply the query-input requirements of
After the dependencies required by such an obligation have been resolved, failure to resolve its type is a static
A named type argument reaches each candidate through that candidate's own labels: an attached declaration's are its _ has none. A
A call of a group's name without type arguments selects by ordinary overload resolution among its owners, each with its binding inferred from the arguments as any generic call's is, and its
An application of a group's name to type arguments that is not called is a value: it selects as a call would, with no arguments to count. A chosen declaration's value calls it with its captures bound; applications that choose the same declaration with the same bindings evaluate to the same value, and the declarations of two evaluations of one scope are two. A group with an
An application inside a generic body whose arguments name that body's open type parameters is selected for each _ admits any binding and F.<…> of captures and _ admits every binding of a bound that is the family F. Each other declaration that some such binding would select instead, and whose parameters can receive the call's arguments, must then have that declaration's
Standalone declarations whose lists hold only fixed arguments, captures, and _, of one length, are
A method's group is the same-named methods of one class body or object literal, however many; the selection rules above apply to a call, an application, or a stored application of a method as to a function's. The group a method call names is found from its receiver: in the nearest class of the receiver's class and its superclasses whose body declares the name, in the receiver's object literal, or, for super, from the enclosing class's superclass.
A binary use of a class operator whose group holds a specialized declaration selects as an implicit call of that group would, its right operand the one argument, and the operation has the chosen declaration's return type. A use with no right operand to select by, a unary, compound-assignment, or index operator's, is a static error when its group holds one. A class operator's
A declaration whose where clauses do not all hold for an application is not applicable to it: its clauses are evaluated with its bindings, and a method's with its class's bindings too, before any body runs, and selection then proceeds among the rest.
A group's type is its Reflect.getReflection given a function value that holds specialized declarations, or a value one of their applications produced, returns an object whose signatures has one entry per declaration, each with its role ("owner", "replacement", "additive", or "standalone") and its typeParameters: for each position, its label (an selected is the entry it selected, the
Declarations told apart by their lists are not duplicate overloads for sharing parameter types: a
A
_ is accepted by more than one abstract.The captures of a where clauses, and its body, under the ordinary rules for class and static scope. Within that scope a bare Pair in class Pair<const T, T>, is resolved before the Map in Map.<string, const E>, is resolved within it, so a first-order Map makes that application a
The abstract operation CollectCaptures takes argument list (a
... A where clause (
Matching compares a Box.<uint32, N: 8> and Box.<uint32, 8> meet the same specializations. Matching evaluates no
A callable group containing a
The abstract operation MatchSpecializationList takes arguments list (a
The entries are arguments of the primary as an application's are, so a class Tup<...Ts: [].<type>>, the list <uint8, ...const Rest> matches every application whose first type is uint8 and binds Rest to the tuple of the others. The list is positional only, so no run is opened by a name. An omitted trailing entry stands for the primary's default at that position, not for a class FixedBuffer<T: type = uint8, N: uint32 = 16> the list <uint8> matches only an extent of 16. Structural positions are matched first and computations over captures are checked afterwards, which is why a computation may read a
The abstract operation MatchSpecializationPattern takes arguments pattern (a
_, return _, then_, then... A fixed position compares by identity and not by assignability: Pair<uint32, _> matches Pair.<uint32, string> and not a Pair whose first argument is a refinement of uint32, and any in a list is the exact argument any. A repeated Pair<const T, T> matches Pair.<uint8, uint8> and not Pair.<uint8, uint16>, and a repeated value NaN repeats and 0 and -0 do not. A repeated pack requires the same length and elementwise identity, and a run of one sequence holds at most one ..., since two leave no boundary between them. The Map.<_, uint8> matches every Map whose uint8 and is never resolved as a type named _. The form of an array is part of what matches: [const N].<const E> matches only an array of fixed extent and [].<const E> only a dynamic one. Only structural positions expose a component: a builder is never run backwards to find a
Callable selection, the relationship of a specialized callable to its
A parameter's kind is decided by how its domain is written: the type declares a type parameter, [].<type> on a parameter of the ... form a pack of types, and type is refused rather than read as type.
The kind decides how an argument binds, a type or a constant, so it is read where it is written: a reader, a tool, or an implementation knows it from the declaration alone, without resolving a name that may be declared later or imported from a module not yet linked. Rust marks a value parameter syntactically, const N: usize, and C++ distinguishes typename T from int N the same way. The diagnostic for an alias of type offers the spelling, T: type or ...Ts: [].<type>.
The abstract operation ParameterKind takes argument p (a
type, throw a ... form and written is the [].<type>, return ... form and written is the type, return ... form, thentype type, throw a The final step is where a bound written as a domain is caught at its declaration. T: Ordered.<T> declares a value parameter whose values would be objects, which no generic argument can be, and the diagnostic offers T: type extends Ordered.<T>; Rust, Swift, and Kotlin write a bound as T: B, so this is the mistake a reader from those languages makes first. A domain that admits both Type Objects and other values, any or type | uint32, reaches the same step, since a binding from an argument could read it either way.
The abstract operation IsPermittedValueDomain takes argument d (a
type type, return An array or tuple of value domains is itself one, though its values are Objects: a value parameter binds the LITERAL type of its argument (join<D: string, P: [].<string>> relies on to bind the strings a call passes. An array whose elements are objects is not a value domain, for the reason no other object type is.
A decimal128.<{ currency: To }> names one type however the record is spelled. No other object type is a value domain, since a generic argument's identity would then turn on object identity.
A union is a value domain when each of its members is one, so V: uint8 | string declares a parameter over constants of either type, the case C++ writes template<auto V>. any is not a value domain: it admits Type Objects, which would make a binding from an argument ambiguous between the argument's type and its value, and it admits objects, which no generic argument can be. Neither is type | uint32, for the first reason. A parameter over types is written T: type, and one over several kinds of constant is written as the union of their types.
The abstract operation EffectiveConstraint takes argument p (a
This operation selects source; it does not resolve that source in the caller's environment. Its denotation at an application, including a completed description retained by a substituted signature, is determined by
A parameter is bound from a call's argument by its kind. Where a formal parameter's annotation reads a parameter p, the argument contributes to p its function id<T: type>(x: T): T called as id((3 := uint16)) binds T to uint16, while function init<C: Component>(component: C) called with Component.Transform binds C to that constant. A pack binds elementwise by the same rule.
The T: type extends Ordered.<T> does, is an F-bounded constraint and is not refused: it is checked against the binding once the binding exists, which is how Rust reads T: PartialOrd<T>. The list owns its names, so a later parameter shadows an outer binding of the same name throughout the list and a read of it is the forward read above.
For a permitted F-bound on a non-variadic type parameter, first determine its candidate from the explicit argument or required default using the ordinary source contexts and preceding bindings. Make that candidate provisionally available to the bound's references to that parameter, then check the candidate against the resulting constraint. Publish the parameter's binding only after admission succeeds. Provisional availability does not permit a computed self read, a forward read or a self-referential default that the preceding paragraph forbids.
Describing a recursive interface constraint is distinct from admitting its arguments. An unfinished description supplies no completed admission or implicit
When a self-bound candidate is a class whose declared interface graph is being formed, retain its admission obligations until its written interface targets have resolved in the class's actual declaration context. Check those obligations against the completed graph before publishing the class contract. Do not infer an additional implemented interface from a bound's demand. The class's member conformance requirements remain required. Retaining these descriptions neither initializes a source binding nor establishes reference liveness.
A class's bare self-type reference during that formation may use the forming candidate even when the class has no type parameters. Lexical resolution must select the candidate's class declaration and the applicable lexical activation must identify the same evaluated class family. A nearer binding, another activation or a same-named parameter frame does not supply this candidate. This type-description rule neither changes ordinary value reads nor publishes the class contract before its obligations succeed.
Parameters bind left to right. At a use of the generic declaration, whether through explicit arguments or through inference from a call, the first parameter is bound, then the next parameter's constraint is evaluated by
Where a parameter has an evaluated constraint at all, the binding inferred for it from a call argument is the LITERAL type of the argument's value, not the widened base. This was stated for a constraint that is a literal type or a union of literal types (
This is the one placement of a builder call that needs a sentence of new specification, and the one inference rule builders ask for. function pluck<T: type, K: keyof T>(o: T, key: K): indexed(T, K) reads in order: infer T from o, evaluate keyof T over that binding, and infer K from the second argument literally, so pluck(user, 'name') binds K to the literal type 'name' and the return type evaluates to that property's type. The constraint does double duty, checking the argument and cueing the literal inference, which is the cue K extends keyof T gives an erased system. It is a rule about constraints rather than a hook: no user code runs inside the inference of the parameters the constraint reads, and a mistaken argument fails the ordinary constraint check, with the evaluated union available to the diagnostic.
A constraint that is a W: uint32; a constraint that is a union of literal types binds a literal type, whose value is recoverable from it. The
An annotated rest parameter whose target is a
When checking a function's parameter initializers, all parameter binding identities and their effective declared or contextual contracts are in scope, including bindings introduced by destructuring. Initializers are then checked in declaration order. Recording a contract does not initialize a binding or remove its temporal dead zone. A closure written in an earlier default may therefore be checked against a later parameter's contract; invoking it before that parameter is initialized still throws a
A type annotation attaches a type to a binding, a parameter, a return, or a class member. This clause adds the annotation and the positions that accept it; the meaning of an annotation in each position is given by the declarations, functions, and classes clauses.
A binding may carry an annotation, or a for-of or for-in head takes the same annotation, as for (const a: int32 of f()), and a
A parameter may carry an annotation, an optional marker, and a ref modifier.
A DESTRUCTURED binding takes its annotation the same way a named one does, before its initializer: function f({ a, b }: Point) annotates the object being destructured, not the names taken out of it, and each name's type follows from the annotated type's members. Without this a pattern could carry an annotation only as a REST element - ...{ a, b }: T was admitted where { a, b }: T was not - which is a distinction the grammar drew and nothing else did.
A rest element's annotation is the type of what it collects, an ...args: [].<uint32> collects uint32 values into a [].<uint32>. It is a [].<any> or to a narrower array or tuple type, so that the error is reported where the rest is declared rather than at every application. Where the annotation states an EXTENT — [2].<uint8>, or [N].<uint8> for a value parameter N — the extent is part of the type and the call must supply that many arguments; for a value parameter this is checked where N is supplied. Its function f(a: string, ...args: [].<uint32>) {} and let [a: uint8, ...b: [].<uint8>] = [1, 2] have no parse.
The array-or-tuple annotation requirement applies to every annotated
A
ref on a rest parameter, in a declaration and in a function type alike, distributes over the run: each parameter the rest collects is a ref parameter. References are not values, so a ref rest binds no array. Its name is usable in exactly three forms, each a direct use of one collected reference and none a store — spread into another call's ref rest position, name[k] with a compile-time-constant k, and name.length (whose count has the ref parameter already has, applied per element. The rule is static: the checker refuses every other form where it can see the name — an index that is not a constant, a property, a bare read, a store, a whole pass — and the run time, which cannot tell a constant index from another, refuses a store of the run, a pass of it to a parameter that is not a ref rest, and an index out of range. A callback whose type carries a ref rest is satisfied by a function whose parameters are individually ref; the run is matched by
Reference-run forwarding retains an occurrence independently of its expanded entries.
Apply this forwarding permission to the effective callable
The forwarding permission is also a static judgment under
Checking also rejects forwarding when every effective candidate has no possible route from the written occurrences to reference-rest positions. Preserve established named pinning and source order: a fixed named
any, including through an alias or a substituted type parameter. Adjacent constrained rests may have the same element type. Arguments are distributed by
Every language admitting a rest away from the end pairs it with something that marks the boundary. Swift allows several variadic parameters and requires the first parameter after one to carry an argument label; Ruby allows def f(a, *b, c) and binds the fixed parameters from both ends before the splat takes the middle; TypeScript reaches a non-final rest only through a tuple type with a middle rest element. This
A function may carry type parameters and a return annotation.
A class
The positions above are representative rather than
ref before a parameter is a ref remains valid, since an identifier that is not followed by a token that can begin a binding element is a parameter name; function f(ref) therefore continues to declare a parameter named ref, while function f(ref a: int32) declares a reference parameter named a. The claim rule this shares with every other borrowing form, and the Early Errors of the ref parameter — no
A type name is already an expression, since a type is a value, so uint8 and Map.<string, uint8> may be written where a value is expected. The type forms whose syntax collides with the expression grammar cannot: a function type reads as an arrow function, an object type as a block, a tuple type as an array literal, and a union as a bitwise or. The type operator resolves the collision by parsing its operand as a type.
A conversion converts the value of its left operand to a type, and is written after the value.
Placing a conversion at instanceof, so a + b := uint8 converts the sum, and the right operand being a x := A | B converts to the union rather than taking the bitwise or of a conversion.
type is a type, and this leaves an ambiguity: type (uint8) => uint8 is a type operator applied to a function type, while type (x) is a call of a function named type, and the two agree until the token after the closing parenthesis. This specification resolves it with a cover grammar, as the grammar already does for arrow parameters and parenthesized expressions: the parenthesized text is parsed under a cover production and refined to a function-type operand or a call argument list once the token after the closing parenthesis is known. The type declaration form raises no ambiguity, since its type is followed by a binding identifier, and the computed types of type f(x) was a syntax error and is now the operator applied to a computed type, so the only (. The committee may still prefer to avoid the cover grammar by restricting the operand or by requiring the operator only where a type cannot otherwise be written; that would remove the
Two operand tokens are ambiguous in a way no cover production can resolve. type [0] is the operator applied to the tuple type [0], and a member access on a binding named type; type -1 is the operator applied to the literal type -1 (type [0] is the tuple type, type -1 is the literal type, and a program that means the value writes (type)[0] or (type) - 1. This is the treatment ECMAScript already gives let [ at the start of a statement, which is a destructuring declaration rather than a member access on a binding named let. A property access such as o.type[0] is unaffected, since only a bare type reaches the operator, and the [no LineTerminator here] restriction means a line break before - 1 leaves it a subtraction. Deciding the two tokens differently is the hazard worth naming: - given to the operator and [ left to member access would admit type -1 while refusing type [uint8], and a tuple type would then have no spelling in expression position at all. If the change of meaning is judged too high a price, the alternative that keeps a tuple operand writable is to make it an type is also bound, so that the program is refused rather than reinterpreted.
The operand of type is a full type A | B is the union of A and B, not the bitwise or of type A and B. A bare name is a type expression, so type uint8 is a redundant spelling of uint8. The extensions that introduce type-position operators use the is takes the wider x is A | B tests the union exactly as before.
is is a a is b on one line is a Syntax Error today, so the new production takes syntax no program can be using; but a, is, and b on three lines is three statements today, by automatic semicolon insertion, and must stay three. The restriction keeps it so, at the price that a program cannot break the line before is. This is the treatment async already receives before an arrow's parameters, and the alternative, reserving the word, is not available.
new in Expression PositionA f.<uint8>(x) and a placement allocation such as new(buffer, byteOffset) Type(0) are member-expression forms. The productions below restate
A where clauses ran once at its creation, so f.<uint8> may be stored, compared, passed to map, and called later exactly as the function it specializes, and evaluates its type arguments once rather than at each call. Where the expression's ?..
When an expression-position application has an established generic interface, alias or class-
Establish the expression's actual declaration origin using the ordinary lexical, import, alias and effect rules. Parentheses and established immutable aliases preserve that origin; an erased value, a mutable exported binding or an arbitrary property or call result does not obtain it from a matching spelling. Written arguments retain their caller context, while constraints and defaults retain the declaration context. These judgments do not evaluate the ordinary base expression, materialize a class
A named import that resolves to a generic alias declaration retains that declaration as its expression origin, including through renamed imports and re-exports. Retain its parameter identities and declaration context independently of its expanded result type. type. Transporting this origin does not introduce nominal identity for an otherwise transparent alias. Linking establishes the source declaration without evaluating its runtime binding or initializing its dependencies. An unavailable input in a cycle and an exception from ordinary dependency evaluation retain the ordering required by
The retained declaration context includes the original environments of helpers used by the alias's constraints, selected defaults and
The placement forms allocate into an existing buffer rather than into fresh storage. The first argument is the buffer, the second, when present, is the byte offset and otherwise 0, and the third, when present, is the byte length reserved for each element and otherwise the constructed type's byteLength. Construction stores each byteLength leaves padding between elements, a smaller one makes elements overlap, and bytes the layout does not assign keep the buffer's contents, since reusing storage is what the form is for and the zero-fill rule of fresh allocation does not apply. It is a
Each written placement
new ( begins both a placement form and an ordinary new whose new(buffer, byteOffset, byteElementLength) [10].<Type>(0), needs no further production: [10].<Type> is an
new . new.target by the token after the dot, an new . ( has no parse, so the added reading changes no existing program.
The type comes from the position rather than from the expression, so a position requiring no type gives nothing to construct: it is a Syntax Error for this form to appear where the let n: uint8 = new.(1). Among a constructible type's signatures the ordinary ranking chooses, with no rule of its own, and nesting follows without further rule, an inner form taking its type from the outer
This clause adds the value types of this
A value type is a type whose values are
The values of distinct value types are distinct. Where two types are described below as containing the same uint8 representing 0 and the uint16 representing 0 are different values of different types, as the Number 1 and the BigInt 1 are today. Every value therefore belongs to exactly one type, which is what lets
Every type added here is described by a uint8 is described by the vector.<float32, 4> by { [[Kind]]: float32, 4 » }.
Because Type int8 and int.<8> name the same
For each positive int.<N> is a uint.<N> is a
N is a value argument, so int.<8> and int.<16> are distinct types, and the width of a type is recoverable from its
N is bounded: it lies in the uint.<1> as the lane type of a boolean vector, but nothing above restricts N, and an unbounded N obliges every implementation to provide arithmetic at arbitrary width, which is a substantially larger commitment than the named widths imply. Fixing N to a bounded
float16, float32, float64, and float128 are value types whose values are those of the corresponding
| Type | IEEE 754 format | Width w | Precision p | Values |
|---|---|---|---|---|
float16 | binary16 | 16 | 11 | 63491 |
float32 | binary32 | 32 | 24 | 4278190083 |
float64 | binary64 | 64 | 53 | 18437736874454810627 |
float128 | binary128 | 128 | 113 | 2128 - 2113 + 3 |
Each of these types has a positive zero and a negative zero, a positive infinity and a negative infinity, and a single NaN, as the
float64 and the float64 is the type a program asks for when it wants that format by name.
decimal32, decimal64, and decimal128 are value types whose values are those of the corresponding
| Type | IEEE 754 format | Width | Coefficient digits | Exponent range |
|---|---|---|---|---|
decimal32 | decimal32 | 32 bits | 7 | -101 through 90 |
decimal64 | decimal64 | 64 bits | 16 | -398 through 369 |
decimal128 | decimal128 | 128 bits | 34 | -6176 through 6111 |
An arithmetic result whose exponent falls outside the range of its type is a
Two 1.0, 1.00, and 1.000 are three values of decimal128, one for each exponent, and a decimal type therefore preserves the significance of the quantity it was given. IEEE 754 calls such a
Preserving trailing zeros is the point of the decimal types: a price given to two places prints to two places, which is what the money types of the primitive metadata extension depend on. The consequence is that the numerical order and the value identity of a decimal type come apart, and the equality operations must choose between them. The == compares numerical value, so 1.0 == 1.00 is 1.0 and 1.00 are one key in a Map or a Set rather than two. Identity follows the representation: 1.0 and 1.00, so they are distinct map keys and distinct literal types, while === and == compare numerically and find them equal, the same split Number already has for +0 and -0.
This specification settles Object.is(1.0, 1.00) is decimal128 values of different exponents, while == compare numerical value and find them equal. This is the split the ===/== versus identity, and it is the parallel with negative zero, where +0 from -0 and Object.is unable to distinguish values that print differently, which no other type permits; its cost, that
An operation with a decimal NaN operand gives the NaN, and one with an infinite operand gives the
For each positive rational.<N> is a int.<N>. A value of rational.<N> is a pair of a numerator and a denominator, both values of int.<N>, such that the denominator is greater than 0 and the greatest common divisor of the numerator and the denominator is 1. Every value of the type is therefore in lowest terms, and each rational number has at most one representation in the type.
A rational type has no negative zero, no infinity, and no NaN. An operation whose exact result is not representable, including one whose normalized numerator or denominator falls outside int.<N>, throws a
rational.<bigint> is a bigint values in lowest terms with a denominator greater than 0. No value is outside it, so an operation on it throws no bigint has none. Its numerator and denominator are bigint values, and Math.floor, Math.ceil, Math.round and Math.trunc of one return a bigint.
For parse, a literal of a rational type is a decimal numeric literal — digits, with an optional fraction and exponent — or two decimal /, the second unsigned: 0.1 is 1/10 and 3/4 is 3/4, as a literal in a rational position parse throws a
rational.<N>.approximate(_x_, _maxDenominator_) returns the value of rational.<N> nearest the Number x whose denominator does not exceed maxDenominator. Of two values equally near, it returns the one with the smaller denominator, and of two with the same denominator, the one nearer zero. It throws a rational.<N>, which is overflow rather than approximation; rational.<bigint> has no range to lie outside, and bounds only the denominator. The explicit alias rational64.approximate is that of rational.<64>; there is no bare rational application.
Normalizing on construction is what makes the type a
For a number or one of the binary floating-point types, complex.<T> is a complex.<T> is the square of the number of values of T.
The width-named shorthands of complex64 is a pair of float32 and not a pair of float64.
For a vector.<T, N> is a vector.<T, N> is the number of values of T raised to the power N.
A vector's lane type may itself be a vector, which is how the boolean vectors of the boolean8 is vector.<boolean1, 8>, where boolean1 is the uint.<1>, eight one-bit lanes, and boolean8x16 is vector.<boolean8, 16>. The lane counts an implementation can hold in a register are a matter for the SIMD extension; the type exists for every positive N.
It is a vector.<T, N> where T is not a lane type or N is not a positive
A parameterized lane type is how a vector's lanes carry metadata: vector.<float32.<{ m: 1 }>, 3> is a vector of meters, its lanes read as float32.<{ m: 1 }>, and it has the representation of vector.<float32, 3>, since a parameterization has exactly the representation of its base. A lane enters the parameterization as a scalar does, through a declared cast. Such a vector is a subtype of the vector of the same count over its lane's base, as the lane is a subtype of its base, so a vector of meters is accepted where a float32x3 is; a lane type that differs in representation, uint8 against uint16, relates no vectors.
A vector's lanes are reached by two pairs of operations, and the pairs differ in when the lane is chosen. Where the lane is a
For a vector type vector.<T, N>:
lane.<I>() has the withLane.<I>(value) has the vector.<T, N>, and takes a value of type T. It is a uint32 has the The asymmetry is the reason both forms are given. lane.<I>() is refused at check time for an index out of range and v[i] is not, because an index that is an expression is not known until the access happens. The
withLane.<I>(value) returning a new vector rather than modifying the receiver is not a rule of this clause; it follows from a vector being a
The assignment is asked about because withLane expresses the same intent and returns a value, where v[i] = value mutates one in place, which is what a value type is meant not to do (simd.md, Open Questions). It is kept, and the reason is not the consistency one: an array does already permit a value type's element to be assigned, but the decisive point is that withLane`'s index is a COMPILE-TIME CONSTANT. The two operations therefore do not overlap where it matters — refusing the assignment would leave a lane whose index is computed with no way to be written at all — so the redundancy the question supposes does not exist.
An operation taking a computed index and returning a new vector would restore the redundancy and reopen the question honestly. Nothing here proposes one; a revision that added it could then refuse the assignment without leaving the computed case unwritable, which is the only ground on which refusing it would be complete.
vector.<T, N> declares two construct signatures: (...lanes: [N].<T>), one lane value per position, and the cast (value: T) below, so a call with a count that fits neither is refused by the ordinary call judgment of vector.<T, N> declares a cast operator from T, so a value of the lane type converts to a vector by filling every lane with it, and the conversion is one of the user-defined casts of float32 s, float32x4 accepts s and float64x2 does not, and a float64 first. An operator applies this cast to a scalar operand whose numeric base is the lane type's, so for a float32 s, v * s scales every lane of a float32x4 v, and a literal beside a vector adopts the lane type (uint8x16(300, …) is refused rather than wrapped. A single argument that is itself a vector is an explicit conversion instead (uint.<1> lanes is a bitmask.
Where the lane type is uint.<1>, a lane and a bit coincide: lane i of a vector.<uint.<1>, N> is bit i of an N-bit
This is what makes a bit vector a bitfield rather than a name for a byte. A boolean8 initialized from 0b00000010 has lane 1 a[1] is 1 and a[0] is 0; assigning 1 to lane 3 and reading the value back as an 0b00001010.
The numbering is the one
A bit vector is not a boolean8 here is not meeting the other.
sum() has the
The order is left to the implementation because fixing it would forbid the instruction that makes the operation worth having: a pairwise horizontal add is what the hardware provides, and it associates differently from a left fold. A lane.<I>().
A permutation names a lane of a source for each lane of its result, so it reorders, repeats, narrows, and widens in one operation. Two forms draw from one source and from two (
For a vector type vector.<T, N> and indices I₁ through IL, each a
swizzle.<I₁, …, IL>() has the vector.<T, L>. Lane j of the result is lane Ij of the receiver. It is a shuffle.<I₁, …, IL>(other) has the vector.<T, L> and takes an other of type vector.<T, N>. An index below N selects that lane of the receiver, and an index from N to 2_N_-1 selects lane Ij-N of other. It is a L need not equal N. A permutation of fewer indices than the receiver has lanes narrows, and one of more widens: swizzle.<0, 1>() on a four-lane vector has the vector.<T, 2>.
Each index is a shufps and its kind is an immediate operand. Other instructions take a selector in a register, pshufb, tbl, and vrgather among them, so a permutation whose selection is data is possible and is a DIFFERENT operation. It would be named swizzleDyn here, and it is an open question of the
What is open about it is one rule: an index that is data may be out of range, where an index that is a constant is refused before the program runs, and there is no answer to defer to. Rust's swizzle_dyn and WebAssembly's i8x16.swizzle yield zero for such an index; x86's pshufb wraps; ARM's tbl and RISC-V's vrgather zero; and Java's VectorShuffle does neither, wrapping an invalid index into a negative band that a later operation may use to steer a second source. WebAssembly specified zero and then added a relaxed form whose result is
The two-source numbering is the one place a reader is likely to err. For a four-lane receiver, indices 0 through 3 name the receiver's lanes and 4 through 7 name other's, so a.shuffle.<0, 1, 4, 5>(b) takes the first half of each.
swizzle is how the eight- and sixteen-lane vectors permute, since the component accessors of
The swizzle<...I: [].<uint32>>(), whose length gives the result's lane count. Value generics and their variadic form are specified by
A vector of four or fewer lanes has names for its lanes, drawn from two sets, and a sequence of those names is a permutation written as a property (xyzw and rgba name the same lanes in that order, so v.x and v.r are one lane and v.wzyx and v.abgr are one permutation.
A vector.<T, N> when all of the following hold:
xyzw or each drawn from rgba. A key mixing the sets is not a component accessor.Such an accessor has the vector.<T, L> for a key of L characters. L is independent of N: a two-lane vector's xyxy has the vector.<T, 4>.
An accessor whose key names no lane twice is assignable, and takes a value of the accessor's own
For a union receiver, assignment must be permitted by every known alternative. A known vector alternative for which the key is a repeated-lane component accessor therefore makes the assignment a
The value stored through a component accessor must satisfy the selected accessor's type under the ordinary conversion and contextual literal rules. This judgment applies to dot access and a statically known String key alike, and the lane-value judgment of uint32. Distribute destination contracts over reachable known receiver alternatives rather than checking against their joined read type. Two effect-free component projections of the same vector binding retain the same receiver alternative, so v.xy = v.yx remains valid for a vector union; independently obtained receivers do not acquire that correlation. The same destinations govern compound and logical assignment, destructuring and iteration targets. Unknown keys or receivers defer to run time. This changes neither numeric computed bounds checking nor reference eligibility.
A vector of more than four lanes has no component accessors, because the two sets have four characters each. Such a vector permutes with
That limit is a decision rather than a gap awaiting names. Every shading language that has these accessors stops at four, and no language with wider vectors has invented a a and b already name lanes in rgba and would name different lanes at a different width; adding a second pair of sets for lanes 4 through 7 does not reach sixteen; and numeric names in the manner of OpenCL's s0 through s7 would spell what lane.<I>() and swizzle already spell. The four names exist because geometry has conventional names for its axes, not because permutation wants sugar, so a vector with no such convention has no names and loses nothing.
The stpq, as deliberately absent: HLSL and WGSL dropped it, and two sets already name every lane.
A component accessor is a property, not a syntactic form, and the distinction is observable. The names are non-enumerable properties of the vector's prototype, shared by every value of that
v['xyz'] reaches the same value the dotted access does, since the key is looked up rather than parsed.Object.getOwnPropertyNames(v) includes every accessor name of that vector's lane count.Object.keys(v) is empty, since the accessors are not own properties.Object.getOwnPropertyNames(C.prototype) for a four-lane vector type C includes every accessor name, of which there are 680 counting both sets and every result length from 1 to 4.in and Reflect.get are not among these, and the reason is the language's rather than this clause's: both require an Object, and a vector is a '0' in 'abc' is a 'abc'[0] reads — so an accessor being a property does not make these operations reach it. An earlier draft of this clause listed them, which was an error about JavaScript rather than about vectors.
A form keyed on the receiver's static type would satisfy neither: a computed access could not reach it, and reflection could not see it. Making the accessors properties is what keeps a vector an Object.getOwnPropertyNames reports. None of them collides with a method of the vector prototype, since every accessor name is drawn from xyzw and rgba alone.
A component accessor is equivalent to the permutation of the same lanes:
lane.<I>() for that character's index I (swizzle.<I₁, …, IL>() for their indices (The equivalence is why this clause adds no operation. v.xzzw is v.swizzle.<0, 2, 2, 3>() and v.x is v.lane.<0>(); the names are how a program reads, and the permutation is what an implementation compiles. A compound assignment such as v.xy += w is the read, the operation, and the assignment in the order any compound assignment takes them, and needs no rule here.
The function properties of the Math object apply lane-wise to a vector argument, returning a vector of the argument's
The min, max, abs, sqrt, floor, ceil, round, and trunc are exact, and lane j of the result is the scalar result for lane j of the argument. The remaining functions - the trigonometric, exponential, logarithmic, and hyperbolic families and pow, cbrt, and hypot - are
The independence is deliberate. A vector math library computes a transcendental to a different accuracy than the scalar library does, so requiring the two to agree would forbid an implementation from using the vector instruction at all, which is to forbid the reason the operation is offered here. A program requiring bit-identical results between the two spellings applies the scalar function lane by lane, which is what it is asking for.
Reading any, all, or select from a statically known mask produces the corresponding function type. The any and all functions return boolean. The select function is generic in the selected lane type: both arguments and its return are vector.<U, N> for the receiver's lane count N. A known scalar argument is incompatible even before U is inferred. These callable contracts apply to method values as well as direct calls and do not change eager argument evaluation.
uint.<1>, which is what the booleanN (float32x4 comparison's boolean32x4 result is a mask as much as a boolean4 is. The operations below are declared on a mask in either form and on no other vector. A lane of the compact form is set when its bit is set, and a lane of the wide form when every bit of it is, which is what a comparison writes into one: all-set or all-clear.
For a mask of N lanes:
all() has the boolean, and is any() has the boolean, and is select(whenSet, whenClear) takes two vectors of one type vector.<U, N> and has that U is not the receiver's lane type. A mask selects between vectors of any lane type, so long as they share its lane count.
select over an arithmetic result is how a program computes some lanes and leaves the others, which a target with predicated instructions performs in one instruction rather than two. m.select(_a_ + _b_, _src_) is the write-masked form, whose untaken lanes come from src, and m.select(_a_ + _b_, _zero_) is the zero-masked form. An implementation may lower either to such an instruction, computing only the lanes the mask takes, when the arm whose lanes it elides is an expression whose evaluation has no effect an ECMAScript program can observe; arithmetic over vectors is such an expression.
That condition, and not the select is an ordinary method, so its arguments are evaluated before it is entered: an argument that calls a function, reads an accessor, or otherwise has an effect is evaluated whatever the mask holds, and only lanes of an arm an implementation can show to be pure may go uncomputed. No syntax is added for the masked forms, because this composition already
select is an ordinary call, so **both arguments are evaluated** before it is made, whichever lanes of the receiver are set. It is not a conditional and does not have a conditional's evaluation.
That is the trade select exists to make. A branch evaluates one side and costs a misprediction where the condition varies per lane; select evaluates both and costs neither, which is why branchless code is written with it. A
A mask is a vector and nothing about these operations makes it a separate kind of value: a lane of it is read by index, it permutes, and it carries component accessors like any other vector of its m.x is a lane of it and m.xyxy is a four-lane mask.
vector.<T, N> admits any positive lane count, which is what lets one kernel be written against a width. The width a target executes in one instruction is a property of the target, and float32.preferredLanes is that width for the lane type float32, and every lane type carries the same property.
Its result is a
The operation is on the LANE type rather than on vector, and the reason is that a bare parameterized primitive is not a value: uint, int, and vector denote types only when applied, so const x: uint = 5 is refused for the same reason const x: vector = ... is. A vector.preferredLanes(T) would need vector to be a value, which would single it out among the three. complex declares a component default, so it is complex.<number> in a concrete type position. This supplies the type of an imaginary literal and keeps complex distinct from complex128. rational has no default; rational64 is its explicit 64-bit alias. A lane type is already a byteLength and the parsing operations among them — so float32.preferredLanes is one more property on a surface that exists, and it reads as what the operation asks: how many lanes of this type fill a register.
This is the one operation of this specification whose result may differ between vector.<float32, vector.preferredLanes(float32)> is a different type on a target with four lanes and on one with eight. That is the point of the operation rather than a defect in it — a program written against it is portable in the sense that matters, which is that it compiles to the target's own width rather than to a width chosen for another machine.
It is also why a
A vector whose lane count exceeds the target's width is formed and used as any other, and an implementation executes it in as many instructions as it needs.
Gather, scatter, and masked load and store are not specified. The
A type alias of a vector type is that vector type, so a value of it has the component accessors of
This follows from the rules for aliases and classes rather than adding one, and is stated because it decides a Vector4 written as an alias swizzles and a Vector4 written as a class does not, unless it declares the accessors it wants. The trade is nominal distinction: two aliases of vector.<float32, 4> are one type, so a Quaternion and a Vector4 written that way are mutually assignable, where two classes are not.
any is the type of every value. Every any type, and a binding whose type is any accepts any value. It is described by the any type.
void is the type with no values. It is described by the void type, so no binding may have it.
It is a void type, independently of whether it has an initializer or that initializer is statically known. This includes local and parameter bindings, bindings within destructuring patterns, and catch bindings, including through a type alias. A binding whose type depends on a generic parameter is checked when an application resolves that binding's type; an application that makes it void is a void return types or occurrences inside protocol types such as Generator.<Y, void, void>, and it does not reinterpret void as undefined.
It is a void: for it to be a test (&&, || or ??, the receiver of ?. or of a property access, the operand of a unary operator other than void, delete and typeof, an operand of an arithmetic, bitwise, shift, relational or equality operator, the right operand of a compound assignment other than a logical one, or a template substitution. Such an expression may be an expression statement, the operand of void, an operand of the comma operator other than its last, a value returned where the return type is void, the right operand of &&, || or ?? or an arm of a conditional expression, whose value is judged where it is used, or a value reaching an untyped binding, an untyped parameter, Reflect. An object literal's property value and an array literal's element are not such positions: each stores the value, so { x: f() } and [f()] for a void f are type errors in checked code, and a const whose inferred type carries void through a pattern or a literal member resolves to void as a binding does.typeOf or typeof, the positions the note below names, typeof reporting the value as Reflect.typeOf does.
A call of a function whose return type is void evaluates to return statement does today. The void type is the statement that a program must not depend on that result, not a claim that no result exists.
The sentence is about the FUNCTION'S OWN return type, and it holds because such a function cannot return a value at all:
A call reaching a function through a void-returning TYPE is a different case. void return required of nothing, so const _h_: () => void`` may hold a function returning a String; _h_() then evaluates to that String. What void promises is that no TYPED position will accept it: the _h_() is void, so let _s_: string = _h_() is refused, and every position that demands a type refuses it likewise.
The value is therefore observable only where nothing was declared - an untyped binding, an argument to an untyped parameter, Reflect.typeOf. That is the same reach any has everywhere else in this specification, and it is why the promise is stated over the STATIC reading rather than over the value.
Erasing the value at the call was considered and is not specified. It would require the Reflect.typeOf is itself such a position.
It follows that a void return type constrains the CONSUMER of the result and not the value leaving the function: the check it installs is the one that refuses a binding, and no check is performed on the returned value itself. return; and falling off the end are the same function written two ways, so testing undefined is the type to write for a function that returns the value
null is the type whose one value is null.
undefined is the type whose one value is undefined. It is distinct from the void type: void is the type with no values, written to say that a result must not be depended on, whereas undefined has the one value
These are the types undefined by convention when the promise never rejects, which is one place the undefined type is written directly; another is a union such as uint32 | undefined for a value that may be absent, the position an erased system reaches with an optional. null and undefined are ordinary members of the type universe and may appear in a union, an intersection, or any other type position.
Resolving the undefined type name to void looks like it keeps the name and uint32 | undefined becomes a union no value can inhabit. The agreement to keep is at the undefined type: undefined, the name void remains a return type that no binding may hold.
never is the type with no values: the union of no members, described by the
Expression-position access to it is the type operator, type never, as it is for every other type name (Reflect.never beside the general bridge would be a second spelling of one value, with nothing to say when to prefer which, and the construction it saves - Reflect.makeType({ [[Kind]]: ~union~, [[Members]]: « » }) - is equally reachable through the operator.
Its algebra follows from the operations already defined, with no rule specific to it except the one noted last.
never is a subtype of every type. In never is a subtype of never. A target of the never is the identity of union. Canonicalization (T | never canonicalizes to T with no rule naming the case.never annihilates intersection: an intersection with a never member is never. This is the one fact that does not fall out, and never, which is the general case of the preceding: a never member is disjoint from every member, itself included. number & bigint, uint8 & string, and 'a' & 'b' are all this one type. A program that WRITES such an intersection is reported at the annotation rather than left to discover it downstream (No value is a value of the never type: any type reaching a position of the never type fails its boundary check (never exists to be computed with, as the result of removing every member from a union, not to be inhabited.
Because the empty union canonicalizes to itself and Type Objects are interned (never has the same identity story as every other type. The name is admitted through let x: never; is writable, and the name is shadowable exactly as uint8 is. Admitting it does not extend switch exhaustiveness, which remains reserved to
A literal type has exactly one value. It is described by the string, a number or bigint as the literal is written, and true or false with base boolean. Infinity and NaN are Numbers that are written with a name rather than a numeral, and a default in a parameterization. A Symbol has no literal syntax, so no keyof over a symbol-keyed object type (
The one value of a literal type is a value of its base, so the type is a subtype of its base ('on' is usable where a string is required, and a value is of a literal type exactly when it is that value. A union of literal types is the closed keyof produces one (boolean and true | false have the same values and are distinct types: true | false is a subtype of boolean through the literal rule, and boolean is deliberately not a subtype of the pair, because completing the relation over value-
A literal expression whose let a: 'on' | 'off' = 'on'; gives a the literal type 'on'. A let s: string = 'on'; still gives s a string and let x: uint8 = 5; is unchanged.
The base of a numeric literal is number however the literal will be used, matching the uint8 3 as a type, is expressible through construction but not through syntax, and its interaction with literal propagation is settled where it was needed: a : constraint naming a W: uint32 binds the literal type of 4 over uint32 with 4 as its value view, and propagation elsewhere keeps base number. A Symbol is a literal type's value like any other, compared by unique symbol of an erased system has nothing left to do. Any Symbol qualifies, well-known, registered, or Symbol.for names itself by its key, while one from Symbol is
A metadata parameterization float32.<{ m: 1 }>; over any base it is reachable through construction (int.<N> is not a parameterization: structural arguments
A parameterized type has exactly the representation of its [[Base]]. Metadata never changes layout, alignment, or size.
A metadata record is the value of [[Metadata]]: a record whose keys are meta keys claimed by meta types (
The default relation is the branding rule. A parameterized type is a subtype of its base, so the brand is shed freely on the way up; the base is not a subtype of the parameterization, so the way down is a crossing: calling the UserId(7), is the construction boundary, and the metadata's
A .<> after a type that declares no type parameters supplies neither an argument nor a record, and for an argument of it to denote a type other than an object type, since a type is not a metadata value (P.<uint8> for such a P names no metadata. A uint8.<7> does (
The same rule reaches every parameter no written argument supplies, in three spellings that are therefore one type: a trailing position left empty, so Grid.<8> for a Grid<Rows: uint32 = 4, Cols: uint32 = 4> is Grid.<8, 4>; the empty list A.<>; and the BARE NAME of a generic declaration in a type position — an annotation, a parameter or return type, a heritage clause, a concrete generic bound, the operand of is or a when pattern, a type expression — so A for a class A<T: type = uint8> is A.<uint8>, and Box for a class Box<T: type> is a T. A bare generic name base of a generic node (
There is no bare instantiation for a bare name to denote. A construction of a generic class yields a let a: A = new A.<uint32>(1) would then be accepted for a class whose author wrote T = uint8. The family has a spelling of its own: an argument written any admits any instantiation in its position (Box.<any> is a Box of some Pair.<any, string> a Pair of some first type and a string second.
The argument's own form cannot decide this, and no rule reading it can. A metadata record is WRITTEN AS AN OBJECT TYPE, so Box.<{ a: uint8 }> is the same text whether { a: uint8 } is a type being supplied to a parameter or a record refining a base — the collision is total rather than partial. A rule keyed on the argument makes every generic applied to one object type unreachable, and is arity-dependent besides: two object arguments cannot be one record, so Pair.<{a}, {b}> would read as an application while Box.<{a}> would not.
Deciding by the declaration is what this specification already does for the two neighbouring ambiguities in this same bracket. Whether an argument is a type or a VALUE is not read off the argument — "the syntax need not distinguish the two; the clause on generics decides which a given parameter expects" (
Where the base is a type parameter the reading cannot be decided where it is written, since it depends on what the parameter is bound to; it is DEFERRED with the application and decided at instantiation. So type F<T: type> = T.<{ brand: 'B' }> is a declaration, and F.<string> is string.<{ brand: 'B' }>. A reading that dropped the arguments instead would make the brand its own base, which
A form that takes [4].<uint8> carries its arguments in its own production.
A parameterization applies to any T.<_A_>.<_B_> O["a"].<_A_> parameterizes an indexed access, and T.<_A_>["a"] reads an index of a parameterized one. Attaching arguments only to a
A value is of a parameterized type when its runtime type is that type or a subtype of it, or when it is a value of the [[Base]] and the validation judgment holds of the value and the metadata (validate" as admitting nothing, and the CONSTRUCTION BOUNDARY reads it as having no judgment to run. So UserId(7) produces a value and let x: UserId = 7 is refused, and a pattern - which does define a judgment - is decided by that judgment at both. An implementation that applies the membership test at the boundary makes a brand a type nothing can inhabit; one that skips it at an annotation makes a brand a claim the language does not check.
A parameterization is shed at a BOUNDARY and preserved through an OPERATOR, and the two are different questions. A boundary — an argument, an annotation, a return — is where a declared type says what is wanted, and that is what "shed freely on the way up" governs: UserId is assignable to uint32, so passing one where the base is declared succeeds and the brand is gone. An operator is arithmetic WITHIN a type: UserId + UserId is a UserId, and UserId + uint32 is a
UserId(7) is not 7 — while carrying no wrapper: typeof answers as the base does, and an array of them has the base's footprint. The saving is representational, not identity.
Parameterizing an already-parameterized type NESTS: the outer parameterization's [[Base]] is the inner type, and each layer's metadata is its own. So T.<{ brand: a }>.<{ brand: b }> is a third type refining the second, both judgments run at a crossing, and a brand composes with a pattern on the same value. A parameterization written with several keys at once merges them into one portion, which is the same type by
A union naming both a parameterization and its base is the base: a parameterization is assignable to what it UserId | uint32 is uint32 and admits any uint32, which is rarely what the writer meant. Two parameterizations of one base do not collapse, neither being assignable to the other.
The StringPattern source and flags are identical, which structural equivalence already makes one type, so the
Interning is what makes a brand a real newtype: brand(uint32, 'UserId') written in two modules is one type with no registry, because the metadata compares structurally and
The sanctioned sharpening of the pattern subtype judgment is the
type is the type whose values are the Type Objects (
A class type, as is its associated Reflect.TypeObject admits the same denotations. Admission preserves the original
type is not a type is nonetheless a primitive type in the sense of the
Because type is itself a type, type is a value of type, and a type argument may be constrained to it, which is what a generic parameter written T: type asks for.
The names number, boolean, string, symbol, object, and bigint denote the existing
Each is described by a object, which is described by { [[Kind]]:
number is a
The specification already treats a numeric type as a
For a numeric type T, the operations are those named in
Reusing this table rather than introducing a parallel one is what lets the existing operator semantics carry over unchanged. The specification generalized these operations from Number to two types when BigInt was added, and the
The table was written for types that are closed under their operations and totally lessThan, since the complex numbers are not lessThan. A rational type's operations can fail where a float's would round. These are the cases
Values of two different numeric types are never === and never the same value, as a Number and a BigInt are not; == compares their 1 == 1n does. Within one type, each type's own sameValue and equal rules hold.
For a proved intrinsic construction participating through declared type arguments or a
For an established intrinsic Promise.<R, E> construction, complete omitted type arguments by void. The rejecting function accepts a value of E. The resolving function accepts either a direct value of R, an intrinsic Promise, or a structural then can receive the two ordinary resolving callbacks. For R equal to void, the direct no-value input is
The intrinsic then member of function type (any, any) => any; its normal return is ignored. This does not infer arbitrary
Apply the static intrinsic rule only where lexical resolution, the actual intrinsic data binding and a bounded effect analysis establish the
At an actual intrinsic construction with declared type arguments, record R and E before calling the executor and adopt its callback context through the ordinary function boundary. These are internal type records, not observable properties or changes to the Promise's prototype. An eventual direct fulfillment value, after void admits fulfillment with
If a fulfillment or rejection boundary fails, reject the Promise with that boundary's exception, without checking the exception recursively against E. This is a failed type contract, not a successful fulfillment or a new application rejection payload. Existing intrinsic exceptions, such as self-resolution and a throwing then getter, retain ordinary Promise rejection behaviour. Preserve single settlement,
Lexical declarations and bound parameters are resolved before an intrinsic declaration of the same spelling. In an ordinary concrete type position a bare name and an empty application bind declared defaults, in declaration order, just as an explicit application does. Missing required arguments, unknown labels, duplicate arguments, a positional argument following a named one, excess arguments, and inadmissible domains are type errors at the application, including in an unused body. Defaults and canonical value arguments are completed before canonicalization or interning; an explicit default is never erased from a closed application's [[Arguments]].
| Declaration | Parameters in order |
|---|---|
int, uint | N: required |
rational | N: required bigint. |
complex | T: a permitted binary floating-point component type, default number. |
vector | T: required permitted lane type; N: required positive |
Range | T: required S: Bound, default Closed; E: Bound, default Open. |
RangeFrom | T: required S: Bound, default Closed. |
RangeTo | T: required E: Bound, default Open. |
RangeFull, RangeBounds, ClosedRange, ClosedOpenRange, OpenClosedRange, OpenRange | T: required |
All existing element, component, lane, and layout restrictions still apply. The intrinsic width-or-bigint domain is not a user-declared mixed type/value domain. rational.<int32> is invalid. rational64 and rational.<64> have identical complete arguments and are the same complex, complex.<>, and complex.<number> likewise have identical complete arguments.
Where a receiving parameter explicitly expects a higher-kinded declaration, resolve the declaration without applying its defaults. Intrinsic declarations participate in this context with their stated arity; application of a bound declaration validates its intrinsic domains. This does not introduce a public family-value API. Injected class names and user-class
Range literals retain their existing contextual endpoint rules and metadata's RangeBounds.<T> Range.<T>
Canonical display uses the standard aliases rational8, rational16, rational32, rational64, and rational128; other rational arguments use the explicit family application. Complex display uses complex for the declared default and the standard component-width aliases where defined. User aliases and module order do not affect this choice.
Within an argument position in a type parameter's constraint, the bare identifier _ is an anonymous Pair.<_> for Pair<T: type, U: type = T> requires equality, whereas Pair.<_, _> does not.
A Family Constraint Record contains an application head identifying an intrinsic or nominal declaration and any entry is the exact type any; the ordinary concrete A.<any> view is unchanged.
Supported heads are intrinsic families, nominal class or library declarations, and forwarding aliases expanding to those applications using fixed arguments and direct parameter references. Structural-interface existential patterns, computed-alias inversion, named captures in constraints, RangeBounds is supported by the declared range-
A matching witness is the candidate's canonical application or one of its nominal ancestor applications with substituted arguments. A range RangeBounds application with the same element argument. Metadata wrappers may be examined through their base to find a witness; this examination never alters the original candidate, its metadata, or the type bound to the parameter. A union satisfies a pattern only if every arm does. No conversion or enumeration of possible arguments is performed.
Match a witness and pattern recursively. Heads must identify the same declaration and have the same completed arity. A fixed argument must be the same canonical type or value as its candidate argument. A
A nominal head identifies the evaluated family, including its declaration identity under
Specificity is proven inclusion. Treat a source pattern's independent wildcards as opaque arguments and prove that every such binding satisfies the target pattern. Fixed arguments, supported equality correlations, and established nominal ancestry may establish inclusion. A user computation with an unknown result supplies no inclusion proof. Applicable candidates remaining incomparable under the ordinary selection rules are ambiguous, independently of declaration order. Static checking defers a candidate whose default computation cannot yet be evaluated to the ordinary binding check; it does not assume an inclusion fact. A bound neither permits mixed-width arithmetic nor changes invariant reference location types or liveness.
The abstract operation SatisfiesTypeConstraint takes arguments candidate (a
A built-in Promise, Date, RegExp, Map, Set, Error, WeakMap, WeakSet, Symbol, Proxy, ArrayBuffer and DataView all name types. Seven do not, and in each case the language already String, Boolean, Number and BigInt are excluded because the lowercase name IS the type. Array, Object and Function are excluded because the family has a structural spelling — [].<T>, { … } and (…) => T — and each of those spellings also provides the family's bound, which a bare name could not: a name gives only the top of the family, where the
Each type of this clause is denoted by a name. The intrinsic generic names denote types after required arguments and declared defaults have been bound (complex declares the default T = number; rational declares no default. The remaining names in
| Shorthand | |
|---|---|
int8, int16, int32, int64, int128 | int.<8>, int.<16>, int.<32>, int.<64>, int.<128> |
uint8, uint16, uint32, uint64, uint128 | uint.<8>, uint.<16>, uint.<32>, uint.<64>, uint.<128> |
boolean1 | uint.<1> |
boolean8, boolean16, boolean32, boolean64 | vector.<boolean1, 8> and so on for 16, 32, and 64 lanes |
rational8 | rational.<8> |
rational16 | rational.<16> |
rational32 | rational.<32> |
rational64 | rational.<64> |
rational128 | rational.<128> |
complex64, complex128, complex | complex.<float32>, complex.<float64>, complex.<number> |
never | the union of no members, the never type ( |
TxN, as in int8x16, uint32x4, float32x4, boolean32x8 | vector.<T, N> for the lane type T and lane count N named |
The shorthands are aliases, not distinct types, so int8 === int.<8> is complex expands through number rather than float64, so complex and complex128 are distinct types, as number and float64 are.
How these names are bound is not settled by the number, string, object, boolean, symbol, any, and never are among the most common identifiers in existing code. The available answers each cost something, and this specification decides for the one that costs existing programs nothing: the type names resolve, in either position, through the ordinary scope chain first and through the built-in table only where no user binding of the name exists; in a TYPE position always, and in an EXPRESSION position only within a source text that type operator form, const T = type uint8;, which is available everywhere and is the only spelling in a source text that does not admit them. So no existing use, assignment, shadowing, or typeof probe of these names changes meaning, because no existing source text admits them; and where one does, its author wrote the syntax that says so. This is the convenience the committee was told could be traded back in later, taken without trading the compatibility away first; a future edition may widen what admits, and could not have narrowed it.
Resolution finds a name; what it finds must BE a type. A binding that holds an ordinary value is not one, however exactly that value's type is known, so const q: uint8 = 1; let v: q = 2; is a uint.<8>. The two spellings that say what such a program meant are already available: const T = type uint8; names the type, and Reflect.typeOf(q) (q cannot be in a language where a name may hold either.
The error differs from the one an unknown name gets, and the difference is the useful part. Tokne is bound by nothing, so resolution walks the scope chain, finds nothing, and reports a q IS bound; it simply does not denote a type, which is a
A type operator, a conversion, an is, a type typeof and the target of an assignment. A reference that is the target of an assignment also does not RESOLVE to a type name, even where the text admits for some other reason: it resolves as it would without this typeof is excepted from admitting only, and resolves like any other reference where the text admits. It is a property of the SOURCE TEXT, determined when the text is parsed and fixed for its lifetime; it is not a property of the
Both halves of that are load-bearing. It is per source text because a typeof string in every one of them — the outcome this clause exists to prevent, reached by the mechanism meant to prevent it. And it is fixed at parse because a text that became admitting partway through its own execution would answer typeof string as
The second clause is stated as an exception rather than as a list of admitting positions, because a list is a losing game: the names are reached as a call, as the object of a member access, as an argument, as a property value, and as an operand of a comparison, and each is the same use of the same name. The exception is short and it is decidable, and it costs nothing that was not already lost. Referencing an undeclared identifier is a typeof reports
The assignment exception governs RESOLUTION as well as admitting, and that second half is what makes the first half worth anything. A text that reads one of these names anywhere admits, so a script that creates an implicit global - string = 5; with no declaration - and then reads it would otherwise have its assignment reach the type name instead of the global, and fail against an immutable binding. The assignment must land where it lands today. var string = 5 was never at risk, because a declaration is found by the ordinary scope walk before a type name ever is. The typeof exception needs no such half: a text that does not admit has nothing bound for the probe to find, which is the whole of what existing code requires, and a text that DOES admit has opted in, so reporting
What the exception buys is the reason it is needed at all: decimal128("1.0"), float32x4(1, 2, 3, 4) and complex64(1, 2) are how those values are CREATED, uint64.byteLength and decimal128.parse reach a type name as an object, and a program computing with any of them may write no annotation anywhere. Without the clause each would fail on the one name it needs. It is also what makes this specification's own examples consistent with the rule: uint8(1) + uint16(1) and uint8(1) == uint16(1) (
Two eval or the Function eval ALSO admits where the text it runs inside does, so a name reached inside it resolves as it would just outside it. An indirect eval and the Function uint8 resolve on one line and not the next.
Binding them as properties of the var string = "x" working, since a var at global scope reuses an existing property. But the var string at the top level of an existing script a
This is the one place where the
The rule is EVERY name this specification binds as a type, not only the primitives: the intrinsic metadata interfaces of partial declaration over one reaches it in type position rather than through an expression-scope binding. Stating it here once is deliberate. An implementation that binds these names in expression scope UNCONDITIONALLY, rather than only where the source text admits them, satisfies neither this clause nor its argument - typeof string answers var string = 5 does not take effect, and in strict code it throws - and the breakage falls on number, string, and object, which are the names this clause exists to protect. typeof itself is not touched by any of this (
This clause defines when a value of one type becomes a value of another. The governing rule is that it does not: a value of one
The rule exists because no widening is lossless for every pair. int64 to float64 loses precision above 2**53, and a language that admits the easy cases has to enumerate the hard ones anyway. Refusing all of them is the rule Rust, Swift, and Go use. It is livable here for the same reason it is livable there: the initializers and arguments that would want a conversion are usually literals, and a literal never needs one (
At an assignment, an argument, or a return, the source value's type must be assignable to the target type (any type, no numeric
Four things remain implicit. None is a conversion between two
| Case | Treatment |
|---|---|
| A literal in a | The literal has no type of its own and takes the type its position requires ( |
A value of the any type in a | The value is checked at the boundary. At a numeric target, a Number or a BigInt is converted unless the conversion would wrap, any does not authorize conversion between distinct numeric types. A value of the wrong type raises a |
| Two parameterizations of one base that differ only in their metadata | They convert by Kilometer and Meter are both float32, so this is a conversion within a type rather than between two. |
| A type that declares a conversion | The conversion applies at the boundary, because the declaring type opted in ( |
A function whose signature declares no return type may still have one, inferred from what its body returns. The inferred type is then the function's return type for every purpose a declared one serves: it is the
Override compatibility consumes any return remains unknown; return inference does not change the parameter-based identification of an override.
A
Resolve the parameter references in a formal or local binding's contract in that binding's declaration environment, including references nested within an annotated binding pattern or structural type. Retain those parameter declarations when a nested callable reads the binding, when contributions are projected through a constructed container or destructuring, and when dependent flow completes. A reader's same-spelled parameter does not reinterpret the binding's existing contract. Substitution closes only references to the declarations being applied and preserves a nested callable's own binders. A retained bounded parameter remains eligible for the existing symbolic completion judgments in its declaring context; its constraint neither replaces it nor identifies it with another binder.
Explicit, inferred and contextual generic arguments retain the correspondence to the selected callable's parameter declarations. Closures created during declaration instantiation retain the same correspondence as closures created during subsequent body evaluation. These requirements do not change argument evaluation, parameter initialization or body evaluation order, and do not permit executing a body or initializing a
For a method that adopts a contextual generic signature, its effective own parameters include the adopted parameter declarations. Use them in the reflected signature, callable compatibility and result instantiation as in invocation. Absence of a written
Retain the declaration identity of a supplied type argument before substituting it into an alias or a nested generic signature. A nested signature's same-spelled own parameter does not
When alias composition replaces a
function factory<T: type>(seed: T) {
return { get<T: type>(other: T) { return { captured: seed, own: other }; } };
}
const pair = factory.<number>(0).get.<string>("s");
const captured: number = pair.captured;
const own: string = pair.own;
The outer parameter supplies Number to captured; the method's own parameter supplies String to own. Extraction of get, or an inferred application establishing the same arguments, preserves this behaviour. A String consumer of captured is an established mismatch. Neither the inner parameter's spelling nor its argument converts the
A
An ordinary value-preserving immutable alias retains the callee's result dependency. Resolve each alias from its initializer's lexical binding declaration, not from an equal spelling at a return
Following an alias for contract instantiation does not evaluate its initializer or initialize its binding. Obtaining a
When a returned call supplies a nominal result dependency, retain the identity established by that callee invocation's result contract independently of the returned value. A receiving inferred result may use the retained dependency to instantiate the matching unresolved nominal declaration, but must still perform its own ordinary result admission. A retained dependency does not replace an already completed target or a written annotation, and an erased result contract does not publish a stronger hidden contract merely because its operand had one. Do not modify the shared symbolic publication with this invocation's identity.
For a generic local class, a nominal result dependency retains the evaluated family identity and the
These dependencies can occur in the result position of a returned callable. Retain the corresponding callable result context when admitting and forwarding that callable, while preserving its own generic binders as unbound declarations until application. Completing a callable contract does not invoke it, evaluate a
A callable may retain several signatures. Determine dependency correspondence for each target signature independently of signature-list position. A retained source contract may complete matching unresolved declaration dependencies in the target's parameter, result, receiver and narrowing type descriptions. Retain the target's structure, parameter modes and default values, and require exact correspondence of the completed effective contract, including an inferred result when no result is written. Preserve the correspondence of its own generic binder declarations; equal spelling or alpha-equivalent function types alone do not supply an unrelated binder's dependency. If several source signatures correspond, use their
An implementation dispatcher that forwards an ordinary overload call or a selected
For an inferred immutable binding initialized directly by an ordinary function expression or arrow function, the newly created function's
A source class reference in type position uses its declaration's evaluated family in the applicable lexical environment even if assignment has replaced the ordinary value binding. Respect lexical shadowing and the separate rules for aliases; do not skip a nearer binding to find an outer class. Ordinary value-position lookup retains its existing behaviour. While the class's type is still being established, retain the existing self-reference and forward-declaration rules. This rule neither initializes an unevaluated declaration nor establishes reference liveness.
A
The dependency follows the contributing completion through ordinary control flow. A normally completing finalizer preserves the pending return's dependency; a finalizer that replaces the return supplies its own
A value-preserving conditional or short-circuit expression retains the result dependency of the operand selected by ordinary evaluation; a comma expression retains that of its final operand. Unselected and discarded operands contribute no result dependency. Obtain the selected value by the ordinary evaluation and value-read operations, including their
When an ordinary immutable value binding is initialized from such a result, its inferred initialization target may use the retained dependency to instantiate a matching unresolved nominal declaration. Perform the binding's ordinary initialization admission before retaining its checked dependency on that binding instance. A written annotation remains authoritative, including an erased annotation; do not retain a stronger hidden initializer contract. Failed admission leaves the binding uninitialized and publishes no dependency. A subsequent ordinary read may contribute the initialized binding's checked dependency after the ordinary binding-read checks. Equal spelling, shared declaration syntax, another activation's binding or the stored value itself does not supply that identity. A failed assignment to an immutable binding does not replace its retained dependency.
Complete matching unresolved nominal result dependencies before reifying generic arguments under the receiving declaration's context. Establishing the outer family of an inferred P.<C> does not establish a local class C contributed by the checked result. This ordering applies at inferred result and immutable-binding boundaries; it must not turn a partially completed target into an authoritative completed application that discards the retained argument dependency. An already known outer family must agree with the checked dependency before any nested argument is completed. Already completed applications and arguments, written annotations and mismatched declarations remain authoritative. Use the retained checked dependency, not the runtime value's observed class, and perform no additional conversion, construction or reference read to discover the missing identity.
When a contributing result initializes an ordinary
When an inferred named-object boundary contains matching unresolved nominal dependencies, complete a private boundary contract from independently admitted source contributions at each property
Fresh object construction retains its evaluated source contributions in ordinary definition order. A later literal property definition or copied
After successful admission, retain the checked object view separately from its mutable backing contributions. An ordinary public property read, keyed binding extraction or property copy projects that view's applicable named property contract, after the required key conversion, read and reference-liveness checks. A projected written or erased member contract remains authoritative over stronger backing evidence. A
A computed Symbol key of an inferred named-object contract can depend on an immutable source binding whose ordinary initialization has not yet executed during checking. Retain that binding's declaration identity and applicable lexical environment. A checking identity for the key is not the Symbol that
At the existing runtime boundary, complete each still-symbolic named key in a private contract. Use the actual key supplied by the matching source
Retaining an initialized lexical key occurs only after successful ordinary initialization. It does not read through a reference, invoke a getter or coercion, repeat an initializer, or initialize a temporal-dead-zone binding. Follow an initialized import to its exporting binding under the existing module rules. A same-spelled binding in an unrelated caller supplies no identity. No key dependency establishes reference liveness or permits a read otherwise disallowed by the reference rules.
Fresh property contributions retain the keys obtained by their ordinary property-name evaluation. A copied
A key
The two activations create distinct keys despite equal descriptions and shared declaration source. Each inferred object boundary admits its own key, and the final expression evaluates to
function build(n: uint8) {
const k = Symbol("p");
const h = { [k]: n };
return h;
}
const a = build(1), b = build(2);
const ka = Object.getOwnPropertySymbols(a)[0];
const kb = Object.getOwnPropertySymbols(b)[0];
String(a[ka]) + String(b[kb]) + ":" + String(ka === kb);
For an extracted non-reference immutable binding, use the dependency of the property or
Obtaining an inferred result's nominal dependency does not execute its producer, read through a reference, initialize an uninitialized binding or establish reference liveness. An unavailable dependency is not an absent obligation or a reason to erase the result to
A returned generic signature introduces its own parameter declarations. Instantiating the enclosing result substitutes only parameters supplied by that enclosing environment, including free occurrences in the returned signature's parameter, result, receiver, narrowing, constraint and default descriptions. It does not apply the returned signature to arguments. A later application of that returned function binds its own parameters by
Whenever inference resumes a declaration or one of its contributions, use the same generic environment that declaration checking established: parameter declaration identities, effective bounds, value-parameter domains, variadic collection contracts and enclosing bindings. Retaining names alone is insufficient. The resumed environment still contains symbolic parameters until an application supplies their arguments; do not replace a parameter by its upper bound. This requirement applies equally to ordinary, async, generator and async generator functions. Suspending or resuming an inference computation neither initializes a value binding nor establishes reference liveness.
The inner parameter is distinct from the outer one. The expression evaluates to outer.<boolean> has the same result; explicitly applying inner later binds its own parameter.
function outer<T: type>(x: T) {
return function inner<T: type>(y: T): T { return y; };
}
const fn = outer(true);
const a: number = fn(1);
const b: string = fn("s");
String(a) + b;
An unchanged produce below enforces the instantiated Number contract and throws a get through a mutable binding would instead invoke the
function dynamic(): any { return "s"; }
function outer<T: type>(x: T) {
let unknown: any = dynamic;
const get: () => T = unknown;
function produce() { return get(); }
return produce();
}
outer(1);
For example, both closures below retain their respective
function make<T: type>(x: T) {
const captured = x;
function produce() { return captured; }
return produce;
}
const a = make(1);
const b = make("s");
const x: number = a();
const y: string = b();
String(x) + y;
In a declaration function f<T: type extends string>(x: T) { let value: string | number = x; const captured = value; }, the stored scalar domain and the T. Its constraint makes it usable as String, but assigning an arbitrary String back to a position requiring T is not justified. Forward helper chains over this
A contribution of a function is what return, or return carries no value or the body can complete without one, with a return in a nested function literal belonging to that literal.
A const, or declared let and never assigned within the function. A binding with a declared or inferred type contributes that type. For a binding that still has any, this let. const inference is specified independently in
Without this rule, extracting a subexpression into a local — the most ordinary refactoring there is — silently removes a function's inferred return type. function f(x: number) { return g(); } publishes what g returns, and function f(x: number) { const v = g(); return v; } would publish nothing if the local lost its initializer's const now preserves that information through its binding type as well; the let and for untyped constants. Nothing in the second program says that the type was lost.
Reading THROUGH the binding rather than typing it is the same move
The condition is that the binding cannot change, and not that it is const. A let never assigned is as transparent as a const; a let that IS assigned must contribute nothing, because a let v = g(); v = 5; return v; would publish the type of g's result and make a program that runs today throw at its own return.
A return _flag_ ? 1 : 2 an annotation on flag anchors nothing: the
A function or arrow expression contributes its callable contract, not merely the fact that its syntax is a literal. Determine its declaration provenance under the same lexical and contextual environments that determine that contract:
These rules do not execute a literal or its enclosing function, apply generic defaults, initialize a
The returned literal's annotated parameter and result anchor outer's callable R. This program has a use is uncalled; changing g(1) to g("ok") admits the consumer. The independent T declared by use does not replace the
function outer<T: type>() {
type R<U: type = T> = U;
return function f<V: type = R.<> >(x: V): V { return x; };
}
const f = outer.<string>();
function use<T: type>() {
const g = f.<>;
return g(1);
}
In function make() { return () => "ok"; }, the literal's privately known String result does not anchor make. In function make() { return (x: any) => "ok"; }, the literal make. An explicit function make() { return (): any => "ok"; } gains no anchor from that result.
A read of a function declaration's binding supplies callable provenance under the same known-position and participating-result rules as a function expression. The declaration's name and identity do not themselves supply an annotation. A privately known body result, an all-
A call's independently declared or participating
In function make() { function helper() { return "s"; } return helper; }, naming the helper does not make either function participate. Returning the equivalent function expression has the same outcome. Giving helper a String result annotation, or a participating inferred result derived from an annotation, anchors the factory's callable helper(): any retains the erased result even when its body returns a known literal.
For a
In let s: string = "s"; function g() { return s; }, the annotation on s anchors g even when the surrounding flow knows the singleton value "s". In function g() { const s = "s"; return s; }, naming and widening the literal alone do not make g participate. In let s: string | number = "s"; function g() { return s; }, the string | number; a later valid store of a Number cannot invalidate the
An object literal
In function make() { const x: string = "s"; return { x }; }, the object make. Replacing the result expression with { x, x: "s" } removes that anchor, unless another surviving member supplies one. A factory returning { get } similarly retains a local callable's participating result, including its generic nominal dependencies. The resulting checked object view cannot be retargeted by a later backing mutation;
An array literal
In function make() { const x: string = "s"; return [x]; }, the known element make. Naming [x] with an unchanged local or returning [[x]] retains that provenance. In contrast, function make() { return ["s"]; } gains no anchor from the literal alone. Spreading a mutable value annotated [].<string> does not by itself establish what its replaceable
A member read from a literal-derived aggregate retains the declaration provenance of the selected surviving read contract. Another member's annotation, the aggregate's overall participation, a computed key, or a setter's parameter alone does not annotate that read. Apply descriptor
An independently declared receiver contract, or a declared or participating
Retained origin information determines declaration provenance only. Static Types, member-read domains and bounds continue to be determined by the existing expression rules; selecting an origin does not narrow an array's uniform element domain or the member types derived from it.
An indexed read from an array literal retains evidence from the element contributions it may select. A known position does not gain provenance from another position; where the index is not fixed, combine evidence from the possible positions. For a spread, use only the positional or yielded information established by length gains no anchor from an element's annotation. These provenance judgments do not replace the array's
In function make() { return ({ seed: 1 := number, value: "s" }).value; }, the selected literal-only value does not anchor make. The same holds for the second element of ["s" := string, "t"]. Giving the selected member its own String contract supplies provenance. A function producer(seed: number) { return { value: "s" }; } instead supplies an independent object contract, so selecting producer(1).value is declaration-derived.
A conditional
A logical expression's &&, ||, and non-nullish part for ??. The right contributes only where the left can permit its evaluation. Project retained alternative origins through the same selection before combining evidence; an excluded alternative supplies neither a value nor an anchor. Determine operand evidence in the guarded and contextual environments used for its type. An annotation used solely to select the other operand supplies no evidence to that operand. A written conversion to a known non-
Preserve surviving logical
An optional-chain
Preserve this optional-result evidence through nested chains, unchanged aliases, inferred bindings, captures, imports,
A destructuring
A default contributes only where the extracted value may be
A rest binding retains evidence only from the collected values and preserves each retained member's evidence for subsequent selection. The rest container supplies no annotation. Object rest retains a known
An unchanged binding does not establish that the contents of its value are unchanged. A private aggregate initializer description used by
For a simple assignment to an established own
Where the destination, descriptor, stored value or intervening effects do not establish this transfer, discard the affected private aggregate descriptions. A call, potentially invoking property read, unknown member store, deletion, compound update or suspension cannot retain such descriptions merely because their binding is immutable. Array initializer descriptions do not establish a property-store transfer through mutable storage or a replaceable
Within a value-producing expression, determine each component's && and ||, retain the left operand's value ??, select that continuation from the
When a component produces a private aggregate, retain both its description at that position and its established alias identity. Restoring the incoming environment after a type query must not replace that result description with an earlier one. Binding or storing the selected aggregate preserves its alias identity for later modelled writes; a copied scalar instead preserves its already obtained
A computed member read retains the receiver value obtained before evaluating its key. Determine the key
Object and array construction determine successive component contributions in evaluation order. An object definition transfers its computed key and key conversion before determining its initializer
These construction judgments preserve descriptor
Process the modelled effects of computed keys and selected defaults before later pattern positions. Reading an absent own property can invoke an inherited getter; structural absence alone does not prove an effect-free undefined value. A skipped default supplies no transfer. Preserve the existing checks of all written defaults. These judgments execute no source, getter,
A function participates in inference when its signature declares a parameter type or a return type, or when at least one of its contributions is
A function that does not participate has no inferred return type and no [[InferredReturn]]; a call of it has the function g() { return 'foo'; } remains untyped at its own call sites even where a participating function returns g() and infers string from it.
Annotations are therefore the only seed. A source text containing none computes no inference, publishes nothing, and is unchanged in every respect, which is the compatibility statement of this
This is a position no shipped language occupies, and the neighbours are worth naming. Inferring for every function is what an erased checker does, and it is affordable there because nothing is enforced and no engine must do the work; here it would compute types for the whole existing web to no purpose. Inferring only where a signature is annotated is simpler still and loses the case that matters most in practice, the wrapper that returns what it called, so an annotation would stop being useful one call from where it was written.
A never. Otherwise use its most recent provisional never is the identity of union (uint32 publishes uint32 and one that only calls itself publishes never, which is what a function that never returns a value has.
Distinguish an unfinished never), and a completed unknown join. An unfinished prerequisite remains a dependency under never seed. A change to a private provisional result also requires reconsideration of its dependent contributions; the absence of a
Apply the participation and publication rules anew to the updated
The recursive dependency below initially contributes never, but first ultimately has an unknown join. That unknown result propagates to second; neither function retains an inferred never return contract. The program evaluates to "s". Replacing its binding declaration with const value := second(0); requires a
globalThis.seed = "s";
function first(n: uint32) {
return n > 0 ? second(n) : globalThis.seed;
}
function second(n: uint32) { return first(n); }
const value: string = second(0);
value;
Complete never as the initial
Reconsider a
A dependency read may use its available approximation while the recursive group is being solved. Reconsideration must propagate changed inputs until the group is complete; it need not recursively re-evaluate every previously answered dependency at each read. This permission does not make an answer permanent: the input-change and binding-invalidation requirements above continue to apply.
For a generic result dependency, retain the identity and declaration environment of the generic declaration before applying the caller's bindings. Substitute those bindings into the available provisional answer as well as any never
Repetition does not always converge: a recursive result can grow under a type function _w_(_a_: uint32) { return [_w_(_a_)]; } does. It is a
All three functions below infer uint32. Inserting further functions that only forward or join these results, or reordering the declarations, does not change that result. A String annotation on value instead produces a
function first(n: uint32) {
return n > 1 ? second(n) : (7 := uint32);
}
function second(n: uint32) {
return n > 1 ? third(n) : first(n);
}
function third(n: uint32) { return second(n); }
const value: uint32 = third(0);
String(value);
The unknown globalThis.seed absorbs the constructed recursive contributions. These functions therefore have no
globalThis.seed = "s";
function first(n: uint32) {
return n > 1 ? [second(n)] : globalThis.seed;
}
function second(n: uint32) { return [first(n)]; }
String(second(0)[0]);
Each function below forwards its own type parameter through the recursive group. The explicit application specializes the completed result to uint8. Replacing the annotation on value with string produces a
function first<T: type>(n: uint32, value: T) {
return n > 1 ? second.<T>(n, value) : value;
}
function second<U: type>(n: uint32, value: U) {
return n > 1 ? third.<U>(n, value) : first.<U>(n, value);
}
function third<V: type>(n: uint32, value: V) {
return second.<V>(n, value);
}
const value: uint8 = third.<uint8>(0, 7);
String(value);
A sufficient proof of
This sufficient proof is not a restriction requiring every recursive equation to have that form. A different proof may establish non-convergence. A repeated state establishes non-convergence only when it is the complete state of a deterministic
The projection in this equation removes a structural component at each recursive read. Its Leaf, whose declared getter result closes the projection. Adding further nested value properties increases the "ok" with const bad: number = read(0, new Leaf()); instead establishes an ordinary assignability error once the closure is complete.
class Leaf { get value(): Leaf { return this; } }
function read(n: uint32, leaf: Leaf) {
return n > 1 ? read(n, leaf).value :
{ value: { value: { value: { value: { value:
{ value: { value: { value: { value: { value: leaf } } } } }
} } } } };
}
"ok";
The domain of the fixpoint is the participating functions of the linked module graph, computed before any module in the graph evaluates. A function therefore has one inferred type wherever it is called from, and whether a callee is defined in the same source text, in another module of the graph, or in an earlier script of the same
"Before any module in the graph evaluates" is a phase that need not exist in an implementation before this clause requires it: a checker may run entirely at parse, where the graph is not yet resolved and an imported name is not yet anything. Such an implementation can satisfy this clause by checking a module twice — once at parse, which sees everything a module says about itself, and once when its imports resolve, with the types of the imported names supplied. Nothing is reported twice, because a module whose first check failed never reaches the second.
What a module records for its importers is the type of each of its own top-level declarations, keyed by the LOCAL name. Import resolution already answers with a module and a binding name, and that binding name is the local one, so a renamed import, a default export, and a re-export chain all resolve through the same lookup and none needs a rule of its own. A namespace import binds one name to every export, so its type is the object type of what the module declares; without that, the spelling of an import would decide whether a program is checked.
Importing or exporting a class does not itself make its methods participate in return inference. Apply
A namespace member with a statically established export key obtains its declaration information through the module's export resolution, using the resolved module and local binding. Do not match the public export name directly against the producer's local declarations. Renaming, default export names, re-exports and nested namespace exports retain the resolved declaration's identity and context. A proven namespace binding and its immutable aliases are distinct from an arbitrary object's structural type. Resolving a namespace member does not make a mutable producer binding immutable or establish a class origin that the existing binding-stability judgments do not prove.
When a namespace access establishes a class declaration, construction uses that declaration's instance contract and generic bindings; value access to the
An established unapplied class export default declaration. Named imports, re-exports and namespace member access use the producer's origin proof and the original declaration's checking environment. The alias introduces no new class identity. Retain the established
An import's immutable local binding does not establish stability of the producer's export default expression captures its value at ordinary evaluation, whereas a named export of a local binding remains a
A construction through an unapplied alias binds its arguments under
An established expression-position application of a class
The selected arguments determine the constructed instance contract,
An unavailable required default or other application prerequisite remains a checking dependency under
A fresh explicit
function make<T: type>(value: T) {
class Box<V: type> {
value: V;
constructor(value: V) { this.value = value; }
get() { return this.value; }
}
const Selected = Box.<T>;
function read<T: type>(ignored: T) { return new Selected(value).get(); }
return read;
}
const text: string = make.<string>("ok").<uint8>(1);
The result make's parameter; read's distinct parameter does not alter it. Replacing the destination annotation with boolean is a
The sufficient proof in
For this proof, establish all of the following before classifying a component as growing:
A
The first equation constructs an array containing the second result; the second only copies the first. Together they form a growing cycle and require the non-convergence error in either declaration order.
function first(n: uint32) { return [second(n)]; }
function second(n: uint32) { return first(n); }
"ok";
Here the wrap does not make their cycle grow, and this source evaluates to "ok".
function wrap(n: uint32) { return [first(n)]; }
function first(n: uint32) { return second(n); }
function second(n: uint32) { return n > 1 ? first(n) : 0; }
"ok";
The property projection removes the component that the object literal constructs. Treating the first call as a constructing edge while ignoring that projection would wrongly reject this "ok"; replacing the last expression with const bad: string = second(0); requires the ordinary inferred-return assignability error.
function first(n: uint32) { return { value: second(n) }; }
function second(n: uint32) { return n > 1 ? first(n).value : 0; }
"ok";
This judgment states proof premises and their consequence. It does not prescribe a graph representation, traversal order, source-syntax recognizer, or a resource allowance. In particular, a production implementation can use shared type representations and iterative component algorithms without modifying user-object layouts, runtime call-
Resource exhaustion during a required return-
The limits may decide whether checking completes, but never change a completed result into a different type or into a completed unknown join. Native stack or heap failure is not a non-convergence proof. The implementation must report exhaustion before exhausting its own capacity for the operations it meters, under the same containment principle as
The accounting
Distinct-answer accounting associates each answer with its source function declaration or function-producing syntax node and with the requested
never, and a completed unknown join are answers. An unanswered declaration-contract prerequisite is not a completed unknown answer and creates no history entry; the attempted query still consumes its never never or an explicit This history is an accounting device, not a query cache. Equal component answers can arise from different parameter types, substitutions, receiver types, source environments, contextual targets or dependency states. Their accounting equivalence does not authorize reuse in another context, suppress recomputation after input invalidation, or complete an unfinished dependency. Apply
The arrow's return "ok".
const identity = (value: uint32) => value;
"ok";
The yielded and completion answers are both uint32, but belong to different components. Recording those two answers consumes two
function* values(value: uint32) {
yield value;
return value;
}
"ok";
Implementations may use immutable type records, persistent versions, canonical type graphs or private snapshots to retain history. No particular allocation strategy or traversal schedule is prescribed. These rules do not
A logical inference query identifies the judgment being requested, its source
The applicable checking context contains the inputs prescribed by the requested judgment. The following distinctions must be retained whenever that judgment can observe them. An implementation may omit an input only when its irrelevance to that judgment is established; an absent input and an unfinished required input are not interchangeable.
| Input | Required distinctions |
|---|---|
| Source and lexical resolution | The source |
| Parameters and generic bindings | Effective parameter contracts and binding kinds; names, order, defaults, rest and reference positions where used; generic binder declarations, constraints, value-parameter domains, substitutions and applicable instantiation identities. Pairing generic binders for type equivalence does not replace their identities in a declaration-context query. |
| Contextual contracts | The contextual signature or overload alternative, declared result and generator component contracts, and any contextual adaptation required at the source position. An equal current inferred return does not equate different contextual alternatives. |
| Receiver and captures | The applicable receiver contract and its declaring |
| Flow and completion | The source-position current-value domains, effects, places, admitted predecessors and completion state required by the transfer. A |
| Checking boundary | The containing source check and its applicable phase, enabled |
A reusable outcome additionally has a validity relation to every required dependency. A dependency observation identifies the depended-on judgment in its context, the part of its outcome consumed, and whether that part was unfinished, a recursive approximation, or completed. Completed known types, completed unknown joins and absence of a
These requirements apply to reuse within a check and to any reuse across checking boundaries. An implementation need not retain outcomes across checks. If it does, it must establish the applicable source and dependency identities anew and satisfy all required obligations in the new check. A completed
The yielded String violates the generator's contextual component contract. The Script has an assignability "s" with 1 is valid; adding an applicable alternative whose yielded component is string also permits the String operand.
const value: { item: [].<Generator.<uint8, void, void>> } = {
item: [do * { yield "s"; }]
};
The two applications below have different generic bindings even though they use the same declaration text. The nested read uses the contract of copy in its own applicable context. The source evaluates to "s1". Changing the annotation of b to string requires an assignability error before Script evaluation; an earlier String application cannot supply that second application's contract.
function identity<T: type>(value: T) {
const copy = value;
function read() { return copy; }
return read();
}
const a: string = identity.<string>("s");
const b: number = identity.<number>(1);
a + String(b);
These are semantic reuse conditions, not a required key layout, fingerprint algorithm or incremental compiler architecture. A production engine may intern immutable contexts, retain versioned dependency graphs or recompute judgments. Such compiler-owned structures require no change to user-object shapes or runtime inline caches. A cache miss is not a new language error, and an abandoned resource-limited check supplies no reusable completed outcome.
The
var redeclaration, including a destructuring one, does not erase the contract of the binding it reuses. Preserve the parameter/body distinctions required by var loop-head binding belongs to the enclosing variable environment. Preserve the base-language head, Every
A local class
Retaining a
A transparent initializer let does not acquire a contract from the enclosing
Preserve receiver binding as well as named bindings when querying a function's result. An ordinary function declaration or expression has its own this binding and uses its applicable declared or contextual receiver contract, if any. It does not inherit a class or method receiver merely because the query occurs inside that body. An arrow uses its lexical receiver. This judgment changes neither runtime receiver binding nor reference
These rules select bindings; the existing
In function f(x: string) { let g = x; { let g; g = 1; } return g; }, the returned g is the unchanged outer alias and contributes string. The inner assignment changes another binding. Conversely, in function f(g: string) { { return g; let g; } }, the return reads the inner unknown binding and cannot borrow the parameter's annotation. If executed, that read retains its ordinary temporal-dead-zone failure.
The results required by
These dependencies cross function forms. Inference of an ordinary return may require an async result or a generator protocol, and inference of an async result or delegated yield may require another declaration's result. Apply the
For an async fulfillment
For generator inference, an unknown yielded or returned never, while
An implementation may prepare a declaration group eagerly, resolve dependencies on demand, or use an equivalent schedule. It must produce the same required acceptance and rejection results. An acyclic dependency chain is not non-convergent merely because its declarations are encountered in the reverse order or because it exceeds an implementation's chosen number of whole-scope passes. Module dependency availability and failure order remain governed by
Contract preparation does not change ordinary initialization order or reference liveness. If no
An obligation that consumes an inferred binding contract follows
Default-value compatibility is a consuming obligation. Apply these requirements to parameter defaults, defaults within binding patterns, and instance or static
A parameter default retains the parameter environment in which it is defined, including parameter binding identities and their applicable initialization state. A body declaration does not become visible to that default merely because the body has been visited before the obligation is completed. An untyped or unresolved parameter still shadows an outer declaration. A binding-pattern default retains its pattern context. A
These environment requirements hold through nested dependency requests. Temporarily checking a producer in a different declaration environment does not end a suspended parameter-default judgment or make the producer's body bindings visible to it. On resuming the requesting judgment, retain its original binding identities and source state. A reference in a parameter initializer, including within a closure created there, does not resolve to a body-only var or function declaration. This separation also applies to async and generator functions. A diagnostic's related declaration, when provided, is resolved in the same source environment as the checked reference.
Completing an obligation does not change the applicable compatibility, participation, conversion, default evaluability or reference rules. In particular, ordinary function parameter defaults do not acquire a new compile-time-evaluability requirement through this clause, and a default already subject to such a requirement remains subject to it. A diagnostic produced by a completed obligation retains the original consuming source location and checking
Reconsidering an obligation retains the identity of its consuming source judgment and applicable context. Replace that obligation's unfinished inputs with the dependencies required by the reconsidered judgment; merely reconstructing an environment or revisiting an initializer does not create a distinct obligation or establish progress. Complete newly required producer judgments under their own rules, and finalize the consumer only from established inputs. This requirement does not permit discarding an unfinished judgment, treating it as
Checking the object initializer below may request the contract of captured while inferring produce. Reconsidering the initializer is the same obligation in the same contextual position. It must complete without an unbounded sequence of copies of that obligation. The method call returns "s"; a Number annotation on value instead produces a produce before the initialization of captured retains the runtime temporal-dead-zone failure.
type Shape = { value: string, read(): string };
const object: Shape = {
value: "s",
read() {
const captured = this.value;
function produce() { return captured; }
const value: string = produce();
return value;
}
};
String(object.read());
A chain of inferred bindings is subject to the same dependency relation as an inferred callee result. Completing one initializer can establish the input of another initializer, whose contract in turn establishes an input of a consuming obligation. Retain each initializer's own incoming flow and contextual environments through that chain.
This dependency relation also applies to inferred local binding contracts requested while computing a nested function's contributions. Before answering such a request, identify the binding in the declaration environments of
A required reference-binding contract is also an initializer dependency. Apply
Keep the
The reference s has the String contract of the parameter x, even though the g requests it before the declaration is visited. The Number consumer is a s before its initialization and retains the ordinary temporal-dead-zone failure. If x instead has type string | number, a preceding String store or guard does not replace the location's union contract with the narrowed
function outer() {
function f(x: string) {
return g();
function g() { return s; }
let ref s = x;
}
const q: number = f("s");
}
An unavailable
If a
For a
Preserve each selected entry while computing subsequent label entries: effects in a later label do not become effects of an earlier successful selection. Conversely, effects in labels actually reached along the failed-selection
A delayed expression-type query retains its source position's normal-completion state as well as its flow facts. A return or yield
A typeof result. Apply the effects of each reached label to those relations before selecting its entry. Pass the remaining alternatives along the failed-selection
The incoming flow used by a
Source flow includes the current-value facts established by successful participating initializations and stores under void; a more specific initializer
A provisional callee result available only to
Apply the existing effect and invalidation rules in evaluation order, including between bindings in one declaration and across suspension. A fact
In this example the only normal predecessor of s has narrowed x to string. The inferred contract of s, and hence the g, is string. The String consumer is compatible; changing its annotation to number establishes a return s in g with return x does not give the mutable parameter x the contract of s.
function outer() {
function f(x: string | number) {
if (typeof x !== "string") return "s";
const s = x;
return g();
function g() { return s; }
}
const q: string = f("s");
}
A computation proved independent of incoming flow may be prepared without completing that flow first. This is a scheduling freedom, not a different
Apply a binding form's validation to the result of its completed initializer computation. In particular, the requirement that a
These requirements apply at the checking boundary of
In the following source, the initializer of s has string from the established store to u. The incompatible initializer of q is rejected before the Script body begins. Changing its annotation to string removes this incompatibility; ordinary execution still throws a Reference Error when g reads s before initialization. Changing u after the declaration of s does not change s's inferred contract.
{
let u: string | number = "s";
const q: number = g();
function g() { return s; }
const s = u;
}
The same incompatibility must be established if s instead initializes from h() and h returns a later const t = u. Computing t's contract establishes the input needed to infer h, then s, then g, then the initializer obligation of q. Using let s := h() applies its widening and non-
Likewise, in function f(x: string) { return g(); function g() { return s; } const s = x; }, the f, even though its declaration follows the return. A Number consumer of f is incompatible. A String consumer removes that incompatibility but does not initialize s before the call of g; the ordinary temporal-dead-zone behaviour remains.
In this example the assignment establishes a String current-value fact for x before s is initialized. The constant captures that value contract, and g contributes String. A Number consumer of f is incompatible. Initializing a local let x: string | number = "s" before the same
function outer() {
function f(x: string | number) {
x = "s";
const s = x;
return g();
function g() { return s; }
}
const q: string = f(0);
}
If the parameter instead has type uint8 | string and the assignment is x = 1, apply the ordinary numeric-literal conversion before establishing the stored domain. The uint8, not Number. By contrast, storing { v: "s" } into a location declared { v: string | number } does not change its mutable member's contract to String.
The g requires the contract of s. The guard establishes String at the source position of that initializer. Discovery of s cannot stop merely because the earlier query of g had no completed answer. The String consumer is compatible, and a Number consumer is incompatible.
function outer() {
function f(x: string | number) {
if (typeof x !== "string") return "s";
return g();
function g() { return s; }
const s = x;
}
const q: string = f("s");
}
A read of an inferred binding consumes the completed contract required by
These requirements constrain the final judgments, not the timing of an implementation's internal requests. Eager completion, demand-driven completion and deferred reconsideration are permitted when they give the same required judgments. They neither evaluate initializers nor initialize value or reference bindings, prove reference liveness, permit check elision, change a mutable location's contract, or add participation to ordinary untyped mutable storage.
The completed contract of s is String, so f publishes String and the participating constant q has that contract. The deciding test is a q while s was unfinished. Replacing the if statement with const answer = typeof q === "number"; leaves a permitted value query. Changing return flag ? s : "s" to return s retains the String contract, but a compatible program that calls outer then encounters the ordinary temporal dead zone.
function outer(): string {
const q = f(false);
function f(flag: boolean) { return flag ? s : "s"; }
let source: string = "s";
const s = source;
if (typeof q === "number") {}
return q;
}
outer();
The completion requirements of
Call-effect invalidation identifies lexical bindings and their invocation-owned cells, not merely names occurring in assignment syntax. For the bounded analysis in
Apply this distinction to the existing call, getter, setter, conversion and suspension effects and to construction, including arguments can expose its enclosing ordinary function's object. Unmapped arguments, including those of
These requirements impose neither execution of source expressions nor a particular inference queue, flow representation or object layout. They do not alter initialization state, temporal-dead-zone failures, type participation, storage contracts or reference liveness. In particular, retaining an inferred store dependency cannot authorize a later use of an expired reference.
The first store establishes a String current-value fact for u once the required contracts are completed. The constant s captures that value contract, so g contributes String. The Number consumer is a outer still encounters the ordinary temporal dead zone in f.
function outer(u: string | number) {
u = f();
const q: number = g();
function f() { return t; }
function g() { return s; }
const s = u;
let v: string | number = "s";
const t = v;
}
A subsequent u = 1 before const s = u makes s s to Number. A call between the store and u if callable code captures and may write that binding. The assignment to u in outer itself is not such a
An unfinished input to a deciding test leaves a checking obligation under
A call within the tested expression does not exempt the test from these requirements. At the original call position, establish the callee and argument contracts and the applicable call effects in evaluation order, then use its completed result contract for the test. Reconsideration is a static judgment: it does not call the function, execute its argument expressions, or repeat a program effect. A
Source-order checking within unreachable source retains the environments needed by its ordinary type judgments and inferred contracts. A declaration initialized there is judged using its initializer's source position, including preceding declarations in the same unreachable sequence. Its facts and completions do not create a reachable successor or replace an enclosing return, throw, break or continue. Inferring such a contract neither evaluates the initializer nor initializes its runtime binding.
A completed declaration contract, a current-value fact, a branch-feasibility proof and a reference-liveness proof remain distinct. Completing an independent producer does not complete every fact in its surrounding environment. These requirements allow different dependency schedules and shared compiler-owned representations, but no implementation-selected retry count or source-order cutoff changes a required
The test is encountered before the independent String producer. Once the input contracts are completed, it is an impossible test. Changing number to string makes the test always true, also an error in this deciding position. A declaration const answer = typeof u === "number"; instead remains a permitted value query.
function outer(u: string | number) {
u = f();
if (typeof u === "number") {}
function f() { return t; }
let v: string | number = "s";
const t = v;
}
Adding u = 1 after the test cannot change its earlier String input. A relevant return typeof u === "number" ? "a" : "b"; still requires its deciding-test judgment; checking the following initializers does not make them reachable or remove the temporal-dead-zone failure from an otherwise compatible invocation.
The completion requirements of
A generic producer's applicable context distinguishes its parameter declarations, effective constraints and value-parameter domains, as well as any substitutions or instantiation identities required by the judgment. Equal written parameter names or assignable constraints do not identify the same binder. An unspecialized declaration context is not interchangeable with a substituted context solely because their current output types coincide. Preserve these distinctions when completing a nested producer. A surrounding generic parameter that is not an input to the local computation does not make that computation unfinished: the existing
These completion requirements apply to declarations, function expressions, arrows, object and class methods, accessors and resumable function forms under their respective inference rules. Replacing one enclosing function form by another does not itself exempt a
For a tested expression whose result environments are composed from component expressions, apply source-proof dependencies at the component's evaluation position. An unfinished right operand or conditional arm does not revoke an independent input proof on a continuation that skips that component.
A completed component judgment retains its justified output domains even when no other judgment was unfinished at that component's source position. A later component's unfinished input does not retroactively make those outputs unfinished. Use the retained domains on the continuations to which they belong, together with the effects, refinements and selection dependencies of each intervening judgment. This requirement applies equally to an immediately completed input and to the same input completed after a dependent judgment was suspended. It does not complete a stored value from its destination annotation, discharge another component's inputs, or make different predecessor domains independent of an unfinished selection.
switch transfer has the following additional requirements:
A loop's entry and any zero-iteration predecessor retain their applicable completed domains independently of unfinished judgments encountered only in the body. Establish those domains after the initializers,
Where the prescribed loop widening removes a refinement and supplies an established contract domain, that widened output is complete when its contract and the inputs required to identify the affected place are complete. It does not require the superseded first-iteration value or a later body test to complete. Establish this output at the loop entry and carry it through the applicable iteration-ending predecessor; this is the specified loop judgment, not an assumption that a body store or call ran on that predecessor. Identify stores by lexical declaration and member
match, complete the selected arm's result and outgoing facts, and propagate applicable failed-pattern and failed-guard states to subsequent clauses. For match all, complete the matched, skipped and failed-guard continuations that reach each later clause, including the effects of earlier executed arms. Joining those states does not establish that an earlier arm executed. Clause-local bindings do not escape into another clause or the surrounding continuation.A whole-expression truthiness judgment for a
No earlier unfinished guard is needed for the following chain's completion. Retain completed component outputs before considering unfinished inputs in later components. The String-only consumer is a
function outer(flag: boolean) {
const value: string = f0();
function f0() { return t0; }
function f1() { return t1; }
function f2() { return t2; }
function f3() { return t3; }
let u0: string | number = f1();
if (flag && id(u0)) {}
const t0 = u0;
let u1: string | number = f2();
if (flag && id(u1)) {}
const t1 = u1;
let u2: string | number = f3();
if (flag && id(u2)) {}
const t2 = u2;
let last: string | number = "s";
const t3 = last;
function id(x: string | number) {
u0 = 1; u1 = 1; u2 = 1;
return answer;
}
const answer = seed;
let seed: string | number = "s";
}
Under the existing store and return rules, t3 and f3 have String domains. The initialization of u2 therefore supplies String. The continuation that skips id(u2) retains that source domain; the evaluated continuation receives String-or-Number from the t2 and f2. Completing the preceding stores, component judgments and joins supplies String-or-Number to t1, f1, t0 and f0. The forward source read of annotated seed completes answer with String-or-Number independently of those selections. None of these judgments executes a call in the uncalled body or changes the temporal dead zone. Removing the three writes from id instead preserves String through the
The preceding chain has the same required domains when its enclosing declaration is written as const outer = function (flag: boolean) { ... };, as const outer = (flag: boolean) => { ... };, or as the body of an object or class method with the same parameter contract. In each case, use the inner declarations' own enclosing context. The chain contains no receiver read, so changing the enclosing form does not introduce a different receiver-dependent input.
The same chain also has those domains in function outer<T: type>(flag: boolean) { ... } and in the corresponding generic expression or method. The local producers do not read T, so requiring that unused parameter's argument would introduce a dependency absent from their judgments. A generic class containing the method and an arrow nested within a generic declaration have the same completion obligation. A value-parameter or constraint-dependent producer instead uses that parameter's actual domain and constraint in its declaring context.
For a receiver-dependent producer, the context does matter. In the following contextual method, this.value supplies String to the local producer. Both the Shape as the
type Shape = { value: string, read(): string };
const object: Shape = {
value: "s",
read() {
const value: string = produce();
function produce() { return captured; }
const captured = this.value;
return value;
}
};
This is a static judgment of the uncalled body. Invoking read before the initialization of captured would still encounter the temporal dead zone at the earlier call to produce; completion of the declaration contract does not initialize the
The unfinished guard does not make the source contract of seed depend on the match's selected arm. Completing answer and id allows the seed would be a separate declaration and would not replace the outer source read.
function outer(subject) {
match (subject) { when _ if (f0()): 0; default: 1; };
const value: number = f0();
function f0() { return t0; }
function f1() { return t1; }
let u0: string | number = f1();
if (id(u0)) {}
const t0 = u0;
let last: string | number = "s";
const t1 = last;
function id(x: string | number) { return answer; }
const answer = seed;
let seed: string | number = "s";
}
Separately, if (decision ? flag : flag) {} with independent Boolean parameters decision and flag has an open Boolean truthiness domain. Neither visit order nor the facts on the false outcome of the second arm make it a constant-condition error. The same source-context requirement holds when an arm produces flag through a match expression.
A u0 and supplies its established String-or-Number domain. Completing that output permits t0 and f0 to complete even while the earlier match guard is unfinished. The String-only consumer is incompatible, as u0 contains the Number written inside id; use the existing effect judgment, not execution or inlining of its body.
function outer(subject) {
match (subject) { when _ if (f0()): 0; default: 1; };
const value: string = f0();
function f0() { return t0; }
function f1() { return t1; }
let u0: string | number = f1();
if (id(u0)) {}
const t0 = u0;
let last: string | number = "s";
const t1 = last;
function id(x: string | number) { u0 = 1; return answer; }
const answer = seed;
let seed: string | number = "s";
}
The uncalled body is checked without evaluating its calls or initializing its bindings. The temporal-dead-zone and reference-liveness rules remain separate. If a surrounding short-circuit expression can skip the effect, the continuation also requires the skipped predecessor's domain; completing the evaluated predecessor alone is insufficient.
The right-hand call below can be skipped. Its call effect widens u0 only on the evaluated continuation; the skipped continuation retains its own stored-value dependency. Completing both continuations makes t0 and the result of f0 String-or-Number, so the String-only consumer is incompatible. A String-or-Number consumer is compatible.
function outer(flag: boolean, subject, items) {
match (subject) { when _ if (f0()): 0; default: 1; };
const value: string = f0();
function f0() { return t0; }
function f1() { return t1; }
let u0: string | number = f1();
if (flag && id(u0)) {}
const t0 = u0;
let last: string | number = "s";
const t1 = last;
function id(x: string | number) { u0 = 1; return answer; }
const answer = seed;
let seed: string | number = "s";
}
Replacing the if statement with for (const item of items) { id(u0); } also makes the consumer incompatible. Here the existing loop-widening judgment supplies the completed String-or-Number entry domain, including the iteration-ending predecessor. It does not prove that the body executes. Replacing that loop with for (let u0 of items) { u0 = 1; } writes a distinct loop binding; those stores supply no widening of the outer u0.
The unfinished switch discriminant below does not change the established String-or-Number source contract of seed. That contract permits answer and the result of id to complete. The String stored in last then supplies the result of f1, the t0, and the result of f0. The case "number" selection is impossible. Replacing it with case "string" removes this error. No call or initializer in the uncalled body is executed to obtain these judgments.
function outer() {
switch (typeof f0()) { case "number": break; }
function f0() { return t0; }
function f1() { return t1; }
let u0: string | number = f1();
if (id(u0)) {}
const t0 = u0;
let last: string | number = "s";
const t1 = last;
function id(x: string | number) { return answer; }
const answer = seed;
let seed: string | number = "s";
}
The read that infers snapshot uses the established String-or-Number contract of seed. Neither the unfinished result of test nor the later String initializer changes that read's source-position domain. Completing snapshot and then test therefore rejects the String-only consumer. Changing that consumer to String-or-Number removes this contract error; it does not remove the runtime temporal-dead-zone failure of the earlier call to test.
function outer() {
if (test()) {}
const snapshot = seed;
let seed: string | number = "s";
const value: string = test();
function test() { return snapshot; }
}
The following transfer families retain their ordinary evaluation-order and flow rules while these dependencies are completed.
| Source form | Required dependent outputs |
|---|---|
if, conditional expressions and logical short-circuit expressions | Each evaluated or skipped alternative, its admitted outgoing facts, and the continuation join. Deciding roles are unchanged; a value query does not acquire a deciding role. |
while, do, for, for-in, for-of and for await-of | The prescribed entry, test, body, update or iteration transfers, back edges, and normal, break and continue successors. Iteration and suspension effects still apply at their specified positions. |
match and match all | The subject and clause-entry state; pattern effects and bindings; matched, failed-pattern and failed-guard successors; arm result and match all retains subsequent-clause paths after a successful arm. Existing per-clause and exhaustiveness distinctions are unchanged. |
switch | The |
Blocks, labelled statements, try, catch and finally | The applicable lexical environments and normal or finally. Completing unreachable source does not create a reachable successor. |
| Assignments, initializations and value consumers | The established destination contract, the converted stored-value domain, applicable effects, and every inferred |
The first test has an open String truthiness domain and is permitted. Its continuation still carries String, so the second test is impossible. Replacing the second test with a switch label case "number" for switch (typeof u) retains the impossible-selection judgment.
function outer(u: string | number) {
u = f();
if (u) {}
if (typeof u === "number") {}
function f() { return t; }
let v: string | number = "s";
const t = v;
}
Replacing the second test with const q: number = g();, and adding function g() { return s; } const s = u; before the independent initialization of v, instead establishes an incompatible inferred-return consumer. The corresponding String consumer is compatible. An initialization let u: string | number = f(); requires the same completion of the stored-value domain as the assignment shown above.
Each completed String producer below supplies the next
function outer() {
const q: number = f0();
function f0() { return t0; }
function f1() { return t1; }
function f2() { return t2; }
function f3() { return t3; }
let u0: string | number = f1(); if (u0) {} const t0 = u0;
let u1: string | number = f2(); if (u1) {} const t1 = u1;
let u2: string | number = f3(); if (u2) {} const t2 = u2;
let last: string | number = "s";
const t3 = last;
}
Replacing each if (u0), if (u1) and if (u2) with the corresponding if (Boolean(u0)), if (Boolean(u1)) and if (Boolean(u2)) retains the incompatible Number consumer when those calls denote the original Boolean function. So does using if (id(u0)), if (id(u1)) and if (id(u2)) with a local declaration function id(x: string | number): string | number { return x; }. No specified call effect changes the local primitive places in this example. A declaration or argument whose effects may change those places instead invalidates their proofs under the ordinary rules. A same-named
These requirements specify source judgments and their dependencies, not a traversal count or compiler architecture. An implementation may recompute a source
A class method's result contributions are computed in its declaration environment. The receiver judgment is independent of the caller, the source order of the class's members, and the order in which an implementation requests their contracts. This requirement also applies when the only consumer occurs in an uncalled function or otherwise unreachable source.
For a class method without a declared result type, apply the following requirements together with
this has the declaring class's instance type over its own type parameters. For a static method, it has the declaring class's class-object view with static members. An instance member does not become a static member, or conversely. Class and method type parameters retain their lexical scopes; a same-named method parameter shadows the class parameter where its scope applies.Computing this receiver environment neither binds a runtime method value to a particular receiver nor executes a
class C {
result() { return this.value(); }
value() { return this.text; }
text: string = "s";
}
function unused() {
const q: number = new C().result();
}
Both methods publish String, so the initializer of q is a text or value earlier does not change that judgment. Changing the annotation of q to string removes this incompatibility. If text instead has the contract string | number, its String initializer does not justify a String-only
Apply
An unannotated getter has no parameter annotation to establish participation. Publish its inferred result only under the existing
A getter result that depends on a class parameter retains that parameter's declaration through member lookup and
When an inferred union result contains such local class contributions, an independently admitted producer result may complete a matching
These judgments execute no getter,
class Entry {
get value() { return this.text; }
text: string = "ok";
}
function unused() {
const wrong: boolean = new Entry().value;
}
The getter publishes String, so the initializer is a unused is called. Replacing Boolean with String removes this incompatibility. Replacing the getter body with only return "ok"; removes the annotation-derived
An object literal's static
f contributes the same value contract as f: f. Resolve that read in its source lexical environment and complete the callable-value dependencies required by For example, function outer() { const box = { m(x: string) { return x; } }; const target: { readonly m: (x: string) => number } = box; } is a outer is never called. Replacing the method with a shorthand property carrying a local String-returning function preserves that judgment. A target requiring a String result is compatible. This requirement does not make an untyped mutable binding acquire an inferred storage contract or change ordinary property evaluation order.
Result inference can depend on a function value without containing a call of that function. A
never type. Apply the recursive dependency rules to the resulting type obligations; do not claim that reading or returning a function value cannot complete because calling that function would recur.Determine which signatures a callable value retains by
A method value obtained from an established member contract retains its participating inferred result, including open parameters of its own declaration or an enclosing declaration. Resolve a local class object's static member contract through the class binding at the contributing source position, including when a required query precedes the ordinary source walk of that class. An unrelated same-spelled declaration supplies no contract. Retain the method's receiver requirement through extraction, reflection and
Instantiating an inferred binding with its enclosing generic environment substitutes the free inputs of any retained callable signature, including its
This judgment is independent of declaration order, the presence of an invocation, and the implementation's query order. It requires neither evaluating a function body, default, initializer or getter nor making a
interface I { m(): (x: string) => number; }
class C implements I {
m() {
return f;
function f(x: string) { return x; }
}
}
The declaration of f publishes String. The return of m carries that callable contract even though it never calls f, so this interface implementation is a string removes the incompatibility. Moving f before the return or introducing a
class C {
m() {
return f;
function f() { return s; }
const s = "s" := string;
}
}
new C().m()();
The callable result retains the declared String contract of the s was never initialized.
A
function _k_(_a_: uint32) { return 's'; } publishes string, so const _n_: number = _k_(5) is refused before the program runs rather than at the boundary while it runs.any type, which publishes nothing - the return is unknown, and a signature whose return is unknown is required nothing in that position (It is NOT read by the identity of a signature, by the formation of an overload
Identity must be declared-only or a body edit becomes a declaration edit: two same-named functions with the same parameter types are one signature declared twice, an error, and inferring different return types for them would silently turn them into return-type overloads. [[Untyped]] is likewise a syntactic property — a signature that declares nothing is a
Viability is declared-only for a plainer reason: filtering on an inferred return would replace the diagnostic a program wants with a worse one. With a number and a string, the only signature becomes non-viable and the report is that no signature applies, where the program's actual mistake is that a string is not a number.
A getter in an object literal const. Dependent member reads are reconsidered when inference publishes a result; the order in which a helper's signature becomes available does not change the contract. A setter's parameter independently supplies the write type. Reading these contracts executes no getter. A getter with no declaration-derived anchor, or with an unknown result, does not acquire a contract merely from an untyped returned literal.
Before identity, interning, await inference, member lookup or assignability is computed, omitted Promise type arguments are completed from their declared defaults: Promise.<T> Promise.<T, any>, and a bare Promise Promise.<any, any>. Explicit arguments are retained.
The rules above apply to a function declaration, a function expression, an arrow function, a method of a class or an object literal, and a getter, whose
A constructor the class declares, since a class may declare more than one (any for a parameter that carries no annotation, and it has no return entry. Nothing is inferred there: a
An async function infers the type its result RESOLVES with and publishes Promise.<T, any>. A await would. The reject type is never inferred: anything may throw, and the convention that undefined there means a promise that never rejects is a claim no body supports.
At an explicit then, on every possible selected assimilation then permits ordinary value fulfillment and is not itself an error; the called method's normal result is ignored, not used as the awaited result. Intrinsic Promise paths that do not select this method remain unchanged. A yield* does not assimilate its raw
A promise's two parameters are COVARIANT: Promise is declared Promise.<out R, out E>. Neither type is written through a promise — a promise is settled once, by the code that created it, and every position either type is read from is a HANDLER'S PARAMETER, as the table below shows. So the mutable-container hazard that makes Map's parameters invariant does not arise here: a wider reading of R or E can be wrong about a value the promise produces, and cannot admit a write into storage that does not hold it.
Promise.prototype.then, Promise.prototype.catch and Promise.prototype.finally are Promise.<R, E>:
Promise.prototype methods| Method | Signature |
|---|---|
then | <U, V>(onFulfilled?: (value: R) => U, onRejected?: (reason: E) => V): Promise.<A(U) | A(V), any> |
catch | <U>(onRejected?: (reason: E) => U): Promise.<R | A(U), any> |
finally | (onFinally?: () => ~undefined~): Promise.<R, any> |
In this table, A(T)
A handler receives the type the receiver carries: then's first handler takes R and its second takes E, which is what makes those positions the only ones either type is read from. The two handlers return independently, and their results are assimilated. The result therefore resolves with A(U) | A(V) — whichever ran — and catch with R | A(U): the receiver's value where it fulfilled, the assimilated handler result where it rejected. A handler returning Promise.<T, E> contributes the established fulfillment type, not the Promise Object, to the next handler's input. Preserve the ordinary absent-handler forwarding rules, scheduling, boundary checks and reference liveness.
Every result rejects with any, for the reason stated above: a handler is a function, anything may throw, and the reject type is never inferred. A handled rejection therefore does NOT narrow the result — a never there would claim a promise cannot reject when a throwing handler makes it reject — and finally widens for the same reason, its callback being a function like any other. then and catch must answer alike here, since _p_.then(f, g) and _p_.then(f).catch(g) differ only in spelling.
A generator publishes Generator.<Y, R, N> where Y is the join of the types its yield operands contribute, with yield* contributing the yield type of its operand, and R is the join of its return contributions. N is not inferred, being the type of what a caller sends IN: this
A generic function's inference runs over its uninstantiated body, so a
A reference return
A named STATIC function of the standard library may carry a
The following carry one:
| Function | Signature |
|---|---|
Map.groupBy | (items: Iterable.<T>, callback: (value: T, index: uint64) => K): Map.<K, [ ] . <T>> |
Object.groupBy | (items: Iterable.<T>, callback: (value: T, index: uint64) => K): { [key: K]: [ ] . <T> }, where K is a |
Array.from | (items: Iterable.<T>): [ ] . <T>, and with a mapping callback (items, mapFn: (value: T, index: uint64) => U): [ ] . <U> |
Array.of | (...items: [ ] . <T>): [ ] . <T>, where every argument is of one type T |
Iterator.from | (items: Iterable.<T>): Iterator.<T> |
Object.keys | (o: object): [ ] . <string> |
Promise.all | (promises: Iterable.<Promise.<R, E>>): Promise.<[ ] . <R>, E> |
Promise.race | (promises: Iterable.<Promise.<R, E>>): Promise.<R, E> |
Promise.any | (promises: Iterable.<Promise.<R, E>>): Promise.<R, AggregateError> |
Promise.resolve | (value: R): Promise.<R, never>, a resolved promise having nothing to reject with |
Promise.try | (callback: () => R | Promise.<R, E>, ...args): Promise.<R, E> |
Promise.reject | (reason: E): Promise.<never, E> |
Promise.allSettled | (promises: Iterable.<Promise.<R, E>>): Promise.<[ ] . <PromiseSettledResult.<R, E>>, undefined> |
Array.fromAsync | (items: Iterable.<T | Promise.<T, any>>): Promise.<[ ] . <T>, any>, and with a mapping callback answering U |
Object.values | (o: { [key: string]: V }): [ ] . <V> |
Object.entries | (o: { [key: string]: V }): [ ] . <[string, V]> |
Object.fromEntries | (entries: Iterable.<[string, V]>): { [key: string]: V } |
Object.freeze, Object.seal, Object.preventExtensions | (o: T): T` |
Promise.reject resolves with never, there being no value a rejected promise produces, and Promise.allSettled REJECTS with PromiseSettledResult.<R, E> is { status: string, value?: R, reason?: E }, structural in the way
Array.fromAsync AWAITS each element: an element that is a promise contributes what it resolves with, so a collection of Promise.<T, E> yields [ ] . <T> and not an array of promises. Its two forms are distinguished by how many arguments the call passes, as Array.from's are.
Object.values and Object.entries take V from the argument's index signature where it has one, and from the join of its own property types otherwise - an index signature over an object being what that join means. Object.freeze and its siblings answer the object they were given, so a type crossing one is not lost.
Where the argument to a Promise combinator is a TUPLE of promises rather than a collection of one promise type, the result is positional: Promise.all of [Promise.<uint8, Error>, Promise.<string, Error>] resolves to [uint8, string], and race and any, which settle on one of them, resolve to the union of what the positions resolve with. In every case the rejection type is the join of what any position may reject with, since one failing rejects the whole.
The REFLECTION statics this Reflect.isAssignable is Reflect functions ECMAScript already defines are not, their signatures being the language's question rather than this Reflect.typeOf is <T>(value: T): Reflect.TypeObject.
That row was WITHDRAWN once, written as Reflect.Type, because a Reflect.Type as its runtime type — and it does not, correctly. Reflect.Type is the type of a reflection NODE, the record Reflect.getReflection answers with; a Reflect.TypeObject is the name for the second, and naming it is what the earlier row lacked rather than anything a
The failure the withdrawal prevented was a RUN-TIME one and not a refusal: Reflect.typeOf being untyped made its result any, so a signature naming Reflect.Type type-checked at every boundary and threw at each one that ran.
A TAGGED TEMPLATE is a call of its tag, so `` String.rawx `` carries the same type String.raw(_strings_) does. Only a signature whose result depends on nothing the call passes applies there: a tag receives the strings array and the substitutions rather than the arguments a written call would pass, so a signature reading its arguments in order has nothing to read.
A function whose result depends on NOTHING the call passes carries a type all the same: Array.isArray, Object.is and ArrayBuffer.isView are boolean, String.fromCharCode, String.fromCodePoint and Symbol.keyFor are string, Symbol.for is symbol, and Date.now is number. So do the global functions parseInt and parseFloat, at number, and encodeURI, encodeURIComponent, decodeURI and decodeURIComponent, at string. So do the static DATA properties: the Math constants and the Number limits are number.
None of those is %NumericType%-Math.PI is number and not float64 and a program wanting one writes the conversion. The functions that clause OVERLOADS are not listed here at all, isFinite, isNaN and the Number predicates among them: a fixed result would displace an overload whose answers
T is the any in the undetermined position: a result of Map.<any, [ ] . <T>> would state a key type the call does not support. An untyped source therefore yields an untyped result, which
The two differ in their key. Object.groupBy produces PROPERTY KEYS, so K is constrained to the Map.groupBy accepts any key type, keying with Map does. That difference is the reason the two are listed separately rather than as one rule with a parameter.
A static needs a rule that an instance method does not. An instance method is found through the type of its RECEIVER - _a_.map through the array's type, _m_.set through the collection's - and every such member follows from the type arguments the receiver carries. A static has no Map is a Map. So a signature for one has to be attached to the NAME, which is what this clause does and what makes the shadowing condition necessary: a program that writes const Map = _MyMap_ has not called this function.
These are signature listings rather than new features. Both functions already exist and behave exactly as they did; what the signatures add is that a fully
The callback's second parameter is the standardlibrary.md writes uint32 for it, which predates the dfn: uint64 here and the uint32 is the side that should move.
Promise.withResolvers is a case of its own, and both halves are now stated. It takes NO ARGUMENTS, so where its type arguments are not written there is nothing for R and E to be inferred FROM - a signature would have to read them from the annotation its result is assigned to, which is the opposite direction from every entry above, and the call has no { promise: Promise.<R, E>, resolve: (value: R) => void, reject: (reason: E) => void }.
A BUILTIN CONSTRUCTOR carries an instance type on the same terms. new Error(_x_) is an Error whatever it is passed, as are the other error new Date() a Date and new RegExp(_p_) a RegExp; new WeakRef(_k_) is a WeakRef.<T> for k's own type T, the one whose argument decides the answer. A program that SHADOWS the name, or that declares a class extending one, reaches its own declaration and not these.
new Array(_n_), new Map() and the other collection
That distinction is not special to this function: a call whose type arguments are written binds them however its signature reads its arguments, and a signature that can be satisfied no other way is simply the one where the difference shows.
Array.fromAsync is listed by the
What an inferred return type costs, stated once. A function's callers depend on what its body returns, so a body edit can change a type that other code was checked against — the objection that leads languages with a compiled API surface to require the annotation. Two things answer it here. The change is not silent: the : any and is back to what it was.
The other cost is a real one and is not answered: reading a body to type its returns is work an engine would rather defer, and a function whose return type is inferred cannot have its body skipped at first parse the way an unannotated one can. Participation bounds it — an untyped source text pays nothing at all, and a
Where a function literal has no declared return type, its result type is INFERRED from its body, and the inference is the same whichever way the body is written. For a concise arrow body the result type is the
return within the literal contributes the return within a NESTED function literal contributes nothing, belonging to that literal.The join of a never. Where every return carries a value and none needs to, the join is void. An implementation is permitted to be less precise about whether a body can complete - to treat a body as able to complete where it cannot - because contributing
The two spellings of a function that answers nothing therefore agree with the annotation its author would have written: void is what one writes for a function whose result must not be depended on, and it is what this operation infers where every undefined remains the uint32 | undefined is a type an annotation can write, while a bare
This clause governs a function LITERAL. A function whose name a program can call, and whose inferred type therefore becomes a fact other code is checked against, is governed by
The rule exists so that the two spellings of one function agree. Without it a.map(_x_ => _x_) carries its a.map(_x_ => { return _x_; }) does not, and a program is punished for the braces. Inference of a result type is not inference of a PARAMETER type: a parameter takes its type from the position the literal is written in (
A Number value's exact flow domain and its eligibility for contextual literal adaptation are distinct static facts. Retaining a singleton domain after a store or a guard does not make a read of that value a numeric literal. This distinction is expression provenance; it is not part of
For contextual numeric adaptation, apply these rules before using an exact Number value to establish that a literal fits a numeric
number annotation has the Number domain, even if its current flow domain contains only one value. An immutable alias of that read preserves this restriction. A guard, assignment, join, property read, or call result cannot confer literal-adaptation eligibility merely by establishing an exact value.Thus let n: number = 5; let x: uint8 = n; is a const n = 5; let x: uint8 = n; retains the specified constant propagation. let n: number = 5; let x: uint8 = (n := uint8); uses an explicit conversion. let n: number = 5; if (n === 5) {} still has an impossible deciding alternative; String(n === 5) is an ordinary value query. This clause changes neither dynamic boundary checking nor explicit conversion, metadata admission, literal-derived iteration contributions, or reference liveness.
Decoration of a function literal preserves the established construction context of its operand, including through parentheses. The context applies while checking and creating the literal, not to its decorator expressions or their written arguments. Effective adopted binders and
For a call or construction with a selected parameter declaration, determine each fresh source argument's construction context from its destination where the declaration and source argument structure establish that correspondence without inspecting values. Apply ordinary explicit-name pinning, including named rest segments and their required suffixes. A fixed by-value parameter supplies its own type. A by-value rest supplies the
A fresh object literal used as a call or construction spread receives the established named-parameter contexts for its String-keyed data initializers. A fixed by-value parameter supplies its type; a homogeneous Array rest supplies its
For a fresh Array literal used as a call or construction spread, establish direct element construction contexts from the potential sequential traversal of its source indices and the selected signature. Where fresh
A for-of binding, including the binding's type within the loop body, consumes the declared
Adoption preserves the effective undefined; an effective default supplies the omitted value and excludes omission alone from that union. A written annotation takes precedence. A rest binding has the type of the collected array or tuple, preserving known positional types and extent after mapping the adopted parameter sequence to the implementation's formals. This collection is argument binding, not a proof about a replaceable
A fresh function expression, arrow or object method without a written type-
Use the selected effective own type-parameter declarations when resolving written parameter, result and body annotations and when classifying their dependencies for type checking. A reference to an adopted own parameter is not a closed type expression merely because the literal has no written type-
An adopted own parameter occupies the literal's own generic declaration scope for lexical resolution. It shadows a same-named outer alias or other outer declaration; a declaration in a nearer lexical scope retains its ordinary precedence over it. Resolve a type reference to that effective declaration before consulting active generic arguments, including for an applied or qualified type name. Apply the same precedence to written generic declarations. Retain the selected scope correspondence with the created callable's context, including through nested
For an object literal in a union context, a uniquely compatible complete alternative supplies the contextual types of its members. Consider the literal's surviving property descriptors, required members and effective callable contracts together; do not combine a discriminant from one alternative with a callable context from another. A written method default retains its ordinary callable assignability, even when the destination requires that argument. The last definition of a property determines its surviving value; earlier initializers retain their ordinary evaluation and independent checking obligations. The union-wide excess-property rule of
Unresolved signature annotations do not establish compatibility. Restore losing trial contexts, adopted declaration scopes, deferred obligations and source publications, including after a nested trial succeeds or an
Where complete compatible object alternatives agree except at numeric member positions, and one complete alternative is preferred at every differing position by the existing numeric literal ordering, use that alternative. Apply
An argument expression receives the context of its established destination parameter in the
Carry a selected complete object alternative through its immediate receiving admission as specified in
Apply an enclosing nominal context's arguments before supplying its effective generic signature. Resolve written application arguments in the application environment; evaluate an adopted parameter's required bound or selected default in the supplying signature's original declaration environment, with preceding own bindings available in declaration order. Retain that environment and its
A callable that adopts a generic signature retains the effective input Parameter
An adopted parameter type is a binding contract of the created function, not only a fact for checking its body. At parameter initialization, after applying that parameter's default when needed and before a later initializer or the body can read the binding, perform the required admission at its effective type. Retain the admitted binding's contract for later assignments. Apply the collected contract to an ordinary rest binding and the existing extraction rules to destructuring. A call through an erased alias, a retained callback or a
When a generic call's applicable signature gives a fresh callback literal a single non-generic signature whose input positions are established independently of its still-unbound output parameter, apply those input contracts before inferring the callback's result. Infer the result in the literal's source and
The contextual type of an expression is the type its position requires, or
Parentheses preserve the
| Position | |
|---|---|
| The | The annotated type. |
| An argument of a call whose callee has a single applicable signature | The declared type of the corresponding parameter. |
| An argument whose corresponding parameter's declared type is a generic parameter | That parameter's evaluated constraint ( |
| An argument of a construction, or of a call of a generic function, whose corresponding parameter's declared type is a generic parameter that the construction's or call's own | That binding. So the 1 in const b: Box.<uint8> = new Box(1) is read at uint8, as it is in new Box.<uint8>(1). |
| A | The |
| A property of an object literal at a position of known object type | The type the object type declares for that property, where it declares one; |
The operand of a return in a function with a return annotation | The annotated return type. |
this within the body of a non-arrow function literal whose | The [[ThisType]] of that type's applicable signature, where it is not |
| The right operand of an assignment to a target of a known type | The type of the target. |
| An operand of a binary operator whose other operand has a known | The type of the other operand. |
| An arithmetic expression every operand of which is | The 9007199254740992 + 9007199254740993 at a uint64 is 18014398509481985, and 200 + 100 at a uint8 is a 300 - 100 at a uint8 is 200. A const whose initializer is such an expression behaves as if inlined, and a reference to it is such an expression. |
| An element of an array or tuple literal in a position of a known array or tuple type | The |
| The operand of an explicit conversion to T | |
The for-of head whose binding has a | For a range literal, the range type whose |
| Any other position |
A contextual signature retains the identities of the declarations that supply its bound parameters, their applicable
A successful type computation can retain a type-result context: the resolved
Retain the selected result's context through value-preserving expression selection and successful returns and calls. This includes parentheses, the selected conditional or short-circuit operand, the final comma operand, and forwarding through ordinary helpers, specializations, bound calls and reflective call operations. A
Storing the language value in an ordinary variable,
For a class-
An initialized type-alias binding retains the resolved declaration context it supplies separately from any interned
A namespace-qualified reference to an exported type alias supplies the same declaration context as a reference established through a named import. Where checking establishes a module namespace
These contextual-signature rules also apply when a source text is parsed separately from the declaration it references, including the Script of a direct eval. Resolve external declaration evidence in that source text's applicable lexical environment, following actual import bindings. An initialized immutable, non-reference binding containing an actual module namespace may establish a namespace eval retains its global environment. Preparation and subsequent checking of an eval Script use its own declaration environment over that applicable outer environment, so aliases successfully prepared in the eval Script can supply their context. No context is obtained by searching another activation or by matching public
Where required type-declaration preparation makes a previously unavailable contextual signature available, apply its effective binders before classifying and evaluating the dependent literal's annotation obligations. An annotation referring to an adopted binder is not a closed obligation merely because an earlier traversal lacked that contextual signature. Independently closed obligations still require checking, and an invalid body determined from the completed signature is rejected before the source text's body begins. This requirement does not execute an ordinary value initializer to obtain a signature, change dependency availability or failure precedence, or grant reference permissions or liveness.
The for-of row reaches only a literal written as the for (const b: uint8 of 0..<256) read its range as let r: ClosedOpenRange.<uint8> = 0..<256 reads it, and the range is then judged by the element rule of
An argument of an overloaded call has no single
A uint64 beyond the range a Number represents exactly arrives inexact today when it passes through an unannotated helper, and exactly when it does not.
A function-shaped expression — a function or arrow expression, or a method of an object literal — in a position whose this type, and the type-{ map(x) { return x; } } written against interface J { map<T: type>(x: T): T; } creates a generic function value, and o.map.<uint8>(x) through the J-
A parameter default is checked against the parameter type the expression adopts, unless a written annotation supplies that type instead. The same effective type governs the binding inside the body, including a destructuring binding. The default remains an ordinary per-call initializer; checking neither evaluates it nor moves its effects to function creation. A known incompatible default is a
A numeric literal has no type of its own. In a position whose
A never, a function type, and a union with no common zero. Nor does it reach a PARAMETERIZED numeric, which is unreachable by a bare literal and reachable through an implicit cast the program declares (uint32<{ bounds: 5..=5 }> refuses 5 until such an operator is written, and admits it once one is. The claim is about the LITERAL GRAMMAR and not about default values: a type with no default is nameable and still fillable by an initializer, which is what never, a function type, and a union with no common zero. Nor does it reach a PARAMETERIZED numeric, which is unreachable by a bare literal and reachable through an implicit cast the program declares (uint32<{ bounds: 5..=5 }> refuses 5 until such an operator is written, and admits it once one is. uint8 3 as a type" — while uint8 3, so before this rule a cast was the only way in.
The conversion is the BASE's, so a literal the base cannot represent is refused by the base's own range rule rather than by anything stated here, and the two diagnostics stay distinct: a literal of the wrong value is not assignable to the literal type, while one out of range is not in the range of the base.
The reach is ONE layer and does not re-base a written literal. number however the literal will be used" — so uint8 & 5 and { a: uint32 } & { a: 5 } remain empty, and a member of a sized numeric is narrowed by a literal type constructed over that numeric rather than by a written one. Reading one member of an intersection to re-base a literal in another would make those pairs inhabited and is deliberately not done: & is order-insignificant and
The abstract operation LiteralValueInType takes arguments literal (a
bigint, then"The 9007199254740993 has become 9007199254740992, and an implementation reading its own scanned value would answer that, which is a value the source did not write. Retaining the literal's source text, or its exact value, is what lets this operation be implemented as specified. The rounding step for a binary floating-point t is where rounding is permitted, and it is written as a step precisely because it must not have happened earlier.
bigint takes a plain let x: bigint = 65 needs no n suffix. The suffix exists because BigInt was added before there was a type in the position to take the literal's type FROM; where a type is written it carries no information the annotation does not, and requiring it there would make bigint the one numeric type a literal could not reach. It remains the only spelling in an UNTYPED position, where there is nothing to take a type from, and it remains necessary for a value beyond 253 in any implementation that reads a literal through a Number before its type is known.
The 0.1 0.1 in a decimal64 position is the decimal one tenth and in a rational64 position is 1/10, while in a float32 position it rounds. This falls out of the specification already defining a literal's
A decimal type reads its cohort member from the source text rather than from the 1.0 and 1.00 have the same
Representability is exact for an 0.1 in a float32 position and no useful program could be written.
The
The error is an Early Error (f(256) where f takes a uint8 does not compile, and neither does let a: uint8 = 200; a + 300;, where the literal takes the type of the other operand. This is the difference between a literal and a cast: a cast is an instruction to discard information and truncates, while a literal that does not fit its type is a mistake.
One case in the array[0] = 300 where array is a [].<uint8>, is specified to wrap to 44 rather than to be rejected, on the grounds that it is unchanged from what a Uint8Array does today. But [].<uint8> is new syntax, so no existing program writes that store, and the compatibility argument does not apply to it; meanwhile the literal rule says the same literal in a uint8 binding does not compile. The two cannot both be right for a literal element, and
An established
When establishing such an immutable value's
This distinction also applies when an annotation or another
An explicit conversion is written as a call on the type, uint8(v), or with the conversion operator after the value, v := uint8 (rational64(0.1) is 1/10, as 0.1 := rational64 is, while rational64(float64(0.1)) converts the double nearest one tenth; and uint8(300) is 44, because the conversion then wraps as it always does. A conversion is an instruction to discard information: it truncates, wraps, or rounds as the target requires, and does not fail merely because information is lost.
A value of a decimal, rational, float128 or complex type has no Number value to give implicitly. Math function that is not overloaded for its type, in arithmetic or a comparison with a Number, or anywhere else a Number is taken. The conversion to the _x_ := number or number(_x_); float64(_x_) is the conversion to float64, a distinct type holding the same value. JavaScript's Number(_x_) performs the same conversion for a value of a numeric type, and remains decimal, a rational or a float128 converts, rounding to the nearest Number; a complex has no such row and refuses in every spelling, and .real gives its real part. An implicit Number would round through the binary double a decimal or rational exists to avoid, drop the bits a float128 holds past a double's, or discard the imaginary part of a complex, and a value that looks right and is not is worse than an error.
The abstract operation ConvertValue takes arguments value (an
string or boolean, return ? The object step converts the declared members IN PLACE and returns the same Object. A boundary is where a value acquires a type it did not have (let _o_: { x: uint8 } = { x: 5 }`` refused, the literal's 5 being a Number that nothing has made a uint8. Building a new object from the declared members instead would DISCARD the properties the type does not declare, which the boundary admits and a program keeps, and would break the identity a caller and a callee share: _f_(_o_) === _o_ holds, and is relied on.
Converting in place is therefore the rule, and its cost is the step that REFUSES when the member cannot be written. A frozen object, an accessor with no setter, and a property whose descriptor says otherwise are all values that cannot acquire the type, and a boundary that cannot give a value its type does not have it. The refusal is stated as a step rather than left to the write, so that it reports which member and which type rather than surfacing as an assignment fault; Set is called with
A COMPOSITE is frozen from its creation (Composite.<{ n: number }> does not cross to { n: uint32 }. A composite whose members already hold the target types crosses, since no write is attempted. This is a consequence of freezing meeting in-place conversion, and it is written here rather than discovered: the alternative would be to copy for a frozen source alone, which trades the identity above for it.
The final step is reached only where the operand types are related by no rule above. Where those types are known statically the conversion is rejected as a
The abstract operation ConvertNumericParameterization takes arguments value (an
Determine the ultimate base through the
Retention and removal preserve the numeric representation, including a decimal's width, coefficient, exponent and special value, a binary float's zero sign and special value, exact
Both explicit spellings use this operation. Literal operands keep their exact contextual evaluation.
The abstract operation NumericConvert takes arguments value (a value of a numeric type), from (a numeric
The abstract operation PrimitiveConvert takes arguments value (an string or for boolean) and returns either a
The boolean target is reachable only from v := boolean asks for a truth value and ToBoolean is total, so it cannot fail. string, so let b: boolean = v for a v of the
The signature and the assertion named string alone while
The rule for the string type divides its sources by whether the source has a canonical text or only a diagnostic one, and not by whether it is a primitive. A number, a BigInt, and a Boolean each have exactly one text that 5 is '5' and reads back as 5. That is worth having without ceremony, and it is why this rule is not the mirror image of the numeric one. The asymmetry is principled rather than convenient:
'undefined' reaching an interface that was meant to show a name, a '[object Object]' where a String(_v_) and has then said so. A Symbol is refused for the reason it always was, which is that
The boolean type is not among the targets above: at a boolean boundary a value that Boolean(_v_). An earlier form of this specification converted here, on the ground that if (_v_) is the language's own idiom for asking. What that ground overlooks is that a boundary is a STORE and not a question: if (_v_) interrogates a value in place and moves on, while a boundary mints a durable answer that no longer carries what it was made from.
The rule against it is this specification's own, stated for the numeric targets in 'false' becoming
What is lost is not the value's bit but everything else about it: the difference between a string that said 'false' and one that said 'yes', and the difference between a boolean type is not an exception to them.
The table above does not reach an string target is the one that could, and at those positions it does not.
| From | To | Result |
|---|---|---|
| The | ||
| binary float | The source rounded to the nearest value of the target, with ties to even. | |
| binary float | If the source is NaN or an infinity, 0. Otherwise the source truncated toward zero, | |
| binary float | binary float | The source rounded to the nearest value of the target, with ties to even. A |
| the | float64, and the reverse | The source unchanged. The two types have the same values ( |
| rational | binary float or the | The exact quotient rounded to the nearest value of the target, with ties to even. |
| rational | The source's value over 1. A | |
| rational | rational of width M | The source's exact value. A |
bigint | rational | The source's value over 1. A |
| binary float or the | rational | The source's exact value, which is a dyadic rational, in lowest terms. A |
| any numeric type | decimal | The source's exact value if it is representable, otherwise rounded to the target's precision. Of the cohort members of that value, the one with the fewest significant digits where the source holds the value exactly, and the one at the target's full precision where it does not: a conversion that lost nothing |
| decimal | binary float or the | The source rounded to the nearest value of the target, with ties to even, which reintroduces the binary error a decimal exists to avoid. A |
| decimal | The source truncated toward zero, then | |
| decimal | rational | The source's exact value. A terminating decimal is exactly a rational with a power-of-ten denominator, in lowest terms, so the conversion loses nothing. |
| decimal | bigint | The source truncated toward zero, as the binary float and rational rows to bigint give. bigint has no width, so there is nothing to wrap to. |
bigint | The bigint has no width, so there is nothing to wrap to. | |
| binary float or the | bigint | The source truncated toward zero, as the binary float to bigint has no width. A |
| rational | bigint | The exact quotient truncated toward zero. |
| rational | The exact quotient truncated toward zero, then | |
bigint | As the | |
bigint | binary float or the | The source rounded to the nearest value of the target, with ties to even. A source outside the target's range becomes an infinity of the same sign. |
| complex or vector | any other family, and the reverse | Not defined by this clause. The complex numbers and SIMD extensions define the conversions of their types. |
A literal is judged against its 5 fits uint8 and 300 does not, and a BigInt literal fits the same way, 1n into int64, -128n into int8, and 5n into a binary float it does not overflow, while 300n into int8 keeps its own type and is refused by the boundary as 300 is. A constant expression of literals is judged as its value. An explicit conversion is not a boundary, so uint8(300n) wraps as uint8(300) does.
The conversions between the decimal types and the binary types are the hard ones, and the difficulty is not arithmetic but which cohort member results. A float64 converted to a decimal128 has an exact decimal expansion, since every binary float is a terminating decimal, but that expansion for 0.1 is a 55-digit value that no decimal128 can hold, so the result must be rounded and its exponent chosen. Choosing the exponent that preserves the most significance, choosing the shortest expansion that round-trips, and choosing a fixed quantum are all defensible and produce different values.
The rule the table gives is none of those three but a pair keyed on whether the conversion LOST anything. Where the source holds the value exactly, the reduced member results: decimal128(0.5) is 0.5 and not 0.5 padded to thirty-four digits, because nothing about the source decimal128(0.1) is the binary approximation to thirty-four digits, because there every digit is one the double actually holds. A quantum is a claim about significance, and these are the two
What this rule deliberately does NOT do is make decimal128(0.1) equal decimal128.parse('0.1'). The first carries the binary approximation the double already was and the second is exactly one tenth, and collapsing them would launder an approximation into an exact-looking decimal and hide the reason these types exist. The exact spellings are the literal and the string: let tenth: decimal128 = 0.1, 0.1 := decimal128, and decimal128.parse('0.1') each read the digits as written, while a conversion carries whatever its source already holds. An exact decimal comes from a literal or a string, never from a round trip through binary.
The outgoing directions need no such choice, a binary type having no cohorts: each is the ordinary loss of its target and is given in the table above.
Uint8Array, and the float to
bigint takes that rule's truncation but not its zero. A fixed-width conversion is total, and gives a non-bigint has no width to reduce rational, which has no NaN or infinity either and refuses what it cannot represent rather than rounding. Refusing it is not failing merely because information is lost, since no BigInt is unaffected, and continues to refuse a fraction as well.
A type may declare a conversion to or from another type, and a declared conversion applies at an assignment, an argument, and a return, because the type that declares it has opted in. Three forms declare one.
| Form | Declared on | Converts |
|---|---|---|
| A | The target class T | S to T. A converting let t: MyType = 1; is legal when MyType's float32. Where a class declares several one-parameter |
operator T(), a parameterless member | The source class S | S to T, by converting the receiver. |
operator T(value: S), a member taking one parameter | The target T | S to T, running with no receiver. This is the form a type declares when its Temporal.Instant's is by |
The one-parameter inbound form is called with this having the undefined type. Lexical arrows retain that receiver context. A nested ordinary function, method or class uses its own applicable receiver rule, and leaving it restores the enclosing context. This inbound rule applies independently of a static modifier, class heritage, or generic parameters; declaration names and
The target T of a source or inbound conversion operator is also its result contract. Resolve that target in the executing declaration's lexical and private environments,
After the body completes its return expression evaluation and applicable finally evaluation, and before leaving the declaration's
The nominal membership precondition prevents a nonmember returned by the selected operation from initiating another
An explicit receiving conversion does not relax this declared return boundary: a dynamic out-of-range result for uint8 throws a :=. The body may itself explicitly convert the result. A matching source conversion for a numeric metadata target is consulted for that complete target before an intrinsic explicit conversion decomposes it into base conversion and metadata construction.
Propagate a selected body's finally effects and independently completed nested obligations. A failed receiving assignment does not replace its destination. Result admission neither grants reference permissions nor extends reference lifetime, and existing value/reference distinctions remain applicable.
Publishing a generic class declaration does not apply its own parameters or evaluate their defaults to register a conversion. Retain its conversion declarations until an application supplies that class's own arguments. At
A converting
Materializing the target for conversion uses the actual declaration's retained lexical environment, enclosing type and value parameter bindings, private environment,
When both a converting
For the bounded ordinary identity-class targets and simple parameter signatures
A class
At a non-rest receiving boundary, the same bounded selection proof also applies when the resolved, non-generic source class target is a
Within the supported inbound form, discard an applicable declaration whose parameter type has another applicable declaration's parameter as a proper subtype. More than one remaining maximal declaration is ambiguous and throws a
Retain the selected callable and receiver or argument. Execute it once, keeping its actual parameter boundary checked even when the enclosing conversion is explicit. Propagate a selected
A conversion to a tuple type is what makes a class destructurable: a class declaring operator [number, number, string]() may be destructured into three bindings. The operator overloading extension specifies the declaration syntax; this clause specifies only when a declared conversion is consulted.
Array destructuring is defined over the ITERATION protocol rather than over a const [_x_, _y_, _z_] = _a_ performs
For this fallback, obtain the applicable
Iteration is attempted FIRST, so a class that both declares [Symbol.iterator] and a tuple conversion is destructured by its _a_ spreadable, nor usable in a for-of head, both of which continue to require an
An earlier draft of the note above attributed this to the destructuring position carrying a tuple const [_x_]: [number] = _a_ being a Syntax Error - and reading the rule that way leads an implementation to look for a
A literal argument may satisfy more than one signature of an overloaded function, since it has no type of its own and several parameter types could represent it. The signatures are ranked by the parameter type in the order of
| Rank | Types, in order |
|---|---|
| 1 | float64 |
| 2 | float128, float32, float16 |
| 3 | decimal128, decimal64, decimal32 |
| 4 | uint128, uint64, uint32, uint16, uint8 |
| 5 | int128, int64, int32, int16, int8 |
The ranking is the only place the order of the numeric types matters, and it matters only for a literal. Given f(a: float32) and f(a: uint32), the call f(1) selects the float32 signature by the ranking, while f(uint32(1)) selects the uint32 one because its argument is
The ranking as the number, and it should. number is the type a numeric literal takes with no float64 first appeals to. Given f(a: number) and f(a: float32), the call f(1) has no answer above. The natural repair is to rank number first, ahead of float64, on the same reasoning that ranks float64 ahead of the rest. It is recorded here rather than
The ranking also omits the rational types and the parameterized widths int.<N> and uint.<N> for an N that is not a named shorthand, both of which can represent a literal.
It is a Symbol.toPrimitive contract for which every possible selected
When the effective exotic hook is proved nullish, the negative primitive-conversion judgment may follow toString, then valueOf for valueOf, then toString for
A successful primitive-conversion contract also contributes a conservative result judgment to the consuming built-in operation, using the actual hint and possible applicable overloads. It is a
This judgment applies to computed
For a participating with member must likewise be an Object or
The
uint8 would produce a Number, which is a conversion between two value types that the rule forbids, so an operator specified in terms of +a on a uint8 is a uint8 and never a Number. any type, where the operand's type is not known statically and the existing dynamic semantics apply.
A type violation is reported where it is found: at compile time where the types are known, and at run time otherwise. This clause specifies the second half. It defines the runtime type of a value, the test that decides whether a value belongs to a type, the check that enforces a type at a boundary, the boundaries at which that check is inserted, and the circumstances under which it is not needed at all.
The checks exist because of the any type. A value reaches a any, which is the type of every binding an untyped program declares. Everything else is settled statically. This is what makes the system gradual rather than merely optional: the guarantee a
The abstract operation RuntimeTypeOf takes argument value (an
type.any type where it has none; and whose [[Extent]] is value's extent where it carries one and any type where it declares none, and whose return is the type its body publishes (any type.This step once read "and has declared signatures", so a callable that declared none reached the last step and reported an any parameters would be inference the program did not ask for. That reasoning does not hold: the inference has ALREADY been performed, and a diagnostic prints its result, so the step declined to report a type the implementation held rather than declining to invent one. What it cost is the property this step exists to establish, in both directions - an unannotated f was a () => void where an F was declared and a {} to RuntimeTypeOf, and type {} to RuntimeTypeOf and not a {} where a {} was declared. Every unannotated callable in a program also reported the SAME type, so a Map keyed on Reflect.typeOf collapsed functions, arrows, methods, built-ins and class
The callable description exposes each signature's effective result: its declared result when present, otherwise its
A uint8(_v_), and typeof reports keyof Reflect.typeOf(_C_) reads; a type, a value reporting its [[RuntimeType]], an instance reporting its class, an Array reporting an
A class undefined its body evaluates to. this, so a construction yields the class and the return is derived rather than guessed. Without it two classes with the same new and is the lesser of the two errors.
The step is placed where it is for the reason the Array step above it exists: an operation that RANKS types rather than walking values sees only what this returns, so a callable reporting an f is F and passing f where an F is declared already answer from the signatures; without this step RuntimeTypeOf answered differently about the same value.
The step holds at every depth, since it is a step of this operation and the { f: (x: uint8) => 1 } is { f: (x: uint8) => … }, an Array of callbacks is an Array of their signatures, and a generic call binds a parameter through a callable property as it binds one through a callback argument. A signature is derived in the function's own scope - the [[Environment]] it closed over, and the type-parameter frame it any in the derived signature, and a signature that cannot be derived at all is the one signature of all-any parameters with [[Untyped]]
Every value has exactly one runtime type, because the values of distinct value types are distinct (uint8 representing 0 and the uint16 representing 0 are different values, so a instanceof uint16 is uint8 binding a even though typeof a reports typeof is unchanged by this Reflect.typeOf.
An Array reports an
The [[RuntimeType]]
The types named undefined and null types of undefined as a type, since the reject type of a undefined by convention when the promise never rejects, and a value that may be absent has a type such as uint32 | undefined.
When contextual checking selects a complete member m of a union u for an object literal, associate that choice with the normal result of this literal's evaluation: its constructed Object v, original context u, and selected member m with the completed declaration identities and
Preserve the association when the same producing evaluation supplies a surrounding expression or completion value, including a normal result carried through
When a source argument supplies a parameter, carry its association with that invocation's argument occurrence through positional or named binding and forwarding to the selected implementation. Match occurrences by the binding correspondence, not merely by equality of values. An evaluated parameter default supplies its own producing result and context. Preserve the ordinary argument and parameter-initialization order, including later defaults, before the existing parameter admission. A committed assignment to the parameter ends the earlier initialization
When the actual named-property branch of call or construction spreading reads a fresh source result's own enumerable String-keyed data
When actual call or construction spreading resumes the built-in Array value
When ordinary by-value rest binding collects an argument occurrence into a new Array, preserve that occurrence's eligible construction association through the newly created
For simple assignment and the evaluated right operand of logical assignment, carry that operand's producing association through value extraction to the existing admission of the actual destination. Obtain the construction context from the destination's established declaration, including an owned Array or tuple position and storage reached through a reference or Array span. An assignment expression used as a value still gives its right operand its own destination context before judging that result; the surrounding context does not replace the destination's write obligations. Preserve destination evaluation, right-operand evaluation and admission order. A skipped logical assignment evaluates no right operand and supplies no association from it. Do not read a destination, invoke an accessor or perform an additional property-key conversion to discover a context. Internal environment, reference and data-storage forwarding preserves the operation's eligible association; a super property store uses the actual receiver. Preserve temporal-dead-zone, write-permission, bounds, publication, reference-liveness and failure-precedence rules. Proxy operations retain their existing trap lookup, incoming admission, invocation and invariant-check order; supply the association to their existing checks, not to independently invoked operations in a trap or accessor.
For a declared class
A choice is eligible only at the immediate receiving boundary for that source result, for that constructed Object and the same union context. Carry eligible choices as operation-local admission inputs through recursive structural property and sequence checks and conversions. Do not supply them to an independently invoked operation merely because it runs during admission, including a call made by a getter. Ordinary reads of stored values, independent membership or reference-viability queries, and unrelated later boundaries receive no pending choice. Completing the receiving boundary ends the pending association; the separate rules for retained successful admission and result dependencies still apply.
At the original boundary, require the eligible selected member instead of independently selecting an overlapping member. Propagate failure at that member without fallback to another member of the union. Retain successful observations for required
A structural admission retaining an observation of a receiver's own ordinary
Validate the affected observation after any potentially effectful materialization and before writing based on it. A successfully completed write performed by this admission advances its expected value to the
For an admitted structural property whose observed value was supplied by an inherited ordinary
At the same validation points as for retained own-data observations, require the same supplying Object and
An ordinary materialization write owned by this admission may create a shadowing own
After a successful structural admission publishes a property contract using ordinary inherited-data evidence, retain the receiving Object,
Before an ordinary data definition, deletion or prototype change publishes a state affecting such a dependency, determine the prospective ordinary data lookup for every affected receiving Object and key. Inspect internal descriptors and prototype links without invoking a getter, Proxy trap or other user code. The lookup must still reach ordinary data, and its value must preserve each affected contract. A missing property, accessor or unproved nonordinary lookup does not satisfy this obligation. A supplier's outgoing prototype and unrelated
Apply the mutation's existing destination admission and conversions, but do not select a dependent receiver's converter or perform additional observable membership,
For a definition, preserve descriptor conversion, existing destination admission and ordinary descriptor-compatibility refusals before this additional publication check. A conflicting inherited-storage obligation throws a
For
After successful mutation, replace affected dependencies with the new ordinary paths and accepted values. A former supplier no longer constrains a receiver that no longer consults it. When the receiving Object gains
These requirements also apply to internal ordinary data publication for Arrays. Preflight every definition in an atomic initialization group before publishing any member of that group. Array shortening retains its existing
When a receiving check for an ordinary property store succeeds, retain its receiving Object,
Do not move the first receiving check to a later definition. Preserve its existing position relative to readonly checks, ordinary writable/extensibility refusals and setter dispatch. Continue every required descriptor, backing, extent, tuple-position, inherited-storage and reference-liveness check at its existing phase. User code during the receiving check may change or freeze the destination; those later checks observe the resulting state. On refusal or
Successful evidence is valid only through a continuation with no intervening operation that can execute user code or perform an unproved backing-specific observation. Do not forward it through a setter call, Proxy operation, unknown internal-method override or independent callback invocation. Trap lookup itself may execute user code. A new receiving check after such a barrier starts without the earlier evidence. A distinct required admission that may execute user code does not preserve a cache of earlier successful checks. Evidence is internal to this continuation and is not attached to a user value, descriptor, receiver or ambient
The same rule applies to an owned Array or fixed-tuple store's ordinary element admission and final definition. It neither bypasses scalar-rest planning, atomic initialization, surviving-position requirements nor proves the correctness of distinct backing-specific operations. The separate Proxy incoming, callback and post-callback checks retain their existing ordering; an ordinary store independently invoked by a trap has its own continuation. This rule does not supply a successful result for failed membership or for an unperformed conversion attempt.
A receiving operation retains the values obtained by structural property Gets during its membership, conversion and admission steps, including an explicit conversion. Identify an observation by the source Object identity and
A failed membership attempt discards its speculative successful-admission and construction-selection evidence under the existing rules. It does not discard the raw values already observed by the receiving operation. Convert those values according to the existing implicit or explicit conversion rules; do not repeat a
Keep
For an observation supplied by ordinary data, retain and validate the same own or inherited data evidence required by structural admission. Before reusing that observation, after member conversion which may execute user code, and at the existing admission validation points, require that evidence still to describe the observed data and supplying
When this operation successfully writes a converted or materialized member to ordinary data, verify the resulting own data value and advance only this operation's observation to it. For previously inherited data, first validate the old supplying
Keep the observation record internal to the receiving operation and pass it only through its internal continuations. A getter, setter, trap or user conversion that independently invokes a receiving operation starts a fresh record. Separate pure membership expressions likewise have no shared receiving record. Failed alternatives retain earlier completed effects and operation-owned writes without promoting raw inputs to successful evidence. A failed outer admission publishes none of its pending contracts; independently completed boundaries retain their effects and contracts. An explicit conversion and a subsequent distinct receiving binding are separate operations. Successful
The abstract operation IsOfType takes arguments value (an
Reading each member once is a discipline rather than an optimization. Where the value is a Proxy or carries accessors, a trap or getter runs at most once per member per check, so it cannot answer one way to the step that admits the value and another to a later one. This is the rule the
It performs the following steps when called:
Deciding a structural type reads the value's properties, so the check calls user code: a property backed by a getter runs it, which may throw, may have side effects, and may return a different value each time it is read. A check is therefore not a passive observation, and this is why the operation returns a completion. A getter that returns a value of the required type once and a different value afterwards defeats the check, which is the same aliasing exposure noted below. Whether a boundary check should read through accessors at all, or inspect only own
The final step is the general case: the value's runtime type is compared against the target with the subtype relation of
The any. An untyped function reaching a (uint8) => uint8 position therefore passes, and if it returns a String the caller receives one where its static type promised a uint8. The admission is the any is judged as written. An established incompatible result is rejected by the ordinary relation even when the callable was obtained from an untyped object property; the check does not call the function to discover a result. A non-arrow function whose signature has no [[ThisType]] and reaches a position whose signature has one is compared as if it had adopted it (this, and an ordinary function stored at it is called with exactly that this, so the boundary reads the adoption the literal rule states for a function written at the position. An arrow, having no this of its own, adopts nothing and is refused there.
This is the higher-order problem every gradual type system meets, and the any where its static type does not, which is a wrapper by another name but pays only where the imprecision is. The choice bears on the f === f across a boundary, and taxes every crossing to police a rare one; blame is machinery without precedent in the language. A program that needs the guarantee declares its functions, and nothing this
The { (a: uint8): 1 } form of the object typing section, so an aliased write through an untyped reference can invalidate a check that has already passed.
At a boundary where a value of the any type reaches a position of a known type, the value is checked. At a numeric target, a Number or a BigInt is converted subject to the representability checks in any; a different numeric target requires an explicit conversion, even when it can represent the value. A type mismatch raises a
A boundary therefore CONVERTS what assignability REFUSES, and the two are not in disagreement: assignability governs a claim made between two types the checker can see, while a boundary governs the admission of a value whose type it cannot. let x: uint32 = someNumber is refused, because a program that has both types in hand and means a conversion writes one; the same Number arriving through an any is converted, because refusing it would leave the gradual frontier impassable for the ordinary case of untyped code calling
The consequence a reader meets first is that ADDING an annotation can turn a program that ran into one that is refused — the same value, the same target, a different verdict, because the checker can now see where it came from. That is the direction gradual typing is supposed to run in: removing annotations preserves behaviour, and adding them may report what was there all along. It is not a defect in either rule, and it is written here because the
The abstract operation RequireType takes arguments value (an
string, return ? The two failures are different and are reported differently. A numeric value at a numeric type has a conversion available and the only question is whether this value survives it, which is a question of range: hence the valueOf, and at a float type, where every value is in range, it could not fail at all, so a missing uint8 at a uint16 is a
This is why a string is not a source here even when it reads as a number. '5' at a uint8 is refused and the program writes uint8.parse('5') or uint8.tryParse('5'), which is the same position
This clause specifies a bounded ordinary prototype
The operation's normal result must be an Object; otherwise throw a
Retain the reached-prototype obligation on the actual received Object. Several acquired exact class obligations are conjunctive. Reacquiring an identical obligation does not broaden it or freeze a particular chain. The obligation is not released when one binding exits or changes, since other aliases may retain its contract. Pure
For an ordinary [[SetPrototypeOf]] operation, retain the existing same-prototype, extensibility and cycle checks and their order. Before publishing a different prototype link, consider every retained acquired obligation whose required prefix currently uses that link.
If every affected obligation qualifies, publish the ordinary link and replace each affected dependency prefix with the new prefix, with no user code between the checks and publication. Obsolete links cease to constrain that instance; shared links retain other instances' dependencies. Changes beyond the required target and unrelated changes retain ordinary behaviour. Dependency bookkeeping must not strongly retain an admitted Object solely because one of its prototypes remains alive, including indirectly through a property of the target prototype. An implementation may retain a dependency until its weak reference to an otherwise unreachable instance is cleared; there is no requirement to discover unreachability before a prototype mutation. Such a retained dependency may still cause the mutation to return
After an ordinary Object crosses an exact C boundary, Reflect.setPrototypeOf cannot detach its required C.prototype prefix. It may insert another ordinary link that still reaches C.prototype; the inserted link then becomes a dependency. C.prototype's own prototype can change if no separately acquired obligation relies on that outgoing link. A forwarding Proxy alias reaches the guarded target operation, while a Proxy trap reporting success without changing that target has not changed its class membership.
Where the target of a boundary is a union, a member supplies the receiving contract. A compatible generic callable may still require uint8 | uint32 and uint32 | uint8 are one type, and a boundary may meet it as a
A union WRITTEN in an annotation has its members canonicalized like any other:
Both follow from the guarantee above and neither is optional. Without the ordering, { x: int32 } | { x: uint8 } and its reverse - one interned type, comparing === - select DIFFERENT members for one value, which is the outcome the fixed order exists to prevent. Without the flattening, (uint8 | int8) | uint16 offers two members where three exist, and a value that would have reached uint8 reaches uint16 instead.
A numeric member is selected by width and signedness rather than by position, so a union of numeric members alone is unaffected by either. The difference shows where a member is an object, an array, or a nested union - none of which that ranking speaks about.
The abstract operation SelectUnionMember takes arguments value (an
bigint has no width limit, so it follows every sized The rungs answer, in order: is the value already of a member, does a member represent it exactly as a number, and does any member admit it at all. The last is where the canonical-text rung of the string rule lands, and putting it last is the substance of this clause: at string | uint32 a Number would otherwise have crossed into string and the boundary would have stored "42", silently textifying a number that a member of the same union represents exactly. That is the failure undefined and an object to prevent, and it should not be reachable through the order two members were written in.
The narrowest representing member is preferred because a union of numeric types is written to say which representations a value may have, and the tightest one that holds the value loses nothing; an Reflect.typeOf reports and which arithmetic the value then
The three ways a numeric value may fail to fit a type are distinguished, and each is treated according to what the program said.
| The program writes | Example | Treatment | Because |
|---|---|---|---|
| A literal in a | let u: uint8 = 300; | An Early Error. The source text does not compile. | A literal that does not fit its type is a mistake, and it is visible in the source text. |
| An explicit conversion | uint8(300) | The value wraps, to 44. | A cast is an instruction to discard information. The program asked. |
A value of the any type at a | let u: uint8 = anyValue; | A | Nothing in the source text says to discard information, and the value is not known until it arrives. |
| A value of another numeric type at a | let u: uint16 = anyValue;, anyValue a uint8 | A | The value has a type, and the boundary checks it rather than coercing it: the checker refuses the same assignment where it can see the types, so the run time does too. |
The boundary converts rather than merely checking, which is what makes untyped data usable: a Number parsed from JSON reaching a uint8 uint8, and a check that demanded it already be a uint8 would reject every value that ever crossed the boundary. Converting only where the target can represent the value exactly, and raising a Math.addChecked draws against the wrapping of +.
The rule above permits a float target to round, since it treats only wrapping, truncation, and overflow to an infinity as unrepresentable, so a Number 0.1 reaching a float32 boundary becomes the nearest float32. The alternative, demanding exactness, would reject almost every float that crossed a boundary and is not usable. This matches the treatment of a literal, which also rounds into a float and errors only outside the range, and it is recorded here because it is a choice rather than a consequence.
An object literal's members are those it writes AND those a spread supplies, wherever the spread operand's type is known. A member a spread supplies is judged as though it had been written in that position: it satisfies what the target requires, it is excess where the target declares no such key, and its type must be the one the target declares.
The literal contributes NOTHING where its keys cannot be known - a spread of an
Where a key is written more than once, by any combination of members and spreads, the LAST one to write it decides its type. That is the evaluation order the language already has, not a rule about types: { ..._t_, a: 1 } is well-_t_'s a is not, and { a: 1, ..._t_ } is not.
A check is inserted where a value whose static type is any reaches a position whose type is not, and nowhere else.
| Position | Check |
|---|---|
| The initializer or assigned value of a binding with a declared type t, INCLUDING a binding that is an element of a destructuring pattern | |
| An argument passed to a parameter of declared type t | |
The operand of a return in a function with a declared return type t, or with a | |
| A value stored to a property or | |
| A value stored to an element of an array of | |
A value read from a position of the any type into an operator whose operands are | |
A value crossing into a Reflect.set and Reflect.defineProperty |
The list is the same list of boundaries at which a user-defined conversion is consulted (where clause that cannot be decided statically is evaluated. A where clause the argument decides, because every parameter it reads is bound to a const with a known value, or a binding narrowed to one), is a
An ARGUMENT reaches such a boundary by one of three paths, and which one decides both the answer and the phase. A fresh object or array literal is taken apart and its members checked against the target, which is what lets { n: 1 } fill a { n: uint8 } parameter. An argument that has a static type — from an annotation, or from a declared return type — is checked by assignability instead, so the same members behind const _o_: { n: number }`` are refused before the text runs, { n: number } not being assignable to { n: uint8 }. An argument of type
The fresh-literal
That refusal is decided AT THE BOUNDARY rather than before the text runs, and it is the one refusal here that is. Nothing in the type system models whether an object is frozen, so an argument whose only defect is an unwritable member is not known to be defective until it arrives. The narrowness of the gap is easy to overstate: f(Object.freeze({ n: 1 })) written inline IS rejected before the text runs, but for an unrelated reason — Object.freeze declares a return type, and an argument with a static type takes the assignability
{ x: uint8 } has a Number where a uint8 is required, and testing would refuse every object literal a program writes; the array element positions have converted since the propagation rule was written, and an object's members are the same boundary one level in. The conversion is applied to the object rather than to a copy of it, because an object may carry properties the type does not declare and keeps them under width subtyping, which a copy assembled from the declared members alone would discard. Converting the members does not decide membership on its own: a type may state more than its members do - a where predicate, an index signature - so the conversion precedes the membership test rather than replacing it.
"A value of the wrong type" is decided by the target, and for the number type it means any value that is not numeric. Reading such a value through string target's rule is written to prevent, and would do it at the type a program is most likely to annotate. The explicit conversion remains available and is what a program writes when it wants that reading: _v_ := number`` is a cast, and a cast is not a boundary.
undefined.
A check is required only where the static types do not already establish the result. It is not inserted, and an implementation need not perform one, in the following circumstances.
any, is not a literal type, is assignable to the target, is not a uint8 by let _x_: uint8 = 5 must perform its conversion and produce a uint8 rather than the Number 5. What licenses eliding a check is that the value is ALREADY of the target type, so the boundary would have returned it unchanged; assignability alone is a weaker statement than that.any type. Every value is a value of it.return of the function satisfies the conditions above and the function cannot complete without one. A return annotation is shared by every return in its function, so the elision is a property of the function rather than of a statement, and a function that can fall off its end returns return, the effective body result type is the ordinary function's result type, an async function's Promise resolution type, or the R of a sync or async generator after shorthand expansion. A bare return contributes void satisfies it, being the annotation for a function that returns no value. "Can complete normally" is decided syntactically, and an implementation that cannot decide must treat the body as able to complete, since reporting a total function is the worse error.[N].<T> is known to be below N, because N is a where clause constrains to be less than N, or the counter of a for over a range all of whose members are less than N. The bound is proven statically and no check is performed.The static type of an expression is stable when the value the expression produces at run time is of that type for the reason the type checker gave. A call-result derivation licenses elision only when evidence establishes both the actual callee implementation and its enforcement of the result contract supplying that derivation. A callable storage annotation, a parameter's callable type, a method signature or an immutable binding does not alone establish that evidence: shallow function admission can accept an implementation whose result is unchecked. An unavailable proof retains the receiving boundary. Parenthesized calls, optional calls on their
Evidence of an isolated unchanged declaration or a fresh function literal with an enforced result can establish a call's implementation. An immutable alias can preserve such evidence from its initializer; it cannot manufacture it from a shallowly admitted callable. A declaration's decorators, including decorators carried by an export, participate in deciding whether that declaration still supplies the implementation. A possibly replacing decorator requires an independent proof of the
A property type is a view of an access, not proof of its current descriptor or implementation. A readonly view alone does not prevent an erased alias, an own-property shadow or a prototype change from supplying a different value or getter. This applies to ordinary members and super properties as well as the range and sequence cases below. Retain required receiving admission unless independent evidence establishes the actual storage or property-read contract; admit the value already read without performing another property access.
A known range property or method-result
An
Determine whether a binding can be replaced by its lexical identity, not by the spelling of its name. Establish the binding environments required by declaration instantiation before resolving reads and writes. An assignment to a distinct shadowing binding does not replace the binding read by a call. Conversely, a
Preserve the distinction between parameter and body environments where parameter expressions require it. A body-local var does not replace the outer binding read by a parameter default. Where the base-language rules make a parameter and a body var share a binding, writes through either name are writes to that binding. Class static blocks have their own variable and lexical environments and their class's static receiver; declarations in one block do not enter another block or the enclosing function.
The same binding-identity judgment applies when a consuming eval, reference aliases and global-object access must retain their existing
A global mutation footprint withdraws a declaration-result assumption at a read that it may affect; this is distinct from erasing a contract independently established for a binding or enclosing function. Track the identity of the affected storage. A write through an alias of the
The footprint includes writes through tracked global-object aliases and their supported binding projections, reflective property writes on those aliases, and exposure of the
For supported stored-value routes, distinguish an aggregate's identity from the values its properties contain. A write to an ordinary container is not a write to the
Supported source callable flows connect arguments with parameter bindings, receivers with applicable this reads, and returned expressions with possible call results. This includes available source getters, methods, concise arrows, callback candidates, bound calls and argument containers used by apply. Such a flow is possible effect evidence; it does not prove that a getter or callable is invoked, completes normally, remains installed, or enforces a result contract. Do not execute the callable or getter to obtain the flow. Exposing a container can expose the global or dynamic-source capabilities reachable through its tracked contents, without identifying the container itself with those contents. Ordinary access, invocation,
A
Unavailable ordinary own-data evidence is not evidence that a value carries no capability. Where an earlier-source or otherwise opaque value requires an accessor, an inherited lookup, an exotic operation, or an unavailable callable effect summary, retain a possible ambient effect at the operation that uses it. An opaque invocation can affect reachable mutable global-object and global lexical bindings without receiving the
Implicit accesses participate at their evaluation positions, including supported destructuring projections, aggregate copies, descriptor conversion and argument-container access. A destructuring initializer is evaluated before the pattern's accesses; a default initializer can observe effects of preceding pattern accesses. Do not assign those accesses an earlier position merely because the pattern text precedes its right-hand initializer. An apply argument container can run user code when its length or elements are obtained, even if the target declares no parameters. These rules add neither a checking-time lookup nor a second runtime access.
Object rest constructs a fresh
Array binding and assignment patterns, array and argument spread, iteration statements and delegated yield use their applicable iteration protocols. Include possible effects of obtaining the next, reading result properties, and closing an unfinished
Apply these footprints at the semantic operation. The right-hand initializer precedes destructuring, an object-rest copy follows preceding pattern defaults, and argument spreading follows
Descriptor conversion and the invocation of an installed accessor have distinct effect sites. Include possible presence checks and reads of enumerable, configurable, value, writable, get and set when converting a descriptor. An ordinary own data
Installing an accessor does not invoke the function stored as its getter or setter. Retain its possible value and effect flow for applicable later property operations, with the call or apply routes must not silently discard the same descriptor or target flow. Object-returning definition operations preserve the target alias; a Boolean-returning reflective operation does not return that alias.
Copies and assignment operations use the applicable property protocol. Retain known keys and exclusions when relating a source read to a target write; do not invoke a target setter solely because a different known key is copied. Primitive source values can still supply copied properties: string copies have their applicable UTF-16 index keys, whose target writes can invoke setters. A known non-enumerable accessor does not contribute a rest read unless an affecting change can make it enumerable. Possible descriptor changes and prototype dependencies participate in these proofs; a snapshot alone does not establish permanent stability. These summaries change no reference permission, liveness rule or nominal prototype obligation and introduce no speculative accessor invocation, duplicate operation or replay after
Ordinary property-key coercion has its own effect footprint. Evaluating the key expression and converting its resulting value are distinct operations. Computed references, computed in operator, and applicable reflective operations include possible object-conversion effects at their consuming operations. A primitive-key proof excludes those conversion hooks only; it establishes neither the absence of subsequent property or index-operator effects nor a callable implementation identity. An unknown expression or incomplete flow summary is not a primitive proof, and a primitive alternative does not cancel an unknown alternative.
Preserve the applicable evaluation positions. A computed reference evaluates its base and key expression before an assignment right-hand side, but a simple store converts its final reference key after that right-hand side. A compound or logical assignment converts the key for its initial read before evaluating its right-hand side. A nested member read used to obtain a destination base retains its earlier conversion. A computed property definition converts its name before its value. A destructuring initializer precedes its pattern; a computed pattern key precedes its own default. Reflective calls obtain the callee and evaluate arguments before the intrinsic's key conversion. Computed method and
Absence of a candidate in a
A positive intrinsic summary can retain evidence where the operation cannot enter user code. For example, calling the intrinsic String or Symbol function with no argument or a primitive literal as its first argument does not invoke an object coercion hook. Calling it with an opaque object does not have that guarantee. Establish the intrinsic identity and relevant operand facts; an operation's name or native implementation alone supplies no such proof. Effects of evaluating the callee, receiver and arguments still participate. An unavailable summary retains the possible effect rather than interpreting a generated body or inspecting hidden collection entries.
A supported reflective invocation summary applies to the established callable value, not to the spelling of the property or binding through which it is obtained. An original intrinsic retained through a copy or another
The same identity requirement applies to invocation adapters. Properties named call, apply or bind are ordinary properties and may supply replacements or accessor results. Only an established original adapter receives its intrinsic summary, including when call and apply, retain the actual target receiver and forwarded arguments. For the original bind, retain the
An original apply obtains its argument list using the applicable array-like operations, including length conversion and all selected indexed reads, even if the target declares no parameters. Null or undefined supplies the empty list under the ordinary invocation rules. This operation is distinct from argument spreading and does not substitute
Possible deletion of an own property
Do not discard an already
Prototype relinking and an operation that later uses the changed chain have distinct effect positions. A summary of the original Object or Reflect setPrototypeOf requires the established callable identity and positive evidence about the target's applicable internal operation. A possible ordinary target does not cancel an unknown alternative, and a compatible callable type or decorated source function does not prove an ordinary target. A proxy or unproven target retains possible effects at relinking. Relinking a proven ordinary target does not itself read or invoke a property on the new prototype; possible rejection under extensibility, cycle, nominal or inherited-storage obligations still follows the runtime algorithm. The Object helper preserves its target alias on success, whereas the Reflect helper returns a Boolean.
Retain possible prototype links with their aggregate identity, contributing operation position and dependent uses. A later inherited lookup or protocol operation includes effects that the changed chain can expose, including changes on an intermediate prototype and deletion that removes an own-property hiding condition. Preserve the original receiver. Positively established own-property presence can exclude a same-key inherited route; an unavailable chain or protocol summary cannot establish effect-freedom. Include applicable inherited setter, descriptor-
An already
Initial prototype construction separates allocation from descriptor processing and later inherited use. A summary of the original Object.create requires its established callable identity and associates a successful result with a fresh allocation and the supplied prototype dependency. Installing that prototype does not itself read or invoke its properties, including when the prototype is a proxy or the
For object literals, distinguish the non-computed colon prototype form from computed, shorthand and method definitions with the same name. Preserve the default prototype where that form ignores a non-null primitive, exclude it for a null prototype, and retain possible effects for unknown contributing alternatives. Apply the separate runtime argument requirements of Object.create rather than the literal form's ignored-primitive rule. A possible descriptor-table entry alone does not establish unconditional own-property presence: descriptor processing can invoke user code that removes a later entry. Require positive evidence that the relevant entry remains available when consumed. Possible failure during creation does not establish successful allocation or bypass any receiving, prototype or inherited-storage obligation.
Descriptor conversion includes the type
Ordinary inherited data lookup or positively established absence does not itself invoke a callback. An accessor requires evidence about its actual implementation and the applicable receiver operation; neither its
A later namespace assignment or property definition does not retarget a factory value obtained before its arguments were evaluated. Retain the relevant callable read and property-write positions, including aliases and supported invocation adapters. Incomplete argument-arity evidence does not prove that descriptor processing is absent. These summaries execute no user operation during checking, add no runtime access or replay, and change neither public type identity nor reference authority and liveness.
A property store selects its applicable data or accessor route; the spelling __proto__ does not by itself identify prototype relinking. A summary for the original legacy prototype setter requires its established callable identity, including a saved or renamed original. An own
Obtaining the Reference for a property assignment does not Reflect.set is not interchangeable with its lookup target: a data definition and its stored capabilities belong to that receiver, and an invoked setter receives it as its this value. Retain conservative alternatives when arity or receiver evidence is unavailable. Preserve the positions of global-property may-writes as well: a later
The ordinary store method's identity alone does not establish absence of user code. Include applicable
Where every contributing callee alternative has established bound invocation flow, apply the
Descriptor observation and ordinary property Object.getOwnPropertyDescriptor, Reflect.getOwnPropertyDescriptor or Object.getOwnPropertyDescriptors, preserve the applicable target validation, key conversion, own-key enumeration and own-descriptor operation. Observing an ordinary own accessor descriptor does not invoke its getter or setter and does not search the target's prototype. A proxy trap, trap-result descriptor conversion or unavailable backing representation retains its effects unless positive evidence
A reflected descriptor is a fresh
Follow a transparent unannotated immutable callable copy to the read in its initializer. A copy made after an affecting
Available initialized binding or ordinary own-data evidence from an earlier source can identify a possible global alias without invoking user code. Do not invoke an accessor, exotic operation, initializer, decorator or imported body to obtain such evidence; do not bypass an uninitialized or reference binding. Import syntax alone proves neither producer implementation identity nor absence of effects. These observations can withdraw result evidence, but do not prove that a mutable value remains unchanged in a later execution. A
The global alias write makes the subsequent call through f, including a transparent copy made after the write, unavailable as evidence of the former string result. The Number receiving boundary must admit the actual result. The earlier copy retains the original implementation; replacing the final receiving type for before() with Number therefore retains the string-versus-Number Early Error.
function f(): string { return "old"; }
const before = f;
const world = globalThis;
world.f = () => 1;
const after = f;
const n: number = after();
const s: string = before();
Storing the f. The Number receiving boundary admits the box.world.f with box.f would instead write the container's own property and would not replace the global function.
function f(): string { return "old"; }
const box = { world: globalThis };
box.world.f = () => 1;
const n: number = f();
// First Script:
const box = { get world() { return globalThis; } };
// Second Script:
function f(): string { return "old"; }
box.world.f = () => 1;
const n: number = f();
Withdrawing a replaceable callee's result assumption is distinct from discarding a required result contract of the enclosing function. Where
The final call returns typed unknown, so neither produce nor id obtains a result contract from that call. Adding : T to either function's result requires a runtime
function bad(): any { return "s"; }
function id<T: type>(x: T) {
function typed(): T { return x; }
function produce() { return typed(); }
let replacement: any = bad;
typed = replacement;
return produce();
}
id(1);
Without this condition the first elision above is unsound in fully annotated code, and no inference is needed to reach it:
function f(): uint32 { return 5; }
function g(): uint32 { return f(); }
f = function () { return 'now-a-string'; };
const n: uint32 = g();
Both checks are licensed by the first bullet — g's return because f() is a uint32, and the binding because g() is — and with both elided the string reaches n and nothing reports it. A function declaration creates a mutable binding, so the assignment on the third line is admitted by the shallow
A lexical assignment inventory proves only the bindings and effects it models. A Script-
A production engine may use an independent identity, descriptor or dependency guard to establish the proof. Failure retains the required admission in the same execution, using the original
A range's bounds are part of what its MEMBERS are, so the four shapes of one span do not prove the same thing: 0..<3 proves an index into a [3] and 0<..=3 does not, its members being 1 through 3 and its ceiling therefore 4. An open start raises the
A value generic alone does not carry the proof, which is why the rule above names a where clause rather than the parameter. Inside a generic body the parameter's VALUE is not yet bound, so a[I] in a function taking [N].<T> proves nothing about the relation of I to N and may index 9 into a [4]. What supplies the relation is the where clause, checked at each
Stating it over members also makes the proof independent of SPELLING. 0..<8 and 0..=7 have the same members, so a rule keyed on the written end would prove one and refuse the other, which is a distinction about notation rather than about values.
A check at a PARAMETER is not elided on the evidence of the calls a checker can see. A parameter's annotation is shared by every call Function.prototype.apply, as an argument to a built-in that calls it, through a direct eval, and from another module or another source text entirely. An implementation may elide a parameter check where it establishes that every call is proven, which is a whole-program property and is outside what this clause requires. The same holds of a store to a property, which is reached through Reflect.set and through every other route to
In a fully
A class in which at least one public or private
The sealing takes effect at the END of construction: at the outermost construct frame, once the most-derived class's instance fields have been initialized. It cannot take effect earlier, because a derived class's fields are defined after its base's this.x = v in the body of a this[k] = v with a statically established key or
READING a property the type does not declare is unaffected. The restriction is on a property being ADDED or REMOVED, and a read of an absent property adds nothing: it yields if (o.maybe), typeof o.maybe, and o.absent === *undefined* are all ordinary. The asymmetry is deliberate. A write that adds cannot succeed, and in sloppy mode it fails SILENTLY, so refusing it early prevents a program that cannot work; a read succeeds and the value it produces is one a program may want.
A successful structural boundary establishes the declared storage contracts of the properties it admits even when their current values already satisfy the receiving type and require no conversion. Preserve a property's existing backing contract; a later wider or narrower structural view does not replace it. Subsequent ordinary and reflective stores, deletion, and reference admission consult that backing contract. A readonly member in an object type still describes the view permission, not a change to another alias's permissions.
Retain the property identities, keys, values, and resolved types from the successful admission rather than evaluating members again merely to acquire their contracts. Carry this evidence through nested structural admission. A failed or abrupt membership alternative contributes no acquired contract: a union retains the admitted alternative's evidence, an intersection requires its member judgments, and sequence admission retains the selected assignment's evidence. An independent membership query, including is and instanceof, and a reference or overload viability probe do not acquire contracts. An absent optional object member is not created by this process, and acquiring a contract alone does not change user-visible properties, identity or prototypes. Initializing missing own Array elements below its admitted length is a separate requirement below.
Successful membership may still require generic-callable
Where several admitted paths reach the same Object and
Publish the new property contracts acquired by this admission after its required specializations and writes succeed. A failure does not replace the receiving binding and publishes none of those new contracts, but earlier completed ordinary writes, setter effects and independent nested boundaries are not rolled back. Retaining admission values is not a snapshot that prevents user code from changing the Object. Independent membership and reference-viability judgments still neither specialize nor write. Elision must establish required materialization as well as membership and storage acquisition; a broad or generic backing contract alone does not establish it.
Apply the retained-admission,
When an Array acquires owned homogeneous element storage, apply the value-copy requirements of
When a boundary acquires owned sequence storage on an Array, every missing own indexed position below the admitted length must be initialized from its retained admitted value. This requirement applies to homogeneous-array and tuple admission, including nested and selected structural paths. A missing own property is distinct from a present value of
For such new owned storage, retain the observed length and, immediately before each indexed
Perform required materializations, value copies, and ordinary writes in the retained order already specified. Track their resulting values for initialization. Recheck the Array's length before and after each affected materialization and write. An outer write must recheck length, own presence, and an own data value
After all required ordinary writes succeed, validate every new Array initialization plan before publishing any still-missing positions or new contracts. The length must still match. An originally own position must still be own. A still-missing position must permit an own definition. Every current own position must have a data descriptor, including when backing was independently established during ordinary effects. Unless backing was independently established, its data value must be
After all plans validate, create each still-missing own element as a writable, enumerable, configurable
The retained-admission requirements also apply to tuple elements. Retain the element values and admitted union contracts of the assignment selected by
For a tuple without a rest, trailing defaults do not suppress retained admission of supplied positions. Retain each supplied value and selected callable contract on the same terms as a required position, including when the tuple is reached through another structural contract. Its complete storage domain includes every
A converting boundary receiving a no-rest tuple with omitted trailing positions must complete those positions even when membership of an enclosing structural contract or selected union member succeeds. Retain a construction obligation with the observed source, supplied values, resolved tuple type, selected subvalue evidence, and already formed defaults. An independent membership or viability judgment retains no such obligation. A fully supplied compatible tuple continues to use in-place admission. An omitted position is determined by the observed length, not by a supplied hole or
For each selected construction, create a separate Array using the receiving
Where retained paths reach the same receiving Object,
Complete the selected construction using all its retained positional and homogeneous contracts. A newly appended value must satisfy their combined position contract; a declared default validated at type formation does not by itself establish a different retained obligation. Retained object contracts apply to the produced Array rather than to custom properties of the source: a required property unavailable on that Array throws a
At the reached nested construction boundary, finish the output's required checks, materializations, copies, own-data initialization and backing publication before the enclosing ordinary Set. Keep the enclosing admission's earlier preflight and retained write order. A false Set result throws a
A proof used to elide a receiving boundary must also establish that no omitted tuple construction or other retained materialization remains. The backing type's permitted length
Intersect a defaulted tuple's complete length
When one admission acquires several such tuple or homogeneous-array contracts on the same previously untyped Array, intersect their complete length domains and their types at every permitted position. A fixed extent bounds the complete domain; required prefixes and suffixes contribute their combined minimum length. Keep every suffix aligned to the end at each permitted length rather than flattening it at the initially observed length. An empty intersection of length domains throws a
For these required prefix/rest/suffix contracts, a length change must preserve
At a statically known length, contextual typing of a literal against such a tuple uses this same exact assignment. Where existing static index analysis establishes a positional read at index i without a known length, its read domain is the union of the types that can occupy that index over permitted lengths. A required prefix position is exact. Beyond a prefix of width p, this includes the rest type and suffix entries zero through
A successful sequence assignment is evidence for that admission, not permission to replace an existing location's declared contract. Later stores, including stores through invariant references, continue to check the backing location type. A new assignment selected from current values does not by itself relocate or remove storage and therefore does not invalidate existing references under
Eliding a boundary must preserve required storage-contract acquisition. A proof that the current value satisfies a structural type does not alone prove that the backing contract exists. Acquisition can be omitted only where its result is already established, or retained separately from an elided check without introducing new observable evaluation. Discarding speculative admission evidence does not roll back the ordinary effects of a conversion or a nested boundary that completed before a later failure. These rules establish no reference-liveness permission.
Owned
The own-data requirement adds no per-read accessor conversion and does not silently materialize an own getter into storage. Independent membership can observe accessor values without acquiring backing. Inherited values used to initialize missing own data retain the admission rules above. A genuine elementwise copy creates separate data storage and leaves source accessors unchanged; a compatible cast that retains identity still performs owned-storage admission. The general property-reference eligibility rule does not authorize a descriptor prohibited by the receiver's backing contract. Ordinary
A store to a
For a union receiver, this storage judgment is established when every reachable alternative on which the deletion executes establishes prohibited storage at the selected key. An optional chain's short-circuited alternatives perform no deletion. A genuinely unresolved alternative does not establish this judgment. The predicate is about deletion, not whether an element read would be in bounds: an ordinary absent property or a key beyond every fixed extent is not made a protected position by the union. The same single-receiver storage judgment, including the protected index domains below, is applied independently to each executing alternative.
The deletion proof also ranges over a statically established delete.
For example, delete _a_[0] is rejected before evaluation when a has type [2].<uint8> or [uint8], even inside a function that is never called. This is a judgment about deletion, not the bounds of an element read: a key beyond a fixed tuple's positions and an ordinary property not declared by the type are not
The statically protected index domain includes each canonical
A class whose fields are dynamic modifier, which leaves it unsealed. Such a class cannot be used for sequential data and cannot be a
Sealing is what makes a
A
instanceof therefore extends to every type of this a instanceof uint8 is uint8 binding and a instanceof uint16 is Span.<T> is the type that accepts either — and a class is unaffected, since a class's type object is its prototype, so for x instanceof C where C is the prototype of a new Box.<uint8>(1) instanceof Box, new Box(1) instanceof Box, and new S(1) instanceof Box for a class S extends Box.<uint8> are instanceof Box.<uint8> is the ordinary walk against that prototype. A successful check narrows the static type in the branch it guards, which
catch ClausesA catch clause may name the type it handles, and a try may then carry more than one. The productions below restate their ECMAScript definitions (
It is a string by conversion would bind a value that is not of the declared type. It is a catch (e: uint8), a later catch (e: uint8 | string) starts with e narrowed to string; assignment is still checked against uint8 | string and can invalidate that fact. An observable structural membership test supplies no unsupported subtraction proof. This rule does not infer the try had no catch:
A destructuring
This is the await unchanged, since await rethrows the rejection reason and the dispatch above neither knows nor cares that the throw crossed an async boundary. The errors this catch (e: RangeError) handles every range failure in one clause (
This clause gives meaning to the annotations the grammar attached to bindings in
A type alias declaration names a type. It is added to the declaration forms.
A type alias declaration's binding is immutable, as a const declaration's is, so that the value its name holds can never disagree with the type its name
A type alias declaration with no
A completed alias result retained from an initialized binding is evidence only for that binding. Resolve a later reference in its source environment, following an indirect import to its target binding. Neither an alias in an unrelated scope nor another evaluation of the same declaration supplies that result merely because its name or source declaration is equal. A nearer binding, including one that is uninitialized or is not an alias, prevents falling back to an outer alias's completed result. If checking has selected a source declaration, a retained result from a different declaration cannot replace it. These requirements apply across successive Scripts in one
Publish such binding evidence only after the alias's initialization succeeds. A computation that has not initialized that binding, including a failed or incomplete declaration preparation, supplies no evidence that the binding contains a completed alias. The temporary recursive record used while forming an alias is distinct from this evidence and does not make an unrelated read initialized. This rule does not prevent checking an available alias definition by its source declaration under the existing declaration-preparation rules. Retained results remain subject to
When checking retains a type obligation for later evaluation, retain the lexical declarations and determining inputs of that obligation and of each producer it calls or reads. Reconstruct a helper's environment from the declarations visible at the helper's source, including transitive helper and alias dependencies; the consumer's same-named bindings do not replace those captures. A retained constant input belongs to its producing binding, not to every reference with that spelling. Form an available alias definition in its own declaration environment, preserving its ordinary recursive and refinement semantics. This formation supplies a checking result without initializing or publishing an unrelated program binding.
A generic alias dependency retains the environment of its declaration and the
The same argument, default and constraint rules apply when the alias is selected through a namespace-qualified type reference. Qualification neither erases a written argument list nor changes which declaration supplies omitted defaults, higher-kinded parameters or the alias body. Resolving the qualified declaration does not itself apply defaults at a position requiring the unapplied declaration, create an ordinary binding for a generic alias, or alter the module availability rules.
Any internal representation of an unapplied generic alias must distinguish declaration contexts with different determining inputs, including different activations of a
A selected source binding whose required runtime inputs are unavailable remains unresolved under its existing deferral rule. It must not fall through to an older or outer binding with the same name, and executing an ordinary enclosing body is not a way to manufacture the input. A missing reference does not acquire a declaration from the consumer's scope. Imported inputs retain their actual target bindings and the availability and failure rules of
A
type Point = { x: float32, y: float32 }; is therefore close to a const whose value is a type object, and the two spellings agree: Point === type { x: float32, y: float32 } is const is that the name is also usable where a type is expected. An alias is transparent: it introduces no new type, so Point and its expansion are the same type and are mutually assignable, and a nominal type is created only by a class, an interface, or an
An alias may refer to itself, directly or through other aliases, provided every cycle passes through a position that holds a reference rather than an inline layout: a member written T | null, the element of a dynamic array, or a
A callable's parameter and result types are reference positions for this graph judgment: a function value does not contain its signature's parameter or result storage inline. Continue validating the types reachable through that signature; an independent cycle within one of them still requires its own reference position. This rule describes type graphs, not program Reference Values, and grants no reference-liveness or storage permission.
The same recursive-validity rule applies to a generic alias application. During structural description, a repeated application of the same alias declaration in the same determining declaration context, with the same completed arguments,
A retained graph edge is not a completed type result and discharges no independent constraint, predicate, default or required computation. It must not supply an unfinished type as an input to another computation, an admitted argument, a member-lookup fact or an interned
Formation uses the actual declaration environment. Forward resolution in type position does not initialize the ordinary value binding, execute an enclosing body, or change imported-input availability. Restore temporary environments and discard unfinished formation evidence on every exit, including rejection, resource exhaustion and
An OPTIONAL member does not break a cycle. { next?: L } demands the inline layout { next: L } does, because a member that may be ABSENT still needs room for the value where it is present; T | null breaks one because a null member is a reference position, and the size of a reference does not depend on its target's. The two are close in what they admit and far apart in what they cost, which is why they are distinct types ({ next: L | null } rather than { next?: L }, and the second is a
The [no LineTerminator here] restriction is what keeps type a type followed by a line terminator and then an identifier is two statements today, an expression statement naming type and another naming the identifier, and it continues to be. This is the same device the type X = 1; on one line is unambiguous: it is a syntax error today.
Whether a generic alias may be exported, and how export type Point = ... interacts with the module's binding namespace, is left to the modules clause. A generic alias creating no binding is a decision worth revisiting: it means Grown in type Grown<T: type> = [...Row, T]; cannot be reflected on, while a non-generic alias can be.
var, let, and const accept the same annotations. A binding declared with a any type. The type of a binding is fixed at its declaration and does not change with what is assigned to it.
A binding declared with a type and an any type. A binding declared with a type and no
A const declaration without an let binding of that type is, and it is a
The annotation is what makes the initializer redundant rather than absent. const c: [].<uint8>; has already said what c holds — a uint8 array, and therefore an empty one — so requiring the program to write = [] as well asks it to repeat the annotation in a second spelling. Every other const that could not would be the one place where an annotation stops carrying that meaning.
What const promises is unchanged, because it is a promise about the BINDING: the binding does not rebind, and assigning to it remains a const a = []; admits a.push(1) — so a defaulted const holding a mutable aggregate is the case that already exists, reached by a different route. The temporal dead zone is likewise unchanged: the default applies at the declaration and not before it, so a read above the declaration is still a
A ref binding is a separate rule and keeps its ref aliases the storage its initializer names, and a type's default is a value rather than a location, so there is nothing for it to alias.
Assignment to a binding of declared type t checks the assigned value against t by the same rules. The temporal dead zone is unchanged: a let or const binding is uninitialized until its declaration is evaluated, and accessing it before then is a
That a let a: uint32; holds a uint32, so every read of it yields a uint32 and the engine may lay it out as four bytes; were it uint32 | undefined in all but name, and the layout would have to carry a bit saying which. This is the same reason a
The abstract operation DefaultValueOf takes argument t (a
Default aggregates carry their declared element or positional backing contracts, including nested aggregates. Each needed default is computed once per position in order, without a separate availability probe; failure retains earlier independent computation effects but exposes no partially constructed aggregate.
A ZERO extent is answered before the element is consulted. A fixed array's default is its extent filled with the element's default, so an element with none leaves the array with none — but at extent zero there is nothing to fill, and the element's default is not needed. Asking for it first would make [0].<never> have no default while [0].<uint8> has one, a difference no value of either can observe, both being the empty array and the same value the dynamic extent already takes. The step matters where an [0].<_T_> must not become unwritable at the instantiations whose argument has no default.
It is a
A PRIVATE
For a GENERIC class the G<4> whose [N].<uint8> has a [4].<uint8>, so its default is a four-element zero-filled array; the declaration's own
A parameterization's default is its base's zero having crossed into it, by the same conversion an initializer of the base's type crosses (let _x_: _T_; and let _x_: _T_ = 0; succeed together and fail together. Three consequences follow from the crossing rather than from a rule of its own. A validate refuses the base's zero leaves the parameterization with no default, since the crossing throws and the step reports float64.<{ min: 1 }> has no zero because 0 is not one of its values. A brand for which no cast is declared has no default either, being "reachable only by construction" and a declaration without an initializer not being one. And a parameterization whose base declares a cast has the crossed zero, which is what keeps a Velocity is a float64 whose dimensions validate, so the cast admits the zero and let _v_: Velocity; holds 0 m/s.
The meta types and casts declared in the surrounding meta declarations and its primitive blocks' implicit cast operators before applying this operation (Reflect.defineProperty with a type and no value (
Every default above is a zero, or, for a type with exactly one value, that value: the numeric zero, the empty String, ===
An aggregate has a default exactly where its parts do: a Vector3 of three Meter fields is zero-fillable exactly where float64 declares the cast into the dimensions let _d_: [10].<Vector3>; is a
Read the other way, the same rule is how a type says that no zero of it is meaningful. A class that needs its
The reason is security rather than convenience. An allocation that exposed the bytes of a previously freed one would leak whatever had been there: another script's data, a former secret, or a heap pointer that defeats address space layout randomization. A fresh page from the operating system is already zero, so the fill costs nothing on a large new allocation, and reused memory must be cleared regardless, so there is nothing to gain by exposing it. The one pattern an uninitialized form would serve, allocate and then fill from input, is served by placement new over an existing buffer and by array views.
A type with no default is one for which no zero is meaningful, and the operation reports that rather than inventing one. A union of two non-nullable types is the ordinary case: let x: uint8 | string; has two candidate zeros and no reason to prefer either, so it requires an initializer. symbol, object, a function type, and a class whose construction must run are the others.
The bigint is given the zero 0n on the same reasoning as the other numeric types. A tuple defaults element-wise, which follows from a tuple of value types being laid out contiguously, but a tuple with a defaulted trailing position already has an initializer for that position, which let d: [10].<A>; hold ten zero-filled instances as the memory layout extension requires, but it means a class can be brought into existence without its
A binding declared with a any type. Its declared type is the
let a := new MyType(); declares a of type MyType, where let a = new MyType(); would declare it of the any type. The two spellings differ in what may be assigned afterwards, not in what is stored: the first rejects a = 5, the second permits it. := therefore removes the redundancy of let a: MyType = new MyType(); without introducing inference anywhere the program did not ask for it, which is why it is a distinct operator rather than a rule that an initialized binding infers its type. An unannotated binding remaining any is what keeps an untyped program untyped.
It is a any type, since let a := f(); where f is untyped would declare a binding of the any type through a syntax that asks for a type.
A test that establishes something about a value's type
The narrowing facts that hold at a point are the join of the facts on every if statement one of whose branches cannot complete normally see the facts of the other branch, as its else would; the exit of a loop sees the facts of its test's failure joined with those at each break that leaves it, after the loop rule below has discarded what its body can reassign; the end of a switch with no default sees the facts of each clause that completes joined with those of the fall-out past its last label; and a catch or finally block sees the facts at the try statement's entry less those about a try block can assign. Where a test is a conjunction, the facts of its truth are those of every operand, each operand's facts holding where the next is evaluated; where it is a disjunction, the facts of its falsity are those of every operand; a false conjunction and a true disjunction establish none of their operands' facts.
Narrowing preserves whether the binding null cannot support an ordinary property operation, and a
| Form | Where true | Where false |
|---|---|---|
v instanceof T | NarrowFrom(s, T) | |
#f in v, a brand check | #f) | NarrowFrom(s, the class declaring #f) |
v.length === k or v.length !== k, with k a | The members whose exact length is k | The other members |
k in v, with k a String or Number literal. Classify each member independently: always present for established in follows the prototype chain, which a program may extend; unknown otherwise. A receiver still under construction supplies no | Members not proved always to lack k | Members not proved always to hold k. Unknown members remain on both edges; an undeclared property does not prove absence through the prototype chain |
%, the pipeline | Every row of this table, in the positions the test governs. The | |
v is T (v is P for a pattern P ( | ||
v === e or v !== e, where e | NarrowFrom(s, L) | |
v === w, for established built-in strict equality over known primitive value domains | For each operand, the portion of its | No additional domain exclusion in general, except that the edge is new of a class binding the program never assigns whose !== exchanges these edges |
v.p === e or v.p !== e, where e | p admits L) | NarrowFrom(s, the union of the members of s whose type at p is a subtype of L), since a member whose type at p is wider than L can reach either branch |
v op e, where op is a comparison, the v is a e is a | ||
A call of a signature whose [[Narrows]] holds a v is passed in, where the signature's [[Return]] is boolean | NarrowFrom(s, that | |
| The same, where the signature's [[Return]] is | ||
typeof v === e, where e names a String literal type whose value is k, and a case k of switch (typeof v), which applies this row to each label as the case e row below applies the v === e row | typeof is k) | NarrowFrom(s, that union) |
typeof v === typeof w, and the corresponding nonliteral switch label | Each preserved source's members whose tags occur in the other | Exclude a tag only where the other domain has that single tag; inequality exchanges these edges |
An established original Object.is(v, e) | The operation-specific compatible domains and exact evidence of | |
v == null or v != null, and the nullish operators ??, ??=, and ?. | The nullish branch: null | undefined) | The present branch: NarrowFrom(s, null | undefined) |
v as the test itself | The | The |
A switch on a value of an | In each case, | In the default, s with every case's type removed |
A case e of any other switch (v), where e v === e row, applied to the label | In the statements the label reaches, | In the labels after it, NarrowFrom(s, L), since the labels are tested in order whatever the statements do; in the default and in the statements a later label reaches, NarrowFrom(s, L) where no earlier label falls through |
A non-singleton case e of switch (v) with established built-in strict equality over known primitive domains | The equal-edge domains of the v === w row, projected only onto preserved discriminant and label origins, then joined with incoming fallthrough | No whole-domain exclusion merely from a failed comparison; retain effects of evaluating the label |
A match clause when P ( | In the arm, | In later clauses, |
v above is a reference to a binding or a chain of property accesses over one, a path, and s is its this, private names included; an object property access whose computed key is an established constant String, including an immutable binding naming that String; and an index into a FIXED-extent array whose index cannot change - a literal, or a binding the source never assigns. A call anywhere in the chain is not a h.get().p are two values; nor is an index into an array whose extent can change; nor an optional link a?.b, which names a different place depending on whether its base is nullish. A computed key must require no unknown evaluation or coercion to establish its identity. Key identity is property identity: v["tag"] and v.tag name the same property, while v["a.b"] and v.a.b name different places. Parentheses around a test or its operands preserve recognition, including (typeof v) === "number"; this does not make an effectful comma expression transparent. An expression that is not a if: the two branches of a conditional expression are narrowed by its condition, the right operand of && is narrowed by the truth of its left operand, and the right operand of || by the falsity of its left. A test decides a branch in the test of an if, while, do-while or for, in the condition of a conditional expression, in the left operand of &&, || and ??, whose right operand it decides, in the switch whose && or ||, the last operand of a comma expression, and each arm of a conditional expression. The right operand of && or || that is not itself in such a position decides no branch, since its value is the result. A fact about a == and != narrow as === and !== do wherever the Static Types of both operands make the two agree, which a union of literal types over one base always does, so the this.country == 'US' narrows and a comparison that could coerce does not.
The constant key in an in test may be supplied by an established singleton String operand, including a constant alias, rather than only by literal syntax. Resolve the key without executing evaluation or coercion, then apply the existing per-member instanceof Symbol.hasInstance operation supplies no nominal implication by this rule.
An operand e names a literal type L where it is a literal expression — a numeric literal, optionally preceded by - or +, a String, BigInt or Boolean literal, or a const binding with no type annotation whose initializer is such an expression, and L is that expression's literal type; where it is void applied to such an expression, and L is undefined; or where it is an enumerator reference E.A, and L is the literal type of the enumerator's value over the
Where a switch's default does where no clause falls into it.
The types whose typeof is null; the types whose typeof is typeof is either, since a function with the required properties is a value of it; nor is an untyped class, whose typeof test of a value of one of those can go either way.
The bare-v row uses the existing representable truthy and falsy parts. A boolean becomes true or false; boolean | null becomes true or false | null. Falsity is not evidence of absence alone: zero and the empty String also reach the false edge. Where their precise subsets are not represented, numeric, String and BigInt members conservatively contribute to both parts. The literal-idiom exceptions and ordinary write invalidation still apply.
Facts reach paths because the types this if (addr.country == 'US') is the mode reassigned after a name was narrowed by it. A LOOP discards a fact about every name its body can reassign, before the body is entered: a body is reasoned about once, and a read that precedes the assignment textually still follows it on the next pass, so a fact that the body invalidates cannot be held for any part of it. A CALL discards a fact about any name some function body assigns to. A call can reach a binding the caller narrowed - a closure over it needs only to be in scope - and the caller cannot see which one, so the conservative fact is whether any function assigns that name at all. A name no function assigns is untouched, which is what keeps an ordinary call from discarding anything. The boundary check remains the backstop for what static reasoning cannot reach at all, such as a value arriving through any (
The type errors this clause states, among them an excluded value that leaves nothing, a repeated test and a loop that can never be entered, apply only in <, <=, >, >=) of a place whose type is an where clause is judged under the clauses written before it.
Narrowing is a bounded analysis of the control flow of the source text. A fact identifies a lexical binding and, where applicable, a
For a reference binding, distinguish its declared read contract, the selected location and a fact about a value read from that location. A store that can change the referent, including through another alias, or a rebinding that can change the reached location revokes affected current-value facts. An alias of another reference binding follows that binding's rebinding under
A value read through a reference has the ordinary effects of accessing its referent, including possible accessor or proxy execution and
Classify a reference value read using its source-position location alternatives. A proved chain of ordinary lexical cells does not invoke an accessor merely because it is reached through a reference. A possible property location or unresolved reference source retains possible accessor and
Location formation evaluates the receiver and computed key in ordinary order, with their value-read effects, but does not consume the value at the selected location. Apply read effects only on admitted evaluation paths. Account for possible reads in loop widening, exceptional entry and finalizer transfer, and restore their source-position inputs during deferred checking. Creating a callable does not execute reads in its body. An imported binding can forward a reference: use symbolic binding information where available, and do not treat unresolved imported origins as proof of an ordinary data read. Obtaining this information executes no module body or accessor.
This distinction does not remove separately required receiving operations. Where an ordinary function creates an arguments object, the reference arguments decay into that object's values under
An instance
Effect timing is separate from the classification in
Retain an established lexical reference source as a binding-cell dependency at the source position where it is formed. Do not replace a dependency on another reference binding with that binding's then-current terminal referent. At a store, invalidate current-value facts about each possible overwritten cell and aliases that may reach it; at a rebinding, invalidate dependent alias facts and replace the selected source without treating the old referent as overwritten. Apply this distinction to updates, compound and logical stores, and destructuring stores. This supplies location dependencies for invalidation and preservation, not a new rule propagating singleton values through aliases.
Location alternatives use the admitted predecessors of the existing flow judgment. A missing required location proof contributes uncertainty, not an unreachable predecessor. A property origin alone does not establish independence from a lexical store or rebinding: its getter may read that lexical cell or reference binding. Include possible writes and rebinding in loop widening and exceptional-entry transfer, and transfer a finalizer's location changes as well as its value effects. Retain the location environment with a deferred judgment's original source inputs and restore the suspended environment after nested checking. A
In
let x;
let ref r = x;
if (r !== undefined || typeof r !== "undefined") {}
The first test below does not establish a permanent singleton contract for r. The subsequent store to its referent invalidates that fact, so the second test is not rejected on the basis of the earlier value. The tests execute against the current referent in ordinary evaluation order.
let x = 1;
let ref r = x;
if (r !== 1) throw new Error("initial");
x = 2;
if (r !== 2) throw new Error("live");
Keep separate environments of facts for the true and false results of a test, and for normal, return, throw, break and
The following transfers apply to the narrowing forms in
a && b, evaluate b under the true environment of a; the true result is the true environment of b, and the false result joins the false environment of a with that of b. For a || b, exchange true and false in this rule. Process effects in evaluation order, so an operand can invalidate a fact established by an earlier operand.if statement joins only the ?? and ??= is governed by the left operand's nullish environment. The right operands of &&= and ||= are governed by the same environments as their non-assignment forms. An optional chain evaluates its computed keys and arguments under the present environment of the receiver at that optional link, and joins its normal result with its skipped paths. Effects in an earlier key or argument may invalidate facts before a later one. A typeof switch Object.is test, or a preserved predicate alias. undefined; an equal-result selection excluding undefined, or an unequal-result selection against undefined, excludes that skipped alternative. Preserve the evaluated alternatives' receiver-presence facts only while the o?.tag === "a" can therefore establish presence, but o?.tag !== "a" and o?.tag === undefined do not generally establish it. typeof tag mapping before selection: a skipped link contributes the String "undefined", and a present result contributes its established tags. Selecting a tag other than "undefined" excludes every skipped alternative; selecting "undefined" need not prove absence, since a present result may be undefined. This is a relation to the evaluated optional result, not permission to repeat a getter or reevaluate its receiver.undefined and the skipped undefined result remain distinct alternatives with different receiver facts, even though one label can select both. Apply the same rule to typeof-transformed optional results using their String tag values.Parentheses around a switch discriminant preserve its selection rules and facts. A constant range label that admits no value of the
The result producers in these tested-expression transfers are deciding positions for the impossible-test judgment to the extent that their results decide the enclosing branch. The transfers export facts about surviving bindings, not new lexical bindings from a pattern, match arm, completion block, or
A store may retain an existing fact about its target if the established stored result, after any required conversion, is a subtype of that fact. Check the store against the declared destination type, process source effects in evaluation order, and invalidate affected descendant paths. Retaining a property fact also requires that evaluation has not replaced a prefix of the
An immutable Boolean binding initialized from a recognized test retains that producer's established true and false implications under the following source-identity rules.
An initializer that reads a preserved immutable Boolean alias consumes that alias's result environments under these same rules. A copy, negation or other recognized composition therefore retains implications about the original source places, not only the immediate Boolean operand. Each source identity includes its declaring binding and
A generic value retains its parameter identity for bindings, assignments and results. Alongside that identity, keep a proof domain obtained from an established upper bound and refined by the actual incoming branch facts. Subsequent type-test and truthiness judgments may use that domain, including on a false edge; the domain is not a
For a preserved scalar value, a failed established literal comparison or literal-pattern selection also records the exact excluded scalar values internally, even when NarrowFrom returns the same broad case label and a literal pattern are such tests. A test over bindings whose operators have no effects records its outcome on each edge in the same way, so the same test before any write to those bindings can never succeed after it failed and can never fail after it succeeded; a case label repeating an earlier effect-free label of its switch can never succeed. Entering the body of a for-of is a test, which can never succeed where the iterated sequence's
A call proved to select the original new, an unknown argument mapping, a replaced or shadowed intrinsic, and an arbitrary Boolean-returning function supply no such correspondence. The intrinsic proof is a static identity and effect proof, not a speculative runtime guard.
A participating immutable binding initialized by an established identity-preserving copy of a primitive value retains that value's existing flow evidence, including exact scalar exclusions. It may also retain a same-value relation to the
An immutable binding holding a typeof result may retain the classification's source identity. A recognized tag comparison or switch label selects the corresponding source domain while the current source still
For a preserved participating numeric value, established original non-coercing Number.isNaN, Number.isFinite, Number.isInteger and Number.isSafeInteger tests refine a
On the equal edge of a proved built-in strict primitive comparison, retain a relation between the two
A comparison of two established typeof results projects compatible tag domains onto their preserved sources. On equality retain each source member whose tag can occur on the other side. On inequality exclude a tag only when every possible value of the other source has that tag. Unknown tags prevent an exclusion that depends on them. Apply this projection to a nonliteral typeof switch label with the ordinary label-effect and fallthrough joins. A shared tag does not establish equal values or equal
A call proved to select the original Object.is intrinsic can project compatible known primitive domains onto its preserved data arguments. A singleton String or other supported exact primitive value supplies success evidence and a sign- and family-correct miss exclusion. For the Number domain, success against the original Number NaN selects the NaN category and failure excludes it. Success against negative zero establishes that zero value; failure excludes only that sign, retaining positive zero.
For an immutable object binding pattern, apply the primitive-copy rule separately at each established data extraction.
A successful structural type test or structural member pattern may project its established member evidence onto a corresponding
The internal truthiness evidence for a preserved scalar is related to that scalar's actual falsy values, even where its public truthy and falsy parts both contain the broad member. The only falsy String is the empty String; BigInt has zero; floating numeric domains additionally distinguish both zero signs and NaN. A truthy edge excludes every applicable falsy possibility. Subsequent truthiness is proved only when every possible falsy value has been excluded; an unexcluded NaN or nullish union member prevents that proof. A falsy String observation may retain its existing empty-String literal type internally. These facts follow the same joins, invalidations, numeric identities and equality semantics as scalar exclusions, and create no public complement type. A short-circuit reachability judgment reads this same evidence about the && or a falsy left side of || instead selects the right operand. A NaN-only category is falsy. Use the same result feasibility when joining tested expression results.
An established awaited-result contract supplies the resumed expression's truthy and falsy parts in a deciding position. Awaiting a Promise is not a transparent test of the Promise object, and this result contract carries no arbitrary pre-suspension place relation. Unknown assimilation or fulfillment alternatives remain conservative. Absence of a normal fulfilled result follows
Metadata comparison requests may identify a
A declared call fact refers to the argument value supplied at its target. A tagged template uses the same selected-signature and argument mapping rules, with its cooked template object first and its original substitutions following in evaluation order. Unresolved overload selection supplies no arbitrary predicate or assertion. If a later argument changes the target place or invalidates its fact, that
This analysis composes the
The total order over canonical Type
The order is arbitrary and load-bearing at once: nothing about it matters except that every
The abstract operation NarrowTo takes arguments s (a
A class type is a Symbol, Proxy and ProxyHandler. Each is a type whose values are its instances by prototype chain, with the single-inheritance identity a class has, so two class types neither of which is a subtype of the other share no value. Symbol names primitive values, and a proxy takes its target's prototype, so neither has that identity.
A union t is taken member by member because C & (A | B), which stands for a type with no values.
The disjointness judgment is d ?? 5 where d is a uint8: the narrowed type would be the intersection of uint8 and null | undefined, which has no values.
never this same disjointness produces in a TYPE, and the difference is what each is for. A narrowing row that cannot succeed is a branch the program wrote and can never take, so it is reported as the mistake it is; a type computation that filters every member away has an answer, and the answer is never. The two are the same judgment put to different uses, which is why they may share the operation without sharing the result.
The type errors of this clause apply only in
This operation returns Type
Both sealed abstract class, as the union of the types of its direct subclasses; and where it is boolean, as true | false, a reading inside these operations that leaves the subtype rule of
Both operations return meet answers
It is a never type (
The judgment is made from Static Types, so each direction of it applies only where its answer is a fact no value can lose after it is checked. typeof is bigint, string, boolean, symbol, null or undefined, a type values are Type Objects); where both are primitive, literal or in rows of where refinement is evaluated again by every test. For a pattern test the condition applies to each
Where a member of s is a
A relational comparison, <, <=, > or >=, of an operand whose string, a union of literal String types, or an string, with a String constant, is judged the same way in code-string having no greatest. A fixed-length (String.prototype: as the limits of this paragraph, as a constant compared with another operand, as the value whose truthiness the truthiness rule settles, and against the case labels of a switch over it.
An equality, or a case label, whose operand is the global NaN or Number.NaN, unshadowed, can never succeed, and its negation can never fail, since NaN is equal to nothing; where the other operand does not participate (
A strict equality of two participating operands, neither naming a literal type, can never succeed, and its negation can never fail, where their Static Types share no value under the stability condition, a closed typeof results whose operands' tags (
The disjoint strict-comparison judgment is an operator obligation in
For example, String((5 := uint8) === (5 := uint16)) is subject to that obligation: consuming the Boolean result does not change the operands' contracts. An equality inside a function with unannotated parameters retains its ordinary runtime query where those operands have no participating contracts. Likewise, after let a: uint8 | string = 1, a read of a uses the established current-value fact under let k: string = "n" can select the uint8 member of { n: uint8 }, whereas a general String parameter without a
A test whose value's type settles its truthiness can never succeed or can never fail wherever it decides a branch, including a non-never, the branch its truth leads to can never be taken, and where the never, the other one can never be; it is a true, false, 0 or 1, under any parentheses and !, is exempt, so while (true), for (;;) and if (false) keep their meaning. The rule reads a test by the boolean receives no additional settled-truthiness judgment. The left operand of || and && is judged by the paragraph below. The bare-v row supplies the corresponding representable truthy and falsy parts as branch facts. A test whose void is refused by
After overload resolution and every specified result transformation, an established selected declared operator result is subject to the truthiness judgment in a deciding position. This includes unary, binary and declared compound operators, overloaded negation and derived comparisons. Equality-derived results and derived relational comparisons use the specified
The established normal Object results of match all, generator do * and async generator async do * expressions are truthy under the same judgment. An empty collection is still an Object; yielded and returned generator values are not the generator object. This does not apply the ordinary match/do scalar-result transfer to these forms, execute a generator body, prove nonemptiness, or discard
switch labels are tested in order, so a case label is a narrowing form over what the earlier labels left, by the case e rows of switch tested can never succeed, and so can a typeof label whose tag is not one typeof produces. The default of a switch over an default of a switch whose discriminant's every member is boolean, null or undefined, closed sets the language owns, once its labels leave nothing; this requires no case, and a written union of literal types stays outside, as the note below says.
This is why d ?? 5 is rejected where d is a uint8: the left operand can never be nullish, so uint8 and null | undefined keeps no member and the test is a mistake rather than silent dead code. A test whose narrowing keeps no member is a mistake WHERE IT DECIDES A BRANCH; elsewhere it is a question with a constant answer, which a program may legitimately ask — a instanceof uint8 for a uint8 a is how one demonstrates what the operator answers, and is not dead code. The ?? above is rejected in a value position for a different reason, and the distinction is worth keeping apart: its RIGHT OPERAND can never be evaluated, so the form contains code the program wrote and cannot reach, where an impossible instanceof merely has a constant result. The same reading applies to || and && whose left operand's type settles the test, and to ||= and &&=, whose right operand is a store that can then never be evaluated. The nullish forms read both ways: ?. on a receiver that is always nullish can never evaluate the rest of its chain, and ??= on a target that is always nullish always stores, so each is rejected as the ?? above is. A redundant ?. on an established non-nullish receiver is permitted: its written continuation remains reachable.
Narrowing on typeof is weaker here than in a type system layered over the language, and deliberately so, because typeof is unchanged: it reports typeof v === "number" narrows uint8 | string to uint8 but cannot separate uint8 from uint16, and reports Reflect.typeOf and instanceof are what distinguish those, and instanceof is the form that narrows. A program discriminating a union of numeric types therefore writes instanceof, not typeof.
Narrowing a binding or
The switch row default is not proven unreachable.
For binary arithmetic, bitwise and
Follow established primitive conversion results into the selected operation's own numeric-family rules. On the built-in numeric
The built-in >>> operation is unavailable for the ECMAScript bigint type, including when both converted operands are BigInt. A participating operation, including the built-in operation of >>>=, is a << or >>, Number unsigned right shift, or unsigned right shift in a sized
It is a + concatenation String(value) is unaffected. Ordinary JavaScript intrinsic results and unrelated annotations do not change exception timing. No coercion method or getter is executed to answer the judgment.
This clause specifies what an operator does when its operands are
The principle the
The abstract operation ToNumeric takes argument value (an
The remaining machinery is unchanged and generalizes as written. In
This is the whole of the dispatch. 1n + 1 already throws a uint8(1) + uint16(1) throws for exactly the same reason, by the same step, with no rule specific to it. The
any type, whose numeric type is not known statically: the operand arrives as whatever it is, and the same steps apply to it dynamically.
The string behaviour of + is unchanged and applies before any of this: if either operand uint8(1) + "x" is therefore uint8 becomes a value of another numeric type; it is the meaning + has always had, and changing it for
In peer-operand literal inference, a literal beside a parameterized numeric value adopts that value's numeric base, not its metadata. This rule precedes primitive-block admission and applies to arithmetic, comparison, equality, and case matching. Explicit binding, argument, return, and cast contexts retain their stated requirements.
Two operands of the same
The LITERAL rule of the preceding paragraph also reaches ==, !=, ===, !==, and a case label, though the uint8(5) === 5 is uint8(5) === n for an n of the number type is bigint literal takes no Number-family type, being of a numeric type of its own. A literal the type cannot represent is not equal to any value of it, so the comparison is
Reaching equality is what makes a a.find(x => x === 65) over a [].<uint16> finds its element, where without the rule it could find nothing. The alternative considered was to leave === strictly identity-based, which is the treatment BigInt receives - but a BigInt has its own literal syntax, 5n, and the types of this
It is a uint8 of 256 is 0, and would make x << k shift by the wrong amount. Where either operand has the any type the check is deferred, and the
float32 arithmetic is float32 arithmetic: x * x for a float32 x is computed in float32 and rounds once, rather than widening to float64 and rounding twice. C promotes float to double in many contexts, so the same source can produce different results depending on where a value is used, and the reproducibility this
A numeric type defines the operations of
| Family | Defines | Does not define |
|---|---|---|
| Every operation of | None. | |
| binary floating-point, and the | unaryMinus, exponentiate, multiply, divide, remainder, add, subtract, lessThan, equal, sameValue, sameValueZero, toString. | For a binary floating-point type of this |
| decimal floating-point | unaryMinus, exponentiate, multiply, divide, remainder, add, subtract, lessThan, equal, sameValue, sameValueZero, toString. | bitwiseNOT, the shifts, and the bitwise operations. |
| rational | unaryMinus, multiply, divide, add, subtract, lessThan, equal, sameValue, sameValueZero, toString. | bitwiseNOT, the shifts, the bitwise operations, and remainder. exponentiate is defined only for an |
| complex | unaryMinus, exponentiate, multiply, divide, add, subtract, equal, sameValue, sameValueZero, toString. | lessThan, since the complex numbers are not |
| vector | unaryMinus, multiply, divide, add, subtract, equal, sameValue, sameValueZero, toString, applied lane-wise; and the bitwise and shift operations where the lane type is an | lessThan, whose result for a vector is a vector of lane results rather than a Boolean. The SIMD extension declares the comparison operators on the vector types instead, overloaded on their return type, so they dispatch by |
An update expression, ++ or --, steps its operand by one of the operand's own type, as ECMAScript applies add with the type's unit. An
The float32 does not, because the same conversion applied to it would be an implicit conversion between two value types: ~x for a float32 x is a int32. This is the one place where an operator that works on a Number does not work on the corresponding
The table is the answer to the three cases
Where a declared operator applies (
For the built-in in and instanceof operators, including the private-name form of in, it is a instanceof operand be a Symbol.hasInstance remains eligible. The normal runtime protocol checks still apply.
For instanceof, a participating right operand also contributes its effective declared Symbol.hasInstance contract. A
A nominal impossible-test judgment for instanceof likewise requires establishing the actual right operand and the stability of its selected protocol at the test. Apply the same evidence requirements to local declarations, immutable aliases, imports, namespace accesses and re-exports; neither local syntax nor an immutable import binding freezes the
Place effects at their actual operations and possible invocations. A deferred body does not execute merely because it is declared, but later initialization can precede its invocation, and repeated activations or unknown external callers can invalidate an earlier protocol observation. In particular, the dependency graph of an exported API does not establish the absence of effects in all of its future consumers. A receiving contract does not establish the absence of effects while admitting its source; positive evidence that an admission preserves an already established member remains separate from evidence about construction and
For the nullish-hook fallback, it is a Symbol.hasInstance contract is proved nullish and a
For a numeric operator whose operand Static Types include a union, the same-type and family-availability judgments below are negative proofs over the operand alternatives. It is a
The
&&, ||, or ??, the &&, the ||, and, for ??, the result of NarrowFrom (null | undefined; where that part has no members, the +, the built-in operation is established, and either operand is known to contain only String values, the The concatenation result judgment reads String category evidence through string is the type of a + to addition nor parses the String. No getter, conversion or operator body is evaluated to obtain this fact, and the operand's actual type, metadata, participation and reference permissions are unchanged.
A selected declared class operator or primitive-block definition retains its result contract before the built-in concatenation judgment. An open left operand may carry a declared operator whose result is not String. Failure to select a primitive block for an entire compound operand contract does not establish absence of dispatch for each member. Preserve an established selected result; unresolved dispatch or result alternatives do not justify replacing that result with string. This judgment does not change overload admission, evaluation order,
The two exceptions in that step are duals, and between them they keep the obj && f() is the type of f(), since no object value is falsy and the left cannot be produced, while undefined && f() is undefined, since the right is never evaluated at all.
The first step is where the short-circuit operators part company with the rest: they produce one of their OPERANDS rather than a value of a common type, so the step that refuses two unlike types does not reach them and a && a < 10 is a uint32 or a let c: uint32 = x || 10 puts the 10 at uint32 by literal propagation exactly as let c: uint32 = 10 does. A literal that is a member of a union reaches its target only this way, since assignability compares the union as a whole.
An untyped numeric literal at a union is adapted toward the member it FITS, by the rule let _v_: string | uint8 = 1 gives _v_ the uint8 member, and { x: 1 } at { x: int32 } | { x: uint8 } gives its x the same member the scalar position would.
The members are
The members need not differ in a NUMERIC type, and the difference need not be at the literal's own level. Where they differ in an object member, an array member, a tuple member, or in a member of one of those in turn, the literal is adapted toward the first member it fits, by the same rule. { p: { x: 1 } } at { p: { x: int32 } } | { p: { x: string } } reaches the first, and so does { p: [1] } at { p: []<int32> } | { p: []<string> }.
Whether a literal fits a member is decided by ATTEMPTING the adaptation and observing whether it reports, not by comparing types. By the time a member must be chosen the literal has a type in which the values are gone - { p: { x: 1 } } is { p: { x: number } } - so a question about range, which decides { p: { x: 70000 } } against { p: { x: uint8 } }, cannot be asked of it.
An attempt that reports nothing is a fit whatever it returns. An ARRAY literal adapted against its target returns no type at all -
An attempt that reports is discarded together with anything it recorded, so a member that does not fit leaves nothing behind for the members after it.
An INTERSECTION is not this case: its members must ALL be satisfied, so members that disagree admit nothing and the literal is refused.
The last step is the substance of it: an operator on operands of one type yields that type, and never a wider one. What follows from that, and is not implied by a type's name, is collected here.
| Family | Arithmetic + - * / % ** | Shifts and bitwise | Relational and equality | Unary - |
|---|---|---|---|---|
the operand type, wrapping (/ truncates toward zero | the operand type | the operand type | ||
| binary floating-point | the operand type | not defined | the operand type | |
| decimal floating-point | the operand type | not defined | the operand type | |
| rational | the operand type; % not defined, ** for an | not defined | the operand type | |
| complex | the operand type; % not defined | not defined | equality only; the complex numbers are not | the operand type |
| vector | the operand type, at each lane | the operand type where the lane type is an | the three forms of | the operand type |
bigint | bigint; does not combine with a Number | bigint | bigint | |
string | + concatenates; the others are the existing Number coercion | the existing Number coercion | the existing Number coercion | |
| the | the | the | ||
| the return type of the declared operator, or the existing rules where none is declared | as declared | as declared | ||
| as its referent's type: an operand is read through the reference | as its referent's type | as its referent's type | as its referent's type |
Three results in the table are worth stating in words, being the ones a type's name does not suggest.
An 7 / 2 at uint8 is 3 and 1 - 3 at uint8 is 254.
A one-bit 1 + 1 at boolean1 is 0. The name suggests a Boolean, but the family is
A relational or equality operator yields a
The falsy part of a type is the union of the members of that type that have a falsy value, and the truthy part is the union of the members that have a truthy value. A member contributes to a part as follows, and where a member contributes to neither the part is a union of no members, the never type.
| Member | ||
|---|---|---|
| A | The member, where its value is falsy. | The member, where its value is truthy. |
undefined, null | The member. | Nothing. |
boolean | The literal type of | The literal type of |
A numeric type, string, or bigint | The member. | The member. |
symbol, type, an | Nothing. | The member. |
| An | The literal types over the | The literal types over the |
void | Nothing. | Nothing. |
A numeric type, string, and bigint contribute the whole member to both parts, because each has both falsy and truthy values and the falsy one has no type of its own to name: a uint32 zero is a value of uint32, and there is no narrower type this specification can write for it. Naming the member is the widening direction, and a wider boolean, may do so.
These parts are what make the common idioms type as written. s || "anon" at a string is a string, since the string is string and both operands are then of one type. x ?? 10 where x is uint32 | undefined is a uint32, since NarrowFrom removes the absent member. obj && f() is the type of f(), since no object value is falsy and the left contributes nothing. And a && a < 10 where a is a uint32 is uint32 | boolean rather than boolean, which is the honest statement: the expression evaluates to a where a is zero. A program that wants the Boolean writes !!(a && a < 10).
For an
T::divide returns the exact quotient truncated toward zero. T::remainder returns the remainder whose sign follows the dividend, unchanged in meaning from the
It is a / or % is a known integer operand whose value is zero and the operands are of an bigint type, and a known negative exponent of a bigint left operand of ** is likewise a bigint shift distance is not judged, as a negative distance is a shift the other way. A const bound to a constant expression over literals and such constants (const of an uint8(256) is therefore the value 0, and (-1 := uint8) the value 255.
The shift operators take their distance as written: x << k is the exact x × 2*
The distance rule is a consequence of the reduction rule above rather than an addition to it, and is stated because three implementations of one operator disagreed about it while that rule sat above them. x << k and x × 2*
A negative distance throws for the reason a zero divisor does: it names no result. Treating it as an exact power would make the answer depend on the operand, since 2*
Truncating toward zero and taking the remainder's sign from the dividend are one decision rather than two, because the identity (a / b) * b + a % b = a holds only when the quotient and the remainder round the same way. Truncated division pairs with a dividend-signed remainder and floored division with a divisor-signed one; mixing them produces an arithmetic that does not add back up whenever the dividend is negative and the remainder is not zero, which is two of the four sign combinations. The floored pair is available under its own name rather than by redefining the operators, as Math.divFloor and Math.mod in
That an int32 has nowhere to put 1.5, so the result type decides the answer, and this is the one operator whose meaning the type changes. A floored % would be a choice, since both answers fit the type, which is why the operator keeps the meaning it has for a Number.
Division by zero throws because an float32(1) / 0 is an infinity and float32(1) % 0 is a NaN, because a float has somewhere to put them.
The literal-zero rule matches the treatment of a literal that does not fit its type (bigint, where 1n / 0n continues to throw a
The negative update/store proof includes an established numeric read with a callable, structural object, array, tuple or nominal destination that cannot receive the updated value. Determine the actual setter or reference-location write contract, including applicable inbound conversions and declared operator precedence. Unknown or viable alternatives prevent rejection. This is a conversion-and-store judgment, not ordinary assignment compatibility: the existing numeric-to-String update
Unary - applies T::unaryMinus for the numeric type T of its operand. For an unsigned
For a built-in prefix or postfix increment or decrement, it is a
For example, a boolean location cannot receive a built-in increment's numeric result, a symbol value cannot undergo its numeric conversion, and a location of literal type 1 cannot receive 2. A location of 1 | 2 can increment from 1; it is not rejected solely because incrementing 2 fails. Number, BigInt and the
Unary + returns its operand unchanged when the operand is a value of a numeric type of this
After applicable user-defined operator dispatch, it is a + to an operand whose participating - or ~ to one establishing only Symbol values. Participation here is an operand contract: an annotation, a declared member, a declared or
Knowledge of an ordinary JavaScript literal or intrinsic result is not such participation: +1n, +Symbol(), +BigInt(1), and aliases or patterns derived solely from those untyped values retain their runtime exception timing. An ordinary unannotated mutable binding does not acquire a permanent contract from its initializer. A rejected
Unary minus on an unsigned value is defined rather than rejected because the alternative is worse: an unsigned type is a bit pattern as much as a count, and -x on it is the two's complement negation the hardware computes. Casting to the signed type first is usually clearer, and a program that wants the error uses the checked form.
Unary + is a decision, and it is recorded here because it splits from BigInt. +x throws for a BigInt because +x is the idiom for coercing to a Number, and returning a BigInt from it would silently defeat that idiom. The same argument applies to a uint8, and would say +x should throw for every type this operator+() is an overloadable unary operator on a class in the + already means more than +a on a uint8 is a uint8. This clause follows the +x a
=== and !== are not overloadable and retain their identity semantics. What identity means follows from the type. The identity of a ===. The identity of a uint8 and a String, so two instances of a === exactly when their fields are, compared
== between two values of the same numeric type applies T::equal. Between two values of different numeric types it is
The relational operators apply T::lessThan through
That === compares a Vector2 instances holding the same coordinates are the same value, and a Map keyed on them has one entry. This is the existing rule for a uint8 and a String applied to an aggregate, not a new one.
== returning == and != between two uint8(1) == uint16(1) is uint8(1) + uint16(1) remains a compile-time
A class may declare an operator, in which case the receiver is the left operand and the declaration's parameter is the right. Where an operator's operands are of a type that declares it, the declaration is applied in place of the steps above. The overloadable operators are the arithmetic, bitwise, shift, relational, and logical operators, their compound assignment forms, the increment and decrement operators in prefix and postfix form, the unary operators, the index accessors, and the conversion operator of
A COMPOUND ASSIGNMENT form that a class declares is invoked as a method call on the left operand, and THE ASSIGNMENT TARGET IS NOT WRITTEN: the value of _a_ op= _b_ is whatever the declared operator returns, and the binding, const binding.
Where the class declares the BINARY operator and not the compound form, _a_ op= _b_ is _a_ = _a_ op _b_ as it is today, and the target is written — so that form does not apply to a const binding. Which of the two a program gets is a consequence of what its class declares.
A declared compound operator exists to update its receiver in place, which is what makes it worth declaring beside the binary form; writing the returned value back over the target would make the form a reassignment and defeat that. It matters most for a const precisely because the value is updated through the operator rather than replaced — so writing the target back made the form unavailable exactly where it was meant to be used. Returning
It is a Syntax Error if an ++ or -- declares a parameter, if one of + or - declares more than one, or if any declares a rest parameter; a static declaration's receiver is the class object, so it takes the same count. A declaration whose count fits no form of its token is one no use could select, and it is refused where it is written rather than at a use as a missing argument.
=== and !== are not overloadable. An operator that the operand's type already defines through uint8 addition. An operator a built-in type does not define may be declared for it, which is how the SIMD extension gives the vector types their comparisons and how the primitive metadata extension declares operators on a parameterized primitive.
A relational operator that the class does NOT declare is derived from < where < is declared: _a_ > _b_ is _b_ < _a_, _a_ <= _b_ is the negation of _b_ < _a_, and _a_ >= _b_ is the negation of _a_ < _b_. The result of the declared operator is coerced with < and > uses each as written.
Without this an undeclared relational operator reached the abstract relational comparison, which coerces both operands to "[object Object]" and compares that String with itself - making > always <= and >= always <. Half of those answers are correct by coincidence, which is why only the reversed operand order reveals it: _M_(2) > _M_(1) answered
Deriving <= as the negation of _b_ < _a_ assumes a TOTAL order. Where neither _a_ < _b_ nor _b_ < _a_ holds - as for a <= answers <= itself, which the precedence rule above permits. Partiality cannot be detected from < alone, so this is a caution to the author of such a type rather than a rule the implementation can enforce.
An index accessor is applied by a computed member access rather than by an operator between two operands. A computed access may supply more than one index, grid[x, y], which is how the [i] and [x, y] and each is reached by the access that matches it.
Where a class declares a read direction whose
A read-modify-write applies the accessor once per direction, as it does for a declared read and write pair: the read yields the value at the location and the write asks for the location again.
Where no accessor applies, a computed access supplying several indices has the meaning it has in the base language: the brackets hold one expression, the indices are its comma-separated operands, and the key is the value of the last. Every operand is evaluated, in order, whether or not an accessor applies, so which reading a given access takes is observable only in the value it produces and never in the effects it has. A parenthesized m[(a, b)] supplies one index, as it reads.
For an applicable index accessor, check each supplied index expression as an argument of its selected signature. Apply the corresponding parameter's m[(effect(), 2)] supplies one argument whose final literal can take the index parameter's type; it does not supply two arguments, adapt the discarded result of effect(), or make (effect(), n) adaptable where n is a stored Number. Read and write directions use their own selected parameter contracts. The assigned value and any reference returned by a read direction retain their separate store and liveness obligations.
Resolution among several declarations of one operator is overload resolution on the operand types, specified in
This clause defines the structure of a function type, the identity and subtyping of one, and the resolution of a call among several signatures declared for one name.
A Signature Record describes one callable signature. It has the fields of
| Value | Meaning | |
|---|---|---|
| [[Parameters]] | a | The parameters, in order. |
| [[Return]] | a | The declared return type, and any type because a signature declaring any and a signature declaring nothing are different signatures: identity is declared-only, so making them equal would report a function declared twice, and would leave |
| [[InferredReturn]] | a | The |
| [[ThisType]] | a | The declared this type; for a method, the self [[ThisType]] of |
| [[Narrows]] | a | The narrowings a call of this signature declares ( |
| [[TypeParameters]] | a | The type parameters the signature declares ( |
| [[Untyped]] | a Boolean |
A Type Parameter Record describes one declared type parameter, of a signature or of a generic declaration. It has the fields of
| Value | Meaning | |
|---|---|---|
| [[Name]] | a String | The declared name. Carried for named arguments and for reflection, and not part of the parameter's identity: two signatures that differ only in how they name a parameter are one signature. |
| [[Kind]] | ||
| [[Domain]] | a | The |
| [[Variadic]] | a Boolean | ... ( |
| [[Variance]] | The declared variance ( | |
| [[Arity]] | a non-negative | The count of _ holes of a |
| [[Constraint]] | a | The extends, or V: T = 0 at whatever T meant first. |
| [[Default]] | a | The |
| [[Declaration]] | a | The |
The [[Domain]], [[Constraint]] and [[Default]]
Two <T: type>(x: T) => T and <U: type>(x: U) => U are one type, and a constraint, a default, a modifier, or the order makes two.
A Parameter Record has a [[Name]] (a String), a [[Type]] (a ? or has a default), a [[Rest]]
A signature is identified by its parameter types and its return type, so two declarations of one name may differ in the return type alone (let, const or class declaration bound is not one, and is redeclared as it always was.
Stated for every goal because it was true of only one. A script's top-level function declarations are var-scoped, so a second was already admitted there; a module's and a block's are lexical, so the same two declarations were an
Where the signatures do NOT differ, the second declaration is the error this clause already gives — one signature written twice — rather than a redeclaration. The parser cannot tell the two apart, since it has no resolved types; the check that can runs after resolution, and reports at the second declaration either way.
An export of an overloaded name, named or default, exports the WHOLE
These operations complete the cases
The abstract operation SameFunctionType takes arguments s (a
Two generic signatures are the same up to renaming: the type parameters are identified position by position through assumptions, the mechanism the recursive cases already use, so a parameter of a and the parameter at the same position of b are one type for the comparison, and <T: type>(x: T) => T and <U: type>(x: U) => U are one function type. [[Name]] is never read. Kind, variadic marker, variance, arity, and the constraints and defaults are compared under the same identification, so <T: type extends A> and <T: type extends B> are two, as are <T: type> and <T: type = uint8>.
Signatures are compared position-wise, so the order in which a name's overloads are declared is part of the function type. Canonicalization does not reorder them, because there is no order over signatures that is both total and meaningful, and because the declaration order is already observable through the reflected signatures list.
The abstract operation EffectiveSignatureReturn takes argument signature (a
The publication rules of
This result is used on both sides of callable conformance, including the signature comparisons reached through interface satisfaction, inherited contracts, callbacks and function-valued results. A target with a
A
The abstract operation IsSignatureSubtype takes arguments a (a
undefined type.undefined type.Reference modes and narrowing guarantees participate in ordinary callable conformance, including assignments, callbacks, interface satisfaction and overrides. The untyped
Identical parameter contracts accept the same argument sequences even when a rest precedes a fixed tail. Parameter-list indices are not argument indices in such a sequence. The identical-parameter step establishes only parameter compatibility: receiver requirements, narrowing guarantees, reference modes and effective return covariance are still checked. It does not evaluate defaults, add a wrapper or change
Omission safety compares effective binding behaviour, not equality of optional flags and not the target's maximum arity. A source default, an undefined-admitting source, or a default supplied by the target crossing can make an omission safe. An optional target without a default cannot stand over a source that requires a value it may omit. Fixed positions after optional/defaulted positions retain their own obligations. The existing sequence-assignment rules govern rest distribution, and explicit reference markers retain their ordinary agreement and liveness checks. These are type-record judgments; no default expression or function body runs to decide them.
The four directions a generic signature can face follow one question: could a caller reading b be misled? A generic a is usable where a concrete b is required by INSTANTIATION — (uint8) => uint8 holds id.<uint8> — and the bindings the relation infers are the ones the crossing specializes at (<T: type>(x: T) => T promises every T and (x: uint8) => uint8 would return a uint8 for a string; the untyped <T: type>(x: T) => T satisfies <T: type extends Animal>(x: T) => T and not the reverse. The inference here never runs user code: it is the structural walk the checker already has, and the constraint check on an inferred binding is where on a's declaration is not consulted, because a function type has none; it is checked at the
A parameter and a [[ThisType]] are related by any is bidirectional at a boundary. any only as its target, which is the right rule for a subtype question and the wrong one here: it made a specific parameter refuse an any-(uint8) => void was not usable where (any) => any was declared although the mirror was, and gradual typing broke at the function boundary in one direction only. The two [[ThisType]] steps refuse both mismatches, which is what this is required, and one that has a [[ThisType]] is usable nowhere a this is absent. That is what makes adoption load-bearing rather than decorative — a non-arrow function reaching a position whose signature has a [[ThisType]] is compared as if it had adopted it (
A return type keeps any is required by any does not thereby satisfy a specific result promise.
Parameters are contravariant and the return is covariant, which is the standard rule and the only one that is sound: a function used where (uint8) => uint8 is required will be handed a uint8, so it must accept at least every uint8, and its result will be used as a uint8, so it must produce only uint8 values. A ref parameter is invariant because it is a location that is both read and written, so a mismatch in either direction is unsound; this is the parameter-level counterpart of the
A function that accepts fewer arguments than the target supplies is admitted, since ECMAScript already ignores extra arguments and rejecting it would make every callback that omits an unused parameter unusable. A void return is required of nothing, since a caller that has declared it will not use the result.
The function g() { return f(); }, which declares neither a parameter type nor a return type; but g PARTICIPATES in inference all the same, since a f's declared return, and it therefore publishes a return type. g would satisfy () => string while publishing uint32. The
An UNKNOWN return is required of nothing either, which is the last three steps. A signature reaches them when it declares no return type and publishes no inferred one, and any type publishes nothing, "since a function whose result is unknown is indistinguishable from one that never participated". A function that never participated has [[Untyped]] function f(x: uint8) { let v = 1; v = 5.5; return v; } publishes nothing because a reassigned binding cannot, and it satisfies (uint8) => uint8 exactly as function f(x) { ... } does.
This is not the same as declaring any. function f(x: uint8): any PROMISES nothing, and a promise of nothing does not satisfy a promise of uint8: IsSubtype(any, uint8) is any type - and an implementation that stored any for both would refuse every function whose body defeats inference, while also reporting the two as one signature declared twice.
This admission does not agree with the arity rule of f declared as function f(a: uint8) {} with two arguments selects no signature and is an error, because a signature is viable only for an argument list its arity accepts. But f is assignable to (uint8, string) => void by the rule above, and a call through a binding of that type reaches f with the second argument present and is not an error. The same call is therefore rejected when written directly and permitted through a function type. Either extra arguments are acceptable, in which case overload resolution should admit them and prefer the signature that uses the most, or they are not, in which case a function that ignores a parameter should not satisfy a type that supplies it and every such callback needs its
interface I { m(): number; }
class C implements I {
text: string = "s";
m() { return this.text; }
}
The m fails the interface's Number result contract at the checking boundary. Changing the interface result to string, string | number, any or void removes this result incompatibility. Removing the anchor and returning only an unannotated literal leaves the existing untyped admission in force.
function unused() {
function id<T: type>(x: T) { return x; }
const wide: (x: string) => (string | number) = id;
const ignored: (x: string) => void = id;
}
Parameter matching binds T to String. The T to a union or to void. Replacing the union target with number fails the result obligation.
The abstract operation IsFunctionSubtype takes arguments s (a
An overloaded function type is the conjunction of its signatures, so a function satisfies it by satisfying each one, possibly with a different signature for each. This is why the quantifiers run this way round: every signature the target promises must be met by some signature the source provides, and a source may provide more.
An overloaded name evaluates to a single name is the shared name, its length is the smallest parameter count among its signatures, and call, apply, and bind resolve by the same rules. Individual signatures are not addressable as properties; they are exposed by the function reflection of the decorators extension.
The abstract operation ResolveOverload takes arguments signatures (a
For viability, ranking and specificity, each argument's receiving contract is its assigned fixed parameter's type or its actual position within a rest. For a reference rest, retain the positional leaf type selected by the complete collected contract and the lexicographically greatest successful sequence assignment of
A reference receiving position requires the original reference
Overload selection does not evaluate value parameter initializers or create call, apply, or bind.
| Rank | The argument matches its parameter by |
|---|---|
| 1 | Being of exactly the parameter's type. |
| 2 | Satisfying a |
| 3 | Being an untyped literal that takes the parameter's type, |
| 4 | A user-defined conversion ( |
| 5 | Binding to a signature whose [[Untyped]] is |
Rank 2 separates two ways of matching that are otherwise
indistinguishable, and without it a pair of signatures a reader would
consider plainly
interface I { a: uint8 } and type O = { a: uint8 } as two signatures of one name, called with { a: 1 }.The argument has the O; it satisfies I by having the members, which is what an interface-
Rank 2 does not reach a class that DECLARES the interface: such a class
It is a any type the resolution is performed at run time on the argument's runtime type, and the same errors are thrown as a
Two signatures declared for one name must not be ambiguous for any argument list. A declaration that writes no return annotation declares the SAME return as another that writes none, so two declarations of one name that annotate the same parameter types and neither annotate a return are one signature written twice, and are a function r<T: type>(e: T) after function r<U: type>(e: U) redeclares, whatever the letters.
A generic signature is viable when its type parameters bind, from the call's explicit type arguments or by inference from its value arguments, and is then ranked as the instantiated signature it becomes; one whose parameters cannot be bound is not viable, as one whose arity does not fit is not. Where ranks tie, a concrete position is preferred to a type parameter and a fixed parameter to an element of a variadic one, so route(e: Click), route<T: type extends Event>(e: T), and route<...Es: [].<type> extends [].<Event>>(...es: Es) layer from most to least specific and route(click) selects the first: a declaration that names the type is the one its author meant for it.
Ranking by the worst match rather than by a sum keeps the rule from trading a bad match on one argument against a good one on another, which is where the ranking rules of C++ become hard to predict. A
An untyped function is a function f() {} and function f(a: uint8) {}, the call f(1) selects the f(1, 2) selects the untyped one, since the function f(): void {}, which makes [[Untyped]] f(1) an error.
The ambiguity rule reaches further than duplicate parameter lists. function f(a: uint8) {} and function f(a: uint8, b: string = 'b') {} are ambiguous, because the default makes the second viable for a single uint8 argument at the same rank; in contrast, function f(a: float32): void {} and function f(...a: [].<float32>): void {} are valid together. At the one-argument call their type ranks tie and the fixed position is more specific; other arities use the rest signature. A default or a rest parameter expands a signature into a family of arities, and it is the family that must not remain ambiguous after the same ranking and specificity judgments used at calls. Several rests multiply the family rather than changing the rule, and a signature with a rest of an untyped element collides with a great deal, which is a reason to type one.
An argument at a call name: AssignmentExpression, which supplies the value for the parameter of that name rather than for the parameter at that position. A named argument is a compact way to supply a later parameter while omitting earlier ones that have a default. The parameter names a signature carries (f(b: 1) supplies b and leaves a to its default, so such a declaration is well formed and needs no ordering rule.
The : and an : is a syntax error in the base language, so no existing call changes meaning, and the form is available only where the runtime types feature is enabled.
This is the first restatement of ref argument, by restating
A call is bound to the parameters of the selected signature by
For a named or object-spread call to an overload
After selecting an actual overload, invoke that member with its retained binding map and original argument contributions. Do not resolve the mapped list as a new positional overload call or redistribute its named groups by positional greediness. A named rest's selected count remains fixed even when an earlier rest could otherwise take its elements. Parameter initialization still performs the selected member's ordinary defaults, conversions, reference admission and liveness checks; retaining the assignment does not retain a proof that survives an intervening effect. Binding maps belong to their invocation and cannot be consumed by a nested or recursive call.
When a positional argument is a reference, argument distribution uses the receiving parameter's contract. A reference position requires a compatible borrowed location, with the invariant type compatibility and no-conversion rule of
The signature selected is the signature in view. Where the callee expression is a reference whose binding has a declared type that is a any, where the argument
This is what makes an interface of call signatures usable as a declaration: interface I { (string = '5', named: uint32) } and function f(a: I) { a(named: 10) } bind by the interface's names and fill its default, so a function (a, b) => b passed to f receives ('5', 10) whatever it calls its own parameters. The interface supplies data the caller uses to assemble the argument list; it supplies no behaviour, and the function called is always the value itself. A parameter's declared type is carried on its binding (
Where no declared callable signature is in view, the named parameter declaration of an intrinsic belongs to the actual function Object selected by ordinary evaluation of the callee. It is not obtained from the caller's property spelling, the receiver's inferred type, or the function's public name or length property. Aliasing or borrowing that function preserves its declaration; replacing a property selects the
The following declarations apply to the intrinsic Range methods and
| Intrinsic family | Method | Parameters |
|---|---|---|
| Range | contains | optional value |
| Range | intersect | other |
| Range | scale | factor |
| Range | step | optional by |
| Range and | map, filter, flatMap, forEach, some, every, find | callback |
| Range and | reduce | callback, optional initial |
| Range and | take, drop | count |
The static member signatures expose these same names. The reverse, toArray and self-iteration methods accept no named parameters. This table supplies no declaration for an unrelated built-in, a protocol request method, or an asynchronous helper.
reduce naming only callback has no initial accumulator argument, whereas a call also naming initial with value
Argument binding adds no conversion to an inferred receiver-dependent type. After binding, perform the selected intrinsic's ordinary receiver, value and callback admission,
A rest parameter is filled by name as well as by position. A named argument whose name is a rest parameter's supplies a value of that rest, and the positional arguments that follow it join the same rest in the order written, so function f(a: uint8, b: string = '', ...args: [].<string>) {} called f(8, args: 'a', 'b') binds a to 8, leaves b its default, and gives args the elements 'a' and 'b'. Where a parameter after that rest must still be filled, the rest gives back what the parameters after it require, which is the same rule
The abstract operation SequenceAssignment takes arguments slots (a
This is leftmost-greedy matching with backtracking, which is what the
The result is deterministic. Because the search takes the greatest count first at each
The function f(...a: [].<uint32>, ...b: [].<uint32>, c: uint32) called f(0, 1, 2), the first b then takes the one remaining and c cannot be satisfied, b yields to none, and the assignment settles at a: « 0, 1 », b: « », c: 2. Where the types differ the filter does the work with no further rule: in function f(a: string, ...args: [].<uint32>, ...args2: [].<string>, callback: () => void), args stops at the first argument that is not a uint32.
The abstract operation ReferenceSequenceAdmits takes arguments t (a
A static use of admits applies the reference-argument judgment without reading storage; unresolved facts retain their existing deferred judgment. A runtime use requires a
The abstract operation BindArguments takes arguments sig (a
A named argument b an empty array rather than an error in the
A spread of an object, ...AssignmentExpression where the value is an object with no Symbol.iterator method, binds each of the object's own enumerable String-keyed properties by parameter name, as though each were written as a named argument. A spread whose value is
The spread protocol depends on the evaluated value, not on whether its expression is an object literal, an identifier, a property access or a call.
To collect named properties, call the spread value's [[OwnPropertyKeys]] once. Visit those keys in order. Skip Symbol keys without reading their descriptors or values. For each String key, call [[GetOwnProperty]] at that point; if the descriptor is present and enumerable, perform
These evaluation rules apply to ordinary calls, optional calls that proceed, construction and super calls. An optional call that short-circuits evaluates no arguments. Subject to earlier checking errors, argument evaluation completes before the ordinary runtime callability or constructibility check, and binding uses only the resulting contributions. Abrupt argument evaluation stops later arguments and parameter initialization. Iteration retains the base language's
Where the called value is overloaded (
Named arguments do not change a signature's identity: a parameter's name is carried but is not compared (
Because a signature is identified by its return type as well as its parameter types, two signatures may differ only in their return. The return type does not participate in ranking; it
The
Given function f(): uint32 and function f(): string, the call f() alone is an error, const a: string = f(); selects the second, and g(f()) selects the first where g takes a uint32, because the parameter supplies the h(f()) is an error where h is itself overloaded on uint8 and string, since an argument of a call with more than one applicable signature has no
Return-type overloading exists for the operators. A SIMD comparison returns either a vector of lane results or a compact operator<(v: int32x4): int32x4 and operator<(v: int32x4): boolean8 are the same signature but for the return. const a: int32x4 = x == y; and const b: boolean8 = x == y; select between them, and the comparison written with no
| Type | Rank |
|---|---|
number | Ahead of float64, and so ahead of every other numeric type. |
int.<N> and uint.<N> for an N that is not a named shorthand | Among the widths of their family, in the same order: a narrower width after a wider one. uint.<24> ranks after uint32 and before uint16. |
rational.<N> | After every |
complex.<T> and vector.<T, N> | Not ranked. A literal does not take either type, so no signature is selected for a literal argument by them. |
Ranking number first is the repair float64 and justifies it on the ground that Number is a float64 and the conversion is exact, which is the argument for ranking number itself first: it is the type a literal takes with no f(a: number) against f(a: float32) has no answer for f(1). This changes which function a call selects and so was a supplied repair when first recorded and is now load-bearing: number ranks first, so the ranking stands as a back-compatibility condition rather than a preference.
Placing the rationals after the
An arrow function's parameters may carry annotations and its body may carry a return annotation. The annotations occupy the positions
When the production
(x: uint8): uint8 => x * x is therefore an arrow function of one uint8 parameter returning a uint8. The return annotation sits between the parameters and the =>, which is the only position available, and it is what distinguishes an annotated arrow from a function type: (uint8) => uint8 in a type position is a type, while (x: uint8): uint8 => x * x in an expression position is a function. An arrow function is not overloadable, since it has no name to overload.
Every function-shaped production takes the return annotation the same way: a
A method may declare type parameters, as a function declaration may. They are in scope in the method's own signature and body, and a method of a generic class also reads that class's parameters there; where a method declares a parameter of the same name as one of the class's, the method's is the one its body reads. The arguments are supplied at the call, explicitly with . < > or inferred from the call's arguments, by the rules of
An accessor may not declare type parameters. A getter is never written as a call and takes no arguments, so a parameter it declared could be neither supplied nor inferred; a setter could infer one from the assigned value, but parameterizing one half of a property and not the other would leave the two halves of one construct with different rules. A method serves where a parameterized accessor would, and states which type it is applied to at the
A set accessor declares no return type, so its production is restated unchanged; its single parameter is annotated through async function's annotation types the promise the call returns, and a generator's annotation types the values the T names Generator.<T, ~void~, ~void~>, so a bare annotation types the yields and leaves the generator returning nothing, and a return with a value is refused unless the full Generator.<Y, R, N> is written. An async function's annotation is NOT shorthand in the same way and must be a promise type: the generator form fills its remaining arguments with async function to declare a return type that is not a promise type.
The async function f(): Promise.<uint8, Error> and
This clause defines the nominal and structural types a program declares: the object type and its identity and subtyping, which complete the last cases
It is a
The annotation in { (x: uint8): 1 } declares the own property's storage type and supplies the Composite.
At evaluation the
For an ordinary own property with an established declared storage type,
A Property Type Record describes one member of an
An Index Signature Record has a [[KeyType]] and a [[ValueType]], each a keyof (
The abstract operation SameObjectType takes arguments s (an
Members are matched by key rather than by position, so the order in which an object type lists them is not part of its identity and { x: float32, y: float32 } and { y: float32, x: float32 } are one type. Canonicalization orders the [[Properties]] of an
The abstract operation IsObjectSubtype takes arguments s (an
A required target property needs independent evidence of presence in the source. An index signature constrains values at matching keys; it does not provide a missing named property, whether the key
An object type is subtyped in width, so a type with more members is a subtype of one with fewer: an object that has everything { x: float32 } requires is usable where that type is required, whatever else it has.
An index signature is { [key: string]: uint32 } is the type whose remaining string-keyed properties, beyond those declared, are uint32, so a type whose every such property fits is one of its values. This is what a boundary already does with a VALUE (
The
A property t declares by name is judged by the property loop above and is skipped here, so a named member whose type differs from the signature's is t's own business.
It is subtyped in depth only through a readonly member. A readonly member is covariant, since a value read from it and never written through it need only be of the required type. A writable member is invariant, because covariance there is unsound: were { x: Meter } a subtype of { x: float32 }, a bare float32 could be written through the supertype view into a Meter. This is the same rule the
A boundary that accepts a value into a position of an any type.
This answers the question { x: uint8 } position silently retypes the object for every other holder of it, which is an action at a distance no other boundary performs, and would make the same object have different property types depending on where it had been. The consequence is that a check on a structural type is a statement about the value at the moment it crossed, not a guarantee about it afterwards, and a later write through an untyped alias can invalidate it. A program that wants the guarantee declares the property's type with the { (x: uint8): 1 } form of the object typing section, or uses a class, where the
A narrowing of a property, as in if (o.f instanceof T), follows from the same fact and is admitted only where the property cannot change between the test and the use: a readonly o.f may be a getter or may be assigned in between, and
An imported class retains the contracts established in its declaration's lexical context, including instance and static members,
An established superclass void or
An effective own instance
The accessor-pair declaration rule applies within each effective public, private, instance and static descriptor, even if no expression reads or writes it. The setter must accept every value of the getter's effective return type. Preserve private-name
A paired object-literal accessor obeys the same declaration rule as a class accessor: its setter parameter type must accept every value of its getter's effective return type. The effective return is declared where annotated and
Within a static member or static initialization block, this has the
It is a new also applies here, including its lexical identity, alias and invalidation rules. A function type alone does not establish constructibility or its absence. prototype member proves the subsequent prototype stage invalid: its type admits neither Object nor instanceof. The query executes no getter or Proxy trap and changes no runtime lookup, reference-liveness or storage check. Ordinary untyped JavaScript heritage retains its runtime checks, and this judgment adds no inheritance-cycle validation.
A class is typed when at least one of its public or private fields, declared or inherited, carries a type annotation. A class expression bound to an immutable binding, or written inline where it is extended or constructed, is an established class: it is resolved as a heritage base as a class declaration is, and the new of it is its instance type. Inherited counts because a subclass is a subtype: a dynamic modifier disqualifies a subclass exactly as it does a base. A dynamic is not sealed and is not a
A
Repeated readonly and layout controls) to disagree. Type metadata
An established
It is a readonly permission and the
This clause once said that a T | null is a reference, which read against A | null holding an A is reached by both sentences: this one calls it a reference, and the copying rule copies into a
What a store does is copy. A === compares fields rather than references, and the value cannot be held weakly (
An instance of a typeof reports typeof sees what it saw before.
IDENTITY is the other half, and it does not follow typeof. An instance of a WeakMap or WeakSet key, the target of a WeakRef, or registered with a FinalizationRegistry (typeof reports what KIND of value it is, and a weak reference asks whether the value is one a collector can distinguish from a copy of itself.
What value semantics does reach is EQUALITY. Two instances of one ===, Object.is and
It is a Syntax Error if the
It is a Syntax Error if a this. Bare return; and return this are the control-flow forms and remain legal.
That the class is the type a construction yields is a claim about run-time behaviour, and the rule above is what makes it true. Without it JavaScript makes it false: [[Construct]] returns the new C() need not be a C, a declared
A refusal rather than a check, for two reasons. A base class's returning return receives an object with neither its fields nor its prototype — and whether the base's returned object is assignable to a subclass is not knowable at the base, so no local check reaches the offence. And
The exemption is on the operand's form, not on what it evaluates to: return _cond_ ? this : this is refused, because admitting it would require deciding return _cond_ ? this : _other_, which is the reasoning this rule exists to avoid. A primitive operand is refused for the same reason, though [[Construct]] would discard it. The
Scoped to a
A
=== on two instances of a === on two instances of a class that is not a
A method a class declares whose parameter types are the same as an inherited signature's OVERRIDES that signature rather than overloading it. It is a f(): B may stand over f(): A where B is a subclass of A (a covariant return), but may not change it, since a return that only converts to the inherited one, or is unrelated to it, would make b.f(0) mean two things for one receiver and one argument list. The signature is compared by parameter types alone, which is what a dispatch
An override must also accept every argument sequence admitted by the inherited signature under ordinary parameter binding. Omitted arguments, optional parameters, defaults, rest parameters and reference modes participate in this comparison. The selected derived method evaluates its own defaults; it does not inherit a default expression merely because a base-view call omitted an argument. A derived required parameter therefore cannot replace an omittable inherited parameter unless its boundary admits omission. This is a compatibility obligation after identifying an override by parameter types, not a change to override identity. Apply the obligation again when
A readonly, a modifier written before the static where both appear. A readonly readonly is shallow: it fixes the binding, not the object the readonly
It is a
An effective accessor with a setter and no getter has the readable undefined. Its write contract remains the setter parameter type, or
A member marked protected is accessible only within its declaring class and subclasses of that class. It is a
It is a
Performing frozen on an instance of a Object.freeze does, makes every readonly at run time in addition to making its properties non-writable. A subsequent assignment to any
That a let d: [10].<A>; must hold ten zero-filled instances for the memory layout extension's contiguous array to exist at all, and a conversion from a matching object literal, { ... } := Vector3, is specified to fill the layout directly and run no
A class may declare more than one constructor. The declarations are overloads of one construct signature
The base language makes a second constructor in a
The annotation is what admits the constructor is a Syntax Error, so a program with no types in it is unaffected in either direction.
A signature is identified by its parameter types (
Reported at the class rather than at each construction because the mistake is in the class. Deferring it would name a fault at every new and at none of them — the
Every one-parameter overload is a converting
A VALUE TYPE class is not special here. Its default is built
Assigning a value of a
The positions at which this occurs are: initializing a binding from a value of such a type, assigning to one, storing into a _e_ in let _e_: _V_ = _arr_[0] holds a copy and writing to it does not disturb the element.
CONSTRUCTION does not copy. A return of a newly constructed value build their result directly in the destination — a binding, a return { … } := _Matrix4_ writes the caller's storage in place, Matrix4 being a class that DECLARES a conversion from such a literal (
A field in this clause is a class
A SUBCLASS OF A VALUE TYPE CLASS IS NOT ASSIGNABLE TO ITS BASE. Where Sub extends Base, Base is a return. The base's storage is _Base_.byteLength wide and the subclass does not fit, so the store could only keep part of the value; refusing it is what keeps a position of type Base holding a Base.
Three things follow, and each is deliberate. A subclass that declares NO storage of its own has its base's layout exactly, so storing one loses nothing and is permitted. A REFERENCE TYPE base — a reference, sealed or abstract class (_v_ is _Base_ and _v_ instanceof _Base_ both answer _T_ extends _Base_ instantiates at Sub carrying the whole value.
The conversion _v_ := _Base_ (
A copy let _b_: _V_ = _a_ and let _b_ = _a_ therefore copy alike, and _b_ = _a_ copies whether or not _b_ was declared with a type.
An ARRAY LITERAL at a const _arr_: [1].<P> = [_a_] and _arr_[0] = _a_ are one operation written two ways and hold a copy alike. The elision above does not reach this. It exempts an object-literal CONVERSION — a literal BECOMING a value of a { … } := _Matrix4_ does — and not a literal whose ELEMENTS are values of one; reading it the second way would elide the store this clause requires. A literal of CONSTRUCTIONS is unaffected, each element building in place.
In particular the copy is NOT taken at a type boundary. A boundary is a check
Copying a storage-backed value retains the source location's liveness requirements before reading its layout, including when no
A ref binding (
A method's
The last rule is what makes a mutating method possible at all: were _m_.bump() would compute a new value and discard it. C# reaches the same answer for a receiver that is a variable. Rust requires the receiver to be marked &mut self and this specification has no such marker, so the rule is stated rather than inferred — and the compound assignment forms of
A copy running no
The copy carries the class's PRIVATE fields as well as its public ones, for the reason the default does: a private === exactly when their fields are, compared
Copying is a property of ASSIGNMENT rather than of conversion, and the distinction is load-bearing for an implementation. A check inserted at a boundary (
A class may declare the value its bindings hold before assignment, in place of the one default:
The class's declared zero is that
The
It is a
The restriction is what keeps a declaration cheap. let d: [1000].<Transform>; takes Transform's zero a thousand times; evaluating a user expression that many times would turn a fill into a loop of calls, where copying one snapshot is the fill an engine would have emitted anyway.
An interface cannot declare a zero, because it has no static member to declare it in. A binding of an interface type with no default remains a
An abstract obligation identifies an instance
A class may be declared abstract, sealed, or dynamic. The productions below restate their ECMAScript definitions with the modifier list (sealed is specified by dynamic by sealed and dynamic are declared together.
A member is abstract because it has no body; the abstract static member has no override constructor is not implemented by a subclass but called by one through super(), and a class that cannot be constructed is what abstract on the class already declares; and an async or generator method's contract is written as an ordinary abstract method returning the type it produces, abstract m(): Promise.<T> rather than abstract async m(): T, so that a body's get or set accessor is an abstract member: it has an override
An abstract method is a signature with no body, and its annotation types the implementations: it is a abstract, whether or not the abstract abstract leaves an inherited abstract method unimplemented. Where the new C(…) is an abstract class it is a super() from a concrete subclass, arriving with a concrete NewTarget, runs the abstract
The direct-instantiation restriction follows a super() with a concrete NewTarget remain valid. Reassignment, shadowing,
An abstract class is never a
A source interface declaration introduces a nominal type at an actual lexical binding instance, not at a
The member contract is formed using the lexical and generic inputs belonging to this declaration activation under implements contracts retain the actual resolved interface identities, including through aliases and inherited class contracts. A symbolic checking record with no established binding identity is not a
Interface membership may still be structural, and structural aliases retain ordinary canonicalization. This identity rule changes neither the reference-liveness permissions of
Self-referential, mutually recursive and forward-referenced interface contracts are formed under
Call signatures are obligations of an implements clause. It is a call does not supply [[Call]], and the clause itself adds no call capability. An established callable construction result is checked by the ordinary signature relation. Function-derived construction with an unknown signature, unknown heritage, and
Resolve each implements target by ordinary lexical type resolution, including transparent aliases and known imported identities. The resolved target must denote an interface; it is a
Publication of a generic class family is not an application of that family. For each implements target whose evaluation reads one of the family's own unbound parameters, defer evaluation of the entire target, including its interface head, until application supplies those inputs. Retain its declaration obligation without publishing a placeholder or symbolic interface as a completed runtime contract. Classify dependencies by lexical binding identity: a nested callable's own generic binder does not make the enclosing target open, an enclosing declaration's bound parameter does not bind a same-spelled parameter of the class, and an injected bare self type includes the class's own parameters. A closed target in the same clause retains its ordinary declaration and availability checks. Already decidable static errors remain errors at their existing phase.
Publication of a generic family does not finalize the member implements target is closed. Retain a missing-member or missing-operator obligation until application completion; a partial registered later, including by another module, may supply it. A candidate partial need not occur in the family's source. This does not defer the target's existing kind, argument or required availability checks, and an established declaration incompatibility that no legal
A symbolic checking contract retains each open implements target as a declaration obligation. Substitute the application's bound arguments when using that declared relationship in a nominal type judgment, including a destination, argument or result check. Dropping an open target is not a reason to reject an otherwise established relationship. This symbolic relationship neither supplies a class member nor completes runtime admission: the application must still resolve an interface target and verify its declarations before successful publication. It gives no relationship to an unrelated interface and grants no reference-liveness permission.
When an application resolves those targets, use the selected class body's
For a completed class application, resolve all retained targets and bind their arguments in source order, rejecting any target that does not denote an interface, before verifying member obligations in source order. Use the actual declarations contributed by the selected class body, its superclass chain, and applicable partial declarations, instantiated in their respective
Distinguish entries supplied only by interface
Use the actual String or Symbol keys produced by ordinary class definition evaluation, including applicable partial member declarations evaluated while the application is in progress.
The resolved target graph may be
A declared method satisfies a method obligation by the ordinary function-signature relation, including generic parameter binding, reference parameters, accepted argument lists, and declared or
For an imported interface, retain the resolved exporting implements target is a declaration obligation even when the class is never used. For a closed target, a known member contract does not establish availability of the original imported binding. Its required binding read precedes any deferral of application completion caused by a local class argument, as specified in
For a
Declaration-
Declaration-
An interface is declared with the productions below; implements clause (
A class's where clauses are judged together with them, at each call of the method and against the receiver's class arguments. A method's clauses are also judged under them at the method's declaration, in the order written after the class's, so a method clause the class's clauses make impossible, or make hold for every value, is an Early Error, as for a sequence of clauses on one declaration.
An interface's members are the interface Ordered<T: type> { operator<(other: T): boolean; } written with declaration-<…>; a type satisfies an operator member by declaring the operator. It is a
An interface declares a contract. Its type is a
A class that implements an interface cannot remove the contract: deleting a member the interface requires throws a
The declaration is on<U: type> satisfies on<T: type> and on<T: type, U: type> does not. A member the class may leave out, an optional one, is supplied by the interface.
An interface is nominal where a class declares that it implements one and structural where a value is checked against it, which are two questions rather than one contradiction: the first asks whether a declaration promised the contract, the second whether a value meets it. IsOfType decides the second by
The two questions divide by WHO ASKS, and the division decides a case that reads as a contradiction until it is named. A subtype question - is this type assignable to that one - is answered by implements and not by matching the IsOfType, which walks the members; a class instance that has them passes it whether or not the class declared anything.
So class Rich { m(): uint8 { … } } without an implements clause gives new Rich() is Small the answer let s: Small = new Rich() a Rich, and refuses the claim a declaration never made; the same value arriving where its type is not visible is checked and admitted. new Rich() := Small states the claim in one token where a program means it.
The alternative - a class satisfying any interface it happens to match - is what
The missing-first-initializer rule is checked directly for every
The any discriminant supplies no
The labels of an switch are tested in order, so a label naming an enumerator whose value an earlier label of the same switch names, itself or through another name sharing that value, can never succeed, and it is a default that
An
It is a const bound to such a reference, or within a function declaration the initializer calls. Any other reach is the enum D: decimal64 { A = 1.00 } holds the decimal 1.00 rather than a rounded double, and enum B: bigint { A = 1 } needs no n suffix. The
It is a enum A { X = 0 } and enum B { Y = 0 } declare different values that this rule does not reach.
The rule above is stated over values rather than over the form of an initializer. An initializer that constructs its value cannot conflict, but one that calls a function to construct it is equally sound, and no syntactic test separates the two. A declaration may therefore claim a value the program obtained elsewhere, including a shared or built-in one, and the effect of doing so is that
An : is the int32. Any type may be an symbol.
The first enumerator, when it has no initializer, takes 0, and it is a operator++ to the previous enumerator's value, with the previous enumerator itself unmodified; where the
The increment above is the bigint enumeration counts by 1n; an enumeration over a class whose declaration provides operator++ counts by calling it, with the previous enumerator unmodified; and a type declaring no prefix increment repeats, which is what gives B the value of A in enum S: string { A = "x", B }.
An
A switch whose discriminant is a value of an switch has no default, it is a default: the row of default its discriminant with every case's type removed, which is empty, so the default can never be taken (default after every atom of a match subject cannot (
An toString, which takes a VALUE and returns the key of the enumerator having it, or keys, values, and entries, each returning an @@iterator, which is entries; and underlying, an accessor giving the switch and match judgments read its value at compile time, and a reassignment or an added member would make those judgments wrong. A store to, or delete of, an enumerator property, and a store that would add a member, are type errors where the receiver's
toString takes the value as an argument rather than being a method on an enumerator, because an enumerator IS a value of the comp / 32 read directly, and it leaves an enumerator with no method of its own to override. String interpolation therefore sees the underlying value, and obtaining the key is what toString is for.
@@iterator is entries rather than values, the choice Map makes and the opposite of the one Array and Set make, and for their reason: what an enumeration holds is named values, so yielding a bare value discards the name that distinguishes an Count(_n_), so an index that repeated it would leave the enumeration with no way to be read in order. forEach, filter, and map are deliberately absent: entries composes with the Array methods to give all three, and this specification has declined composable surface before.
The one-way rule is the rule of C#, Rust, and Swift. It is what lets a bitmask index such as comp / 32 read directly while Component(id) remains a deliberate and validated step, and it is why an
An
underlying is an accessor rather than a method because it reports something the type IS, as family and the layout properties do, where keys and values perform an enumeration. It lives on the family on that node rather than a kind of its own. An engine that emitted a distinct node kind for an
Reflect.typeOf(Count.Zero) reports Count, by the rule of int32 follows from Count being a subtype of it, not from a second runtime type, so the uniqueness Reflect.typeOf of a Meter is float32.<{ m: 1 }> and not float32, while m instanceof float32 is nonetheless
What remains is that this specification now has three relations of one
That an enumerator reports its
A class declared sealed may not be extended outside the module that declares it, so its subclasses are a fixed and known
A sealed abstract class can be neither instantiated nor extended outside its module. Its concrete subclasses are therefore a closed switch over a value of its type is default in such a switch is then a
In a switch whose discriminant's case label is a class, and
This is the algebraic data type, assembled from parts the switch the control structures section already defines. What makes it work is that the
That a sealed class is a T | *null* reference, sealed or abstract class - or avoided by giving each concrete class its own array, which needs no polymorphism at all since [N].<A> and [N].<B> each know their own stride.
A class declared reference is a reference type. Its instances are held and passed by reference: assigning one aliases rather than copies it, === on two of them compares references rather than fields, a
A class declared sealed or abstract is a
Because a
Two things need this modifier, and neither is served by the reference types that already exist. A class that contains itself has no sealed is the only existing way to say so - which forbids the extension an open class exists to offer. And a reference (
The decision is made once at the declaration rather than at each use, which is what struct and class are in C# and in Swift. A per-use spelling, as Rust's Box<T>, &T and Rc<T> are, does not survive this specification's two commitments: an owned box would have to copy on store, since there is no move that leaves a source invalid (=== this specification defines as structural. Rust can offer the use-
The non-class-target judgment uses the actual lexical binding, not only declarations in the partial's statement list. An established ordinary-function or non-class value is a
A class declared partial re-opens a class that is already declared, whether earlier in the same program, in another module, or as an intrinsic, and adds methods and operators to it. A partial declaration names an existing class; it declares no binding of its own, and its members are merged into the named class so that its instances and the class itself gain them. An instance method is added to the class's prototype, a static method to the
The partial partial, is an error, so a program cannot fork a class's behaviour by an accidental re-declaration. It is a partial declaration names a value that is not a class.
A partial declaration adds behaviour and no cases: it introduces no subclass and no instance state, so it does not enlarge the closed partial declaration is therefore available from any module, including on a sealed class, which is the difference between it and a mixin: applying a mixin to a sealed class from outside its declaring module would create a subclass and is refused (partial extension adds no subclass and is allowed. A partial declaration over a CLASS does not re-open the partial declaration over an INTERFACE may add members, and adds no instance state by doing so, since an interface declares a
A partial declaration of a generic class or interface takes no partial class Box { get(): T { … } } over class Box<T: type> adds a member to every application. A partial class Box<uint8> contributes only to Box.<uint8>, and partial class Box<const E extends Numeric> contributes where the argument satisfies both the primary's bound and the
The target is resolved through the partial's lexical binding, including an imported binding or immutable class alias. A partial need not share the primary's statement list; the placement restriction on
Within a partial class body, including member signatures and nested function bodies, a bare type reference to the name written in the partial declaration
A method or function created in that partial context retains the receiving application for subsequent resolution of its self type, including in functions created by a later invocation. A caller's generic bindings, another evaluation of the same partial source, another application of the family, or reassignment of its source binding must not replace that context. A checking summary describes the target and its pattern without publishing an evaluated
Each partial is matched independently using the
An extension's source contract is available when its declaring module is loaded. Runtime registration and member installation retain ordinary declaration evaluation order. Matching registered contributions apply to existing and future applications of the same declaration and arguments. The
A module's top-level partial contributions participate in the available contract of their resolved primary declarations, including through imported names, renaming and re-exporting, whether or not the contributing module itself has imports. Preserve each
A statically established collision or incompatible member use is an early
The metadata channel of the decorators and metadata extension is the principal use: a program adds partial interface over it, which is how a type's reflected metadata is extended without owning the declaration that carries it.
Source contract summaries do not register or evaluate a partial class declaration. A nongeneric primary must satisfy its interface obligations when its
This clause defines what a program observes of a type at run time: the operation that reports a value's type, the opacity of a
This function performs the following steps when called:
Reflect.typeOf reports the most specific type of which the value is a value, not every type it belongs to. For a uint8 binding it returns uint8; for a value of the metadata-parameterized Meter it returns float32.<{ m: 1 }>, the full parameterization, which is how reflection obtains constraint information at run time. Membership in a less specific type is not a second runtime type but a consequence of subtyping, which is why m instanceof float32 is Reflect.typeOf(m) is not float32.
It is the companion of typeof, which is unchanged and reports the underlying language type: typeof a is uint8 binding while Reflect.typeOf(a) is uint8. typeof on a non-class typeof uint8 === "object" therefore doubles as feature detection for this
A Player | null no property yields its members, and given the one for [].<uint32> no property yields its
One exception is display: a toString method returning the canonical source form of the type it String on the 'a' | 'b' yields the String
A T(v). For a plain type the call is the explicit conversion of v := T. For an typeof, which reports
A typeof; accepting it as a type input does not classify every callable as a
Opacity is what lets the interning of [].<uint32> as a tag rather than as an object with an element property is conforming, and a program cannot tell. It also keeps the type-object surface from becoming a second, weaker reflection API alongside the real one: there is one way to inspect a type, and it is named.
The abstract operation ResolveReflectedType takes argument value (an
Use the association of the actual evaluated
Argument evaluation and Reflect.typeOf takes a value rather than a type denotation and continues to use
Reflect.getReflection.<Reflect.Type>(_t_) first obtains record as ? kind property whose value
kind | Further properties |
|---|---|
type, the generic, either base (an intrinsic declaration's String name or a nominal declaration arguments (a any, void, and type types. It also carries family, the String the family reports ( | |
value, a String, Number, Boolean, BigInt, or Symbol; base, a | |
base, a metadata, an object: the metadata half of a parameterization such as float32.<{ m: 1 }>. | |
members, a never type. | |
members, a | |
elements, a type (a rest, and initial. | |
element, a extent, a non-negative | |
properties, a name (a String or Symbol), type (a optional, readonly, and initial; indexSignatures, a key and value (Type Objects). | |
signatures, in declaration order, each a record with parameters (records with type, name, index, optional, rest, initial, and metadata), return (a record with type and metadata), thisType (a narrows (a target, a String or the String type, a metadata properties are always | |
target, a ref borrow type of |
There is no back-edge node. A reader walking a recursive type reaches the
The node kinds and the any, void, and type, share the one
A record's [[Origin]] (
The same Reflect.getReflection reflects a declaration's members, keyed on the declaration rather than on a type, which is how a class's fields, a function's parameters, and an
A union's arms are reflected in the canonical order rather than in the order written, since canonicalization has already reordered them and the
For a concrete intrinsic application listed in generic property contains base, the intrinsic declaration's String name, arguments, its complete normalized arguments, and parameters, a name, domain, and default where one is declared. A generic reconstruction with such a String base binds and validates arguments by Promise, retain their declaration Promise type applies its defaults and is distinct from that declaration base; the base can be obtained as Reflect.getReflection(type Promise).generic.base. Reconstructing a library application completes and validates its arguments by
A reflected function signature additionally has typeParameters, whose entries carry name, kind, variance, arity, variadic, constraint, and an opaque context preserving the declaration's parameter and default context. A family constraint is described by kind: "family-pattern" and application. An application entry has kind: "application", head (an intrinsic String name or nominal declaration arguments. A fixed entry has kind: "fixed" and type or value; a kind: "wildcard" and a non-negative index. Repeated indices impose equality. A deferred declaration default has kind: "declaration-default" and its opaque declaration context; it is evaluated only after the preceding entries bind. A default already reduced to a fixed entry or a repeated Reflect.makeType throws a
The validity judgment includes every nested tuple rest operand: after canonicalization it must denote an array or tuple, subject to the same deferred requirement for dependent operands as
The input is structural: any object matching a node's
For every Reflect.makeType(Reflect.getReflection.<Reflect.Type>(T)) is T. A class
Canonicalization is where invalidity is caught: a node describing an infinite value-type layout, or an index signature with an illegal key type, fails exactly as the equivalent declaration fails, with the same message, so a builder's misuse reads as the misuse it is. An intersection whose members are disjoint is NOT among them, and the asymmetry is deliberate rather than an oversight: the declaration is a never (& |; a constructed one is the result of a computation over members the caller did not enumerate, and the empty type is the answer such a computation must be able to reach. An earlier draft of this clause claimed the two failed alike, and named a declaration rule that this specification did not then state. Validity is not syntactic reachability: a symbol literal type is a valid type with no declaration form at all, which is why it is constructed rather than written; an index signature has the declaration form of String(Reflect.makeType(…)) prints the canonical form of
Its (source: type, target: type): boolean; both parameters accept the type denotations resolved by
This function performs the following steps when called:
This is the checker's assignability relation (Reflect.isAssignable(T, U) ? X : Y inside a builder. It is deliberately the assignability relation rather than a new one: mutual assignability is composed from it, and identity is === by interning. That the relation is the checker's has a consequence a builder must account for: the any type satisfies it in BOTH directions and the never type in one, so Reflect.isAssignable(T, string) is T of any and of never alike, and a conditional written on it takes the same branch for both as it does for a string. Mutual assignability inherits this — any is mutually assignable with every type — so === is the relation that discriminates them, and a builder that must distinguish any or never tests for it first. This is not a defect in the relation: any IS assignable to string, and a judgment that answered otherwise would no longer be the checker's. The function is
The existing reflective operations respect a declared type. Reflect.set on a property with a declared type t performs Reflect.get returns the property's value, which is of its declared type. Reflect.defineProperty accepts a type key in the property descriptor, whose value is a type and no value gives the property the default value of that type (Reflect.deleteProperty on a property with a declared type, and on a member required by an implemented interface, throws a
A Proxy constructed with a type argument T has a [[RuntimeType]] internal Reflect.typeOf reports T rather than the any type. Each trap result for a property with a declared type, whether the type is declared by T or carried by the target as a get and getOwnPropertyDescriptor traps carry a value of a member's declared type out of the proxy and the set and defineProperty traps carry one in, so each is checked against that member's type; the has, deleteProperty and ownKeys traps may not deny, remove, or omit a member T declares and does not mark optional; and where T is a apply and construct traps are checked against it. The new Proxy .<T> Proxy constructed without a type argument is any, so the static and reported answers agree. Proxy is among the built-ins on which an application may write type arguments, and a construction that writes one has the construct signature (target: T, handler: ProxyHandler.<T>), checked as a user class's constructor signatures are; a construction that writes none declares no parameters, since its result is any and the base language admits any handler object.
ProxyHandler.<T> is an interface of thirteen optional members, one per trap, each a function type over T: get and getOwnPropertyDescriptor returning what the base language does for the property named, set and defineProperty returning boolean, has, deleteProperty, setPrototypeOf, isExtensible and preventExtensions returning boolean, ownKeys and getPrototypeOf returning what the base language does, and apply and construct over T's signatures where T is callable. Every member is optional, since a handler declares the traps it means to intercept and the rest fall through to the target. A handler annotated with it, or passed inline to a construction that writes a type argument, is checked where it is written; the run-time checks at the trap boundary are made regardless, since a trap may return any and the annotation cannot see through it. The two are complements: one bounds what a proxy may do, the other says what its author has written. Constructing a Proxy whose target is an instance of a
Reflection is a boundary in the sense of Reflect.set is checked exactly as one entering through an assignment, which is what keeps a
A Proxy reporting the type it was constructed with rather than its target's
At a statically established intrinsic Proxy construction or Proxy.revocable operation, it is a dynamic subclass does not erase it, and reference identity supplies no Proxy exemption. Structural membership alone does not prove a physical layout. An unknown or ordinary-Object-compatible alternative prevents this negative proof. The target position must be established through the actual argument mapping.
The intrinsic-origin proof is a prerequisite, not an assumption based on spelling. Resolve lexical declarations and aliases and account for mutable global bindings, property primitive block may replace (
A proved intrinsic WeakRef construction checks the actual argument bound to target, independently of whether its result type was inferred or explicitly specialized. The argument must satisfy both T. Apply ordinary named and positional argument binding and established spread contributions. Unknown
FinalizationRegistry.<T> has callback: (heldValue: T) => void. Use the common library signature and ordinary contextual callback rules, including optional, defaulted and rest parameters. An established non-callable or incompatible callback is a callback. At an
An instance of a
Eligibility follows the effective class hierarchy. A subclass inherits a base's dynamic modifier on the subclass does not undo sealing contributed by a base. A reference class kind anywhere in the hierarchy supplies reference identity and retains the reference-kind exemption. Static checking uses the resolved ancestry for this judgment; an unknown base that could supply reference kind retains runtime checking. Thus an empty subclass of class Base { x: uint8; } is refused in weak positions, while a subclass of reference class Base { x: uint8; } is admitted.
A
The same judgment is made statically where the type is known. The type argument bound to the parameter that WeakMap, WeakSet or WeakRef holds weakly - K for WeakMap, T for the others - must be one every value of which object, symbol, any, an object, array, tuple or function type, a class that is not null, undefined, string, number, boolean, bigint, a WeakRef target and unregisterToken of FinalizationRegistry.prototype.register are checked the same way where their FinalizationRegistry's type parameter is its held value and is unconstrained. So are the token of FinalizationRegistry.prototype.unregister, a key given to WeakMap.prototype.set, and a value given to WeakSet.prototype.add, whether or not the receiver carries a type argument: the judgment is on the argument's
This clause defines the library the numeric types need: the overloading of the existing functions over those types, the named arithmetic forms that check or saturate rather than wrap, the floored division pair, the leading-zero count at the argument's own width, the parsing of a string into a numeric type, and the predicates that ask a numeric value about itself.
The functions of the Math object, and the other library functions that TAKE a value of a numeric type, are overloaded for the numeric types by
Separately from the listing, a library function that takes a Number as a QUANTITY - a length, a count, an index, a position, a width of indentation - accepts a value of any numeric type in that position and reads it as its value. Such a parameter is not overloaded: there is one behaviour and one result, and the type of the argument selects nothing. The rule is stated because the alternative is silent: an operation that decides a branch by asking whether its argument Array(3) allocates three elements while a Array(_t_) for a uint32 t of 3 would build a one-element array holding t. What a quantity parameter must not do is change what it answers for an existing call, so a Number continues to be read as it is today.
This is the reading side, and it does not create a conversion. An operation that REFUSES an implicit conversion from a Number refuses one from a value of a numeric type for the same reason and with the same error: uint32 as well, since BigInt u := bigint, and a boundary converts one exactly as it converts between any two numeric types (
The listing ranges over the sized int.<N> and uint.<N> for every N the type system admits, over bigint, over the binary float types of float128 and decimal rows are as normative as the rest. number is not in the
Every signature takes all of its numeric parameters at one type T. There are no mixed signatures and no resolution at a common supertype, because no numeric Math.max((1 := uint8), 3) selects the uint8 signature and is a uint8, while Math.max((1 := uint8), 300) is a uint8 argument leaves viable.
A signature's declared return type is a boundary, and the boundary applies its family's own treatment of a result that does not fit. For an bigint, the exact mathematical result is converted at T by the checked treatment of Math.pow((2 := uint8), (10 := uint8)) and Math.abs((-128 := int8)) throw, their results having no uint8 or int8 to be. For a binary float type the result is converted by the float rule of Math.exp((100 := float32)) is the float32 positive infinity rather than an error. For a decimal type, precision rounding is silent and a result outside the exponent range throws a int.<N> throws a Math.sqrt of a negative value of an Math.pow over an
The semantics are stated on Math.sqrt is correctly rounded at every binary float and decimal width, as float128 they carry none: every float128 value nearest the exact mathematical result, ties to even. A program chooses float128 for its precision, and a last bit that differs between implementations would give part of that precision back.
NaN and the infinities are values of every binary float type and of every decimal type, and a NaN or infinite result at those types is a value of T, not an error: Math.log((-1 := float32)) is the float32 NaN. The min and max and Math.round rounding half up, are unchanged and are evaluated at T.
A family has a row for a function exactly where the operation has an answer in that family's own mathematics, with the return type leaving nothing to decide about the answer's representation. The Math.sqrt and Math.cbrt return the exact root truncated toward zero as a value of T, the direction BigInt.sqrt and BigInt.cbrt. The transcendental functions have no
The listing declares one signature per const a: uint8 = Math.pow(2, 10); resolves at uint8, computes the exact 1024, and throws a uint8(Math.pow(2, 10)) is the untyped call followed by a wrapping cast and is 0, the distinction of float64(Math.sqrt(10n)) being the Math.sqrt(float64(10n)) the real root of the converted argument. A cast constrains nothing here: a
| Functions | bigint | Binary float types | Decimal types | Rational types | |
|---|---|---|---|---|---|
abs, sign | A value of T; abs of the most negative value of a signed type throws a | A value of the type | A value of T | A value of T | A value of the type; abs throws where the magnitude's numerator does not fit its int.<N> |
min, max | A value of T | A value of the type | A value of T | A value of T | A value of the type |
floor, ceil, round, trunc | The argument | The argument | A value of T, exact | A value of T, evaluated exactly; the result's cohort member follows the roundToIntegral rule of | The nearest int.<N> in the function's direction, checked at int.<N> |
sqrt, cbrt | The sqrt of a negative value throws a | The sqrt of a negative value throws a | A value of T; sqrt correctly rounded, cbrt | A value of T; sqrt correctly rounded, cbrt | No signature |
exp, expm1, log, log1p, log2, log10, sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanh | No signature | No signature | A value of T, | A value of T, | No signature |
atan2, hypot | No signature | No signature | A value of T, all arguments of one T, | A value of T, all arguments of one T, | No signature |
pow | The exact power, checked at T; a negative exponent throws a | A value of T, | A value of T, | No signature | |
clz32 | For N ≤ 32: 32 less the bit length of the | No signature | No signature | No signature | No signature |
clz | N less the bit length of the | No signature | No signature | No signature | No signature |
imul | For N ≤ 32: the product's low 32 bits as an int32, whatever T its arguments carry | No signature | No signature | No signature | No signature |
fround, f16round | No signature | No signature | The value rounded through binary32 or binary16, a value of T | No signature | No signature |
sumPrecise | No signature | No signature | For an | No signature | No signature |
The complex numbers extension declares the rows of its family beside its arithmetic: Math.abs of a complex.<T> is the real magnitude, a value of T; the transcendental functions the extension names return values of its types; and Math.conj and Math.arg are its additions. Math.random takes no numeric argument, and its Math.random.<T>() belongs to the random extension. The value properties of the Math object, Math.PI among them, remain values of the
The length property of an array is the capacity. The value READ is a value of that type and not a Number, so a.length is length
The a[i] — is the array's any type, which left indexing untyped in both directions of the check: a store to an element was checked against the let b: boolean = a[0] was accepted on an array of uint32. Indexing is the operation an array exists for, and it is checked in the same way whichever side of an assignment it appears on.
Where the array has a fixed extent and the index is a numeric literal, a numeric literal preceded by a sign, or a reference to a const bound to one, the index is decidable before the program runs: an index that is negative, not an
The bound on which functions are overloaded is a compatibility rule, and it is the same one that leaves parseInt and parseFloat alone (bigint predicate rows ('A'.charCodeAt(0) === 65 is uint16 result would make it indexOf and lastIndexOf, whose === -1 is the universal idiom, the Date getters, and the byte lengths. Where a
This is what makes the conversion rule livable rather than merely strict. Math.clz32 of a uint32 is a uint32, Math.min of two float32 values is a float32, and a uint32 loop counter compares against array.length without a cast, because the library meets the program where it is. A library that returned a Number from every function would force a conversion at every call and would make
The existing signatures are unchanged, so Math.min of two Numbers number first and so keeps an untyped call on the signature it selects today.
The bigint rows are those of the BigInt Math abs, sign, min, max, pow, sqrt, and cbrt, the roots truncated toward zero, sqrt of a negative throwing, and pow refusing a negative exponent. That Math.sqrt whose result type depends on a runtime argument is a hazard every caller inherits. The objection does not transfer: here a signature is selected statically and the untyped surface is untouched, so the listing stays on Math, agreeing with the BigInt.sqrt(x) and Math.sqrt of a bigint are one mathematics with two spellings.
The Math.sqrt((17 := uint8)) is (4 := uint8), and Math.cbrt of a negative
Math.pow and ** deliberately part ways at an (2 := uint8) ** (10 := uint8) wraps to 0, because the operators wrap (Math.pow((2 := uint8), (10 := uint8)) throws a a + 1 against Math.addChecked(a, 1) distinction of ** itself does with a negative
The operators wrap on overflow (
Each form performs the operation its corresponding operator performs, and differs from it only in what happens when the type cannot represent the answer. "The exact result" in / divides, truncating toward zero (Math.divChecked(_a_, 2) on an int32 holding 7 is 3 and not an error. A division is the only one of the four whose result can fail to be representable for a reason other than magnitude, and it does so in exactly one case, the most negative value of a signed type divided by -1.
That the operators wrap while a CONVERSION does not is the one place these rules look as though they disagree, so it is worth stating why they do not. A conversion is a value crossing into a type that cannot hold it, which is a mistake about the value: a store past an let z: uint8 = x + y with x 200 and y 100 the addition wraps to 44 and the conversion then has nothing to refuse, because the wrap happened at the operator and the value reaching the binding is in range. Where that is not wanted,
A zero divisor produces no result at all, so neither treatment applies to it: Math.divChecked and Math.divSaturating both throw a
| Form | Result |
|---|---|
Math.addChecked, Math.subChecked, Math.mulChecked, Math.divChecked | The exact result where the type can represent it. Otherwise a |
Math.addSaturating, Math.subSaturating, Math.mulSaturating, Math.divSaturating | The exact result where the type can represent it. Otherwise the nearest value of the type, which is its greatest value when the exact result exceeds it and its least when the exact result falls below it. A zero divisor is a |
Silent wrapping is the wrong answer when the value is a count rather than a bit pattern, and the right answer when it is a bit pattern rather than a count. Neither is right always, so the operator takes the case that has to be cheap and the named forms take the rest: a + 1 on a uint8 holding 255 is 0, Math.addChecked(a, 1) throws a Math.addSaturating(a, 1) is 255.
These forms exist for the
That a saturating form can still raise is worth stating plainly, because the name suggests otherwise. It raises for one reason only, and it is not an overflow: a division by zero has no result, and saturating chooses among the results a type can hold rather than inventing one where none exists. Every case that IS an overflow saturates, including the signed division that overflows, so Math.divSaturating of the most negative int8 by -1 is 127.
Math.divFloor(_a_, _b_) returns the exact quotient of a and b rounded toward negative infinity. Math.mod(_a_, _b_) returns the remainder whose sign follows the divisor. Both are overloaded for every
A zero divisor follows the family. At an Math.mod(_a_, 0) is _a_ % 0 is, and Math.divFloor(_a_, 0) is an infinity, as Math.floor(_a_ / 0) is.
These are the other half of the pair Math.divFloor(_a_, _b_) * _b_ + Math.mod(_a_, _b_) = _a_ holds for the same reason the operators' identity holds, because the quotient and the remainder round the same way. They are named rather than
Unlike the checked and saturating forms, this pair is not restricted to the array[Math.mod(_i_, _array_.length)].
Math.mod is the % of Python, the mod of Kotlin, and the mod of Haskell. For a positive divisor, which is every case in which an index is being wrapped, its result equals the Euclidean remainder and is never negative, so array[Math.mod(i, array.length)] is always in range. Where the divisor may be negative and a non-negative result is still wanted, Math.mod(_a_, Math.abs(_b_)) gives it, which is why no separate Euclidean pair is provided.
Math.clz is overloaded for the sized bigint has no width to count from the top of, and the float, decimal, and rational types have no Math.clz32 is already the 32-bit count.
This is Math.clz32 freed from its width. The two agree at 32 bits, answering alike for every int32 and uint32, and differ below it: Math.clz((1 := uint8)) is 7 where Math.clz32((1 := uint8)) is 31, because clz32 counts in a 32-bit clz counts in the argument's own. clz; clz32 remains for the 32-bit
The count of a value of T always fits an unsigned T, and fits a signed T at every width past int.<2>; at the two narrowest signed widths a zero's count exceeds the type's greatest value and the checked return throws a clz32 row of bigint is excluded because the operation is width-relative and a bigint has no width; the width-free counterpart is a bit length, which is a different function and is not added here.
A call whose argument is an untyped literal resolves like any other, by const n: uint8 = Math.clz(1); is 7.
Each numeric type has a parse function. For an bigint its signature is parse(_string_, _radix_ = 10); for the parse(_string_). The parse accepts what float64.parse accepts, fails as it fails, and returns a Number. It returns a value of the type the function belongs to.
The accepted input is the grammar of a literal of that type, with optional leading and trailing white space and an optional sign. Numeric separators are accepted, and the form taking a radix accepts the prefix matching that radix. The entire string must be a literal of the type: no trailing text is consumed.
parse throws a
Each type also has a tryParse function with the same parameters, returning a value of the type where parse would return one and parse would fail to parse its argument. Its return type is the union of the type and the null type. A call that is not a parse at all, because the receiver is not a numeric type, throws as parse does rather than answering
parseInt and parseFloat are unchanged and continue to consume a prefix and to return NaN. The new functions do neither, because neither is available to them: an NaN at all, so a failed parse has nothing to return and must throw. Doing the same for the float types is a choice rather than a necessity, and it is the right one, since a failed parse that becomes a NaN surfaces far from its cause. Refusing trailing text follows: a function that cannot signal failure with a value has no reason to be lenient about what it accepts.
Distinguishing the two errors is what makes the failure legible. uint8.parse('12abc') throws a uint8 literal, while uint8.parse('256') throws a
tryParse returning a nullable union rather than a sentinel is what lets the failure be handled by narrowing (uint8 in the branch that tests it against string is deliberately not a conversion source for a numeric type, so let c: uint8 = '1'; is a '1' := uint8 is a parse and lets the failure throw, and one that does not writes tryParse and handles the
The global functions isFinite and isNaN, and the functions Number.isFinite, Number.isNaN, Number.isInteger, and Number.isSafeInteger, are overloaded for the numeric types by boolean, answering as true or false, so a test of it is judged by
The bigint family is the one exception to the table's uniformity, and it is a compatibility rule rather than a semantic one: an overload taking a bigint is declared only for a function that has no defined answer for a BigInt today. isNaN and isFinite perform Number statics already answer Number static reaches that static's existing signature and is answered exactly as it is today.
| Predicate | bigint | Binary float types | Decimal types | Rational types | Complex types | |
|---|---|---|---|---|---|---|
isNaN, Number.isNaN | isNaN; none declared for Number.isNaN, which answers | |||||
isFinite, Number.isFinite | isFinite; none declared for Number.isFinite, which answers | |||||
Number.isInteger | none declared; Number.isInteger answers | 1.00 is integral | none declared; Number.isInteger answers | |||
Number.isSafeInteger | none declared; Number.isSafeInteger answers | none declared; Number.isSafeInteger answers |
A complex answers the two classification predicates by its components, because a component can be NaN or infinite: isNaN is isFinite exactly when both are cmath.isnan and cmath.isfinite. It declares neither Number.isInteger and Number.isSafeInteger, which do not coerce, answer
The complex numbers extension declares its family's rows for all four predicates. The expected
The isFinite has one answer, and the predicate saying so at the type is what lets a reader delete the check. The decimal columns are value tests even though the arithmetic of this specification never produces a decimal NaN or infinity, overflow raising a
Every test is of the mathematical or format value, never of a representation. At a width past 2*
A plain BigInt value is a value of the bigint type, and it raises the one compatibility question in this table that the type system does not settle by itself. Every other row answers about a value that no existing program can write: a float32 NaN and an int32 need syntax this Number.isInteger(5n) is isNaN(1n) and isFinite(1n) throw today, so the flag defines them, and the Number statics answer today, so the flag leaves them alone.
The visible cost is that isFinite(1n) is Number.isFinite(1n) is Number.isInteger and Number.isSafeInteger have no global counterpart, so no surface takes a BigInt for those two questions at all. Their answers at the bigint type, were a surface for them to be declared on to exist, are
The other rows repair a quieter hazard: without them, Number.isNaN of a float32 NaN is Number.isInteger of an int32 is
This clause completes the
A Tuple Element Record describes one position of a ... element), and an [[Initial]]
An established tuple sequence domain interprets a tuple's fixed elements,
Several written unbounded rests may also establish this domain. After
Let p be the full fixed prefix width and s the fixed suffix width. Without defaults the minimum is p + s. With defaults, the suffix is empty and the minimum is the number of required prefix positions. The maximum is p + s without an unbounded run, and unbounded otherwise. At an admitted length n, supplied prefix positions retain their indices, suffix positions start at n - s, and intervening positions have the unbounded run's
This is a semantic expansion, not a requirement to allocate one
For two established positional domains, including the separately specified plain/defaulted-prefix domains, subtyping requires inclusion of their admitted length ranges and positional subtyping at every occupied index for every admitted source length. Repeated runs can be compared at their boundaries. An unresolved or general multiple-dynamic-rest domain supplies no new inclusion proof against an established
At a receiving boundary, use the established domain for arity, per-position membership, elementwise conversion and retained storage admission. Check an impossible length before indexed Gets. Retain selected callable and nested construction evidence, preserving the existing identity, ordinary-write, failure and publication rules. Default construction supplies each fixed position from its formed declared default, or independently from its
Indexed stores, permitted length changes, intrinsic movement checks, search and reference liveness use the complete domain. Surviving indices must retain their contracts across resizing. A
This clause extends owned Array membership admission to
A successful receiving membership admission retains the complete
Before publishing the new guard, revalidate the retained own data observations and their current ordinary membership roles without a second observable source walk. An Object identity does not prove that an earlier nominal role remains true. Each class role is established by following ordinary internal prototype links to its required target. A
A retained
After a permitted prototype publication, update the affected
Ordinary element and length publication must preserve all retained
Dependency bookkeeping must not strongly retain an
For an admitted one-element tuple of type [...[].<C>, ...[].<D>], changing its ordinary child's prototype from C.prototype to D.prototype may preserve the tuple. It need not preserve an independently acquired exact C contract. For two initially C children, changing only the first to D produces an invalid
This membership and storage
This clause extends the
A bounded
A supplied
After pure parameter admission has removed unavailable candidates, rank the remaining converting
Retain a failed pure parameter conversion as an unavailable candidate and a failure fact in input-index and written-run order. It must not preempt another complete viable assignment. If no complete assignment exists, propagate the first retained pure failure or throw a
After selecting the complete assignment, execute each retained
After the selected operations, validate the complete prospective sequence against every acquired
For a staged insertion, only the destination value supplied by the current write may convert. Other entries of its previously prepared private tail are fixed values, whether relocated or supplied by earlier insertion writes. They participate in complete assignment and guard checking without another conversion. In particular, an insertion must not run a
For [...[].<C>, ...[1].<string>, ...[].<boolean>] and source [1, "s"], where C declares constructor(value: any), the selected conversion constructs C once and retains the required String. A
The operator extension below adds bounded source and inbound plans. General effectful or composite parameter checks, nominal defaults, and generic
This clause extends the ordinary nominal rest planner with instance source and inbound conversion declarations from ordinary class definitions. Retain each declaration's resolved target
Source lookup follows ordinary internal prototype links without observable Gets or Proxy traps. For the exact target, use the nearest prototype's source declaration
After complete unchanged membership has failed, prefer an applicable converting
Retain an ambiguous candidate during the complete count assignment. If the selected assignment includes an ambiguity, throw a
Retain the selected callable and receiver or argument while planning. A source invocation calls that callable with the retained source value as receiver and no arguments. An inbound invocation calls its selected callable with
Retain the same post-effect complete-pattern validation,
This clause specifies the bounded
This clause specifies the owned Array number, string, boolean, bigint, symbol, null, undefined, float16, float32, float64, or resolved int and uint types. Nested tuple spreads retain their
The tuple's
A successful owned Array admission retains its
A fresh
Conversion to a tuple in this domain first retains ordinary complete membership success and its admission evidence. Otherwise obtain the source's length and indexed observations under
Purity is a property of the selected conversion operation, not of an implementation's representation of its source. Intrinsic conversions from an unparameterized decimal, rational or binary floating-point value may supply retained results and failure facts, even if an implementation represents that value using an Object. A predictable failure, including a nonfinite value's conversion to bigint, makes that candidate unavailable without selecting it and prematurely propagating its failure when another complete assignment is viable. This does not permit speculative execution of a source class operator, a metadata operation, an ordinary Object coercion or a reference read. Establish that the conversion uses only the intrinsic numeric payload and applicable pure rules before retaining its result. An implicit numeric-family prohibition is still a failed candidate; this rule supplies no new implicit conversion. For example, an explicit conversion of a one-item sequence containing float128(Infinity) to [...[].<bigint>, ...[].<number>] selects the Number run after the pure BigInt failure. A required BigInt position cannot be erased by that giveback.
At a checked receiving boundary, scalar rest candidates are subject to any. The same any source containing decimal128(7) does not implicitly fill [...[].<bigint>, ...[].<number>]; it throws a
Select the greatest count
If no assignment is viable, propagate the first retained pure scalar conversion failure in input-index and written-run order, or throw a
An indexed store, including a reflective data definition, plans against the full proposed sequence, every acquired guard and all existing location contracts. Only supplied destination values may undergo ordinary boundary conversion. Gap initialization and length growth select defaults consistently with the entire
A
Retain the consulted current own data values, length, backing contracts and acquired guards before a receiving conversion. Revalidate them after effects and before publication. A changed consulted value, length, backing contract or guard invalidates that receiving operation with a
Intrinsic movement requires the existing
A borrow uses the established invariant location contract, following reference aliases and Proxy aliases of an already guarded
For [number..., string...], replacing the second value of [1, "s"] with a String is valid. Appending Number 2 may select ordinary String conversion and store "2". Reversing the same input does not convert movement values: its first write can leave ["s", "s"], and the second write then throws. The surviving state satisfies the guard.
These rules do not justify a general first-rest fallback. Defaulted or non-scalar heterogeneous runs, object-valued role dependencies, metadata effects, fresh Proxy or array-like acquisition and general effectful intersection conversion require additional algorithms. Existing copying rules for an unbounded tuple without a determined result
A concrete
Associate an initially empty observation record with the source Object and this concrete operation. It has a retained length value, initially
Use this operation at the sequence-read points of membership and conversion. Retain actual
Observation is lazy: retention performs no additional source
Each independent or reentrant receiving operation owns a new observation record. A standalone membership test does not provide observations to a later conversion. Retained Object values preserve identity; they are not deep snapshots. Observation establishes no storage contract, descriptor stability, reference permission or liveness fact. Successful admission and produced-result publication retain their existing complete-backing rules. Failure preserves ordinary earlier effects, including independently completed nested receiving boundaries, without publishing the failed sequence's speculative backing.
For an
For a tuple with an established sequence domain (
For general rest shapes, retain the collected sequence's count constraint inside
Declared count facts may establish a tuple length range even when several unbounded rests prevent a unique positional domain. Sum the ranges contributed by required fixed positions (one to one), defaulted fixed positions (zero to one),
For a fixed prefix with defaults followed by a trailing unbounded homogeneous array rest, a converting boundary completes omitted fixed positions on a separate Array using their already formed defaults. A fully supplied compatible input retains the ordinary in-place admission rule. Retain the supplied observations and selected evidence, including nested construction, and the complete prefix/rest storage domain; do not replay source Gets to complete a successful admission. Apply the same overlapping-domain, failure and publication rules as omitted no-rest tuple construction. The produced Array is fully admitted before an enclosing setter receives it. Its storage minimum is the number of required prefix positions, its maximum is unbounded, and the rest starts after the full declared prefix even when some prefix positions are currently absent. Subsequent growth uses element-type defaults, not construction initializers. Independent membership and reference-viability tests do not construct the result.
A tuple's length is a range rather than a number because a trailing position may carry a default and a position may spread an array type. [uint8, uint32 = 10] accepts a one-element and a two-element array, which is what lets a shorter array satisfy a longer tuple return, and [uint32, ...[].<float32>] accepts any length from one upward.
Checking every element is linear in the array's length, which is the cost the any boundary has for an array and does not have for a uint8. It falls only where an array of unknown type reaches a [].<uint8> passed to a [].<uint8> parameter costs nothing. Whether an implementation may instead check an element on each read, deferring the cost and spreading it, is the same question the
A possible non-terminal result with value?: T contributes T | undefined: reading an absent property supplies
Exclude an done property is required and established truthy. In particular, done?: true may be absent and then reads as value remains a possible yielded value. Optionality and all viable union alternatives participate in the
Apply the same conservative
For for await, derive the next and then contributes each possible non-done record's value type without separately awaiting that value. The async-from-sync adapter instead awaits the
For a fixed super calls.
A value spread in a call, a super call or an array literal, and an array pattern, use the iteration protocol selected by evaluation. Their static iteration contribution consists of a known yielded
It is a super call alike. A known numeric scalar therefore cannot be spread in new C(...n) or super(...n), just as it cannot in f(...n); unknown operands retain their runtime checks. This does not reject a non-
A declared next method when done is
A mutable Array's Symbol.iterator method. Its [[Element]], indexed position types, and storage extent therefore do not by themselves establish a spread's or array pattern's yielded types or count. In contrast, iteration of an immutable tuple Composite is specified to traverse its elements (
The same
Replacing an uint8 values; an ordinary spread observes those two values and the
This judgment does not change the separate index-based semantics of reference iteration (
Where a
During evaluation, each direct element receives the
Before an unobserved source spread, consider every permitted final length at or above the current index plus one plus the number of remaining direct elements and elisions. Supply a tuple position context only when the destination type is the same at all such lengths. A fixed prefix position is independent of that length. Beyond the prefix, compare the homogeneous rest type with each suffix type that can reach the index; use
A direct element's fresh construction result retains its selected alternative through the enclosing Array's
These evaluation rules do not make an observed
Where no such undefined and a spread element contributes the never would say something false, since element types are invariant and a never-elemented array is assignable to no other array type.
A literal every element of which is a literal does not ANCHOR an inference ([1] has a known type and states nothing a program annotated, exactly as 1 does, so function g() { return [1]; } publishes nothing and stays untyped.
This rule changes the meaning of one
[1n].includes(1) evaluates to The literal is a [ ] . < bigint >, so includes is read at that bigint position; literal propagation then builds the argument as a BigInt, and the search compares it against a BigInt element that holds the same
It is accepted because no program relies on it. A search of a BigInt array for a Number is a mistake in every case it can arise, and the value it evaluates to today is the one that hides the mistake. The alternative — leaving every array literal without a
The neighbouring cases report rather than change: [1, 2, 3].includes("2"), ["a"].includes(1), and [1, 2, 3].indexOf("2") are type errors, where they previously evaluated to [300].map(f) where f takes a uint8, is now reported as a number and uint8 rather than a range error naming 300. The stricter report arrives earlier; the weaker one described the value better.
An elision in an array literal occupies a position and contributes undefined to that position's contextual element judgment. It is a
The default value of a
A store to an element of an array of length is of the
A store to a position of a [uint8] seen as a [uint8 | string] is the same Array, and a String written through the wider view would otherwise land in a position declared uint8. The same reading governs the array store above, where [].<any> is a supertype of every array. A tuple's arity is part of its type, so a store to a position beyond it throws a length that would leave fewer positions than the tuple fixes; a tuple with a rest element admits any length at or above its fixed positions.
When an explicit definition or assignment to an Array's
Preparation does not publish the requested elements, length, capacity or relocation. Its failure does not roll back independent effects of default computation, including independent mutations, reservations or changes to property attributes. Such effects are not retried. Existing element value changes alone do not change the backing contract. A failed outer preparation does not itself invalidate references. A successful increase within capacity preserves references to existing elements.
The new properties are present even when the default is
For a
Creating a new own
For a growing intrinsic unshift or splice on an owned
The private definitions belong only to the specific intrinsic writes, not to code invoked by them. Getter, setter, species and conversion callbacks perform independent operations, including nested mutations of the same receiver. Before and after each intrinsic write and before final publication, if the
At the final length update, prepare the complete proposed tail using the retained descriptors. A position not supplied by an own data definition, including one whose setter consumed the supplied value, uses its actual backing type's default or throws if that default is
Update capacity and relocation only when initialized growth publishes, using current capacity. A failed definition, a discarded private tail, or an inherited setter that creates no element does not itself move backing or invalidate references. Growing beyond capacity invalidates references by
An owned "-0" is not an
For a shrinking intrinsic pop, shift, or splice on an owned
Before and after each intrinsic element write, after intervening lookup or conversion effects and before applying its own property definition, at each deferred deletion, and before final truncation, if the
At the final length update perform the ordinary
An independently acquired required object-property contract is not waived by sequence removal. Check its deletion protection at the ordinary deferred-deletion step. During length truncation, if deletion instead completes abruptly because of that protection, restore length to one past the protected index, apply any requested nonwritable length, and propagate the original
Actual removal, including a successful removal before a final descriptor barrier, invalidates that element's identity under
A method that writes a position takes its value at that position's type: with at the type of the position its index selects, and fill, push and unshift at the type of each position they reach, which is why they are refused on a tuple whose positions differ. A method that would MOVE a value between positions of different types throws a reverse, sort, copyWithin and splice on such a tuple - because the Array would otherwise hold values its own type does not admit. Their copying forms are the way to do it and carry the toReversed answers the positions in reverse, slice the window it took, with the toSorted the
For a bounded tuple without a rest, including one with trailing defaults, or a tuple whose rests all have
For a proved intrinsic mutation, determine its permitted length and movement plan from the backing contracts after the ordinary receiver, length and index/count argument processing, and before element access, element conversion, callbacks, species creation, writes or deletion performed by the mutation. Existing comparator validation for sort precedes this plan. A planned final length outside a fixed array extent or the tuple's permitted length domain, or changing any surviving tuple index's backing type, throws a
A planned move between tuple positions requires reverse, compare each mirrored pair. For copyWithin, compare the normalized source and destination runs. For shift, unshift and splice, compare every source/destination pair the operation moves. A same-length splice sort, every position in the occupied range must have the same backing type because its permutation is not known before comparator execution. For fill, every position in the selected non-empty range must have the same backing type. Empty ranges impose no movement or uniformity requirement. These are restrictions on participating positions, not a blanket rejection of every method on a heterogeneous tuple.
The plan is a pure judgment of contracts and index geometry. It does not read elements, attempt conversions, invoke user code, publish storage or change reference identity. A successful plan does not make the operation atomic or waive any subsequent check: ordinary reads, conversions, writes, deletions and length changes retain their order and effects, and a later pop therefore fails before removing an element, while an ordinary write failure after a valid plan may retain earlier writes. Reference liveness remains governed by
A method of the array that takes or returns an ELEMENT takes or returns it at the includes, indexOf, and lastIndexOf take their search element at t, where for a tuple t is the union of its position types, a rest position contributing its fill, push, and unshift take the values they store at t; at returns t. A literal argument in one of those positions takes t by a.includes(65) finds the element 65 of a [].<uint16> rather than comparing a uint16 against a Number and failing. The same holds of a Map.<K, V> and the element positions of a Set.<T> take their declared types, and so do those of a WeakMap.<K, V> and a WeakSet.<T>, whose key and element positions are constrained to the types a weak reference may hold and are checked against the declared type on top of that constraint. A method that STORES takes its value at the declared type however it is spelled, getOrInsert and getOrInsertComputed included, the latter checking the value its callback returns. A LOOKUP answers the get on a Map.<K, V> is V | intersection and difference on a Set.<T> draw only from the receiver and return a Set.<T> whatever the other operand holds, while union and symmetricDifference draw from both and return a Set.<T | U> for an other operand of Set.<U>. Where the other operand's Set.<T> would state more than the values support. isSubsetOf, isSupersetOf, and isDisjointFrom answer a Boolean. The count a collection reports, and the collection that reports none, are
These are ordinary parameters of a declared type, so any type is checked at the boundary. A search is not treated more leniently than a store. a.includes(70000) on a [].<uint16> is a
This reconciles the two rules [uint8, uint32 = 10] therefore defaults to [0, 10] rather than being rejected because the second position was already spoken for.
The literal element store remains the open case array[0] = 300 on a [].<uint8> as wrapping to 44 on the grounds of matching Uint8Array, while the literal rule would reject it, and the compatibility argument does not reach new syntax. This clause resolves it against the [].<uint8> exists nowhere today and Uint8Array is untouched, and a literal that does not fit its destination is the mistake every other literal rule here treats it as. A
%TypedArray% and its Uint8Array reports its length as a Number, wraps a store that does not fit, and behaves in every respect as it does today. Nothing here changes an object that already exists.
The two are SEPARATE ARRAY SYSTEMS rather than one system with two spellings, and they do not mix directly: a Uint8Array is not a [].<uint8> and does not coerce to one, its elements being Numbers rather than values of a numeric %TypedArray% is the system for byte-oriented work against existing APIs, and the array types are the system for
Where they meet is a BUFFER, which both already understand. A %TypedArray% exposes its buffer, Span.<T>(buffer) (%TypedArray% reads.
An implementation SHOULD report the deprecation once per %TypedArray% %TypedArray.prototype%. Once per
Nothing about the report is observable to an ECMAScript program — no property, no altered result, no thrown completion — and an implementation that emits nothing is conforming. Deprecated says a program SHOULD move; untouched says no program BREAKS by not moving, and a deprecation that changed behaviour would be the compatibility event this clause exists to avoid.
The alternative was to give %TypedArray% the counts and the growth of an array type, so that the two families were interchangeable. It was measured and declined. A length at the let the any type) — so for (let i = 0; i < u.length; i++) and total += u.length would raise in existing programs, and u.length === n against an untyped binding would silently answer
Two systems that each behave well, with a stated boundary and a buffer between them, is a smaller cost than one system that behaves well in new code and changes the meaning of old code. It is also legible: a reader can see which system a value belongs to from how it was written.
Array.prototype.sort and Array.prototype.toSorted order by the String representation of each element when no comparator is given. Where an array's ELEMENT TYPE is known and carries an order of its own, that order is used instead, so [].<uint8> sorts as %Uint8Array%.prototype.sort does rather than as text. An array with no
A type is ORDERED when it is one of the following. Anything else, including any, an object type without a declared comparison, and a union of unlike types, is not, and an array of it keeps the String comparison.
| Type | Order |
|---|---|
| A numeric | By numeric value. A float type places -0 before +0, as |
| A 64-bit integral type | By value, at the type's OWN precision. int64 and uint64 hold magnitudes a Number cannot distinguish - 9007199254740993 and 9007199254740992 are one double - so an implementation that reads such a value as a Number before comparing leaves them in their original order, which is a wrong ANSWER rather than a lost digit. |
bigint | By value, compared AS a BigInt. Converting to a Number to compare would change the ORDER above 253, not merely lose precision. |
string | Lexicographically, which is what the String comparison already gives. |
boolean | |
| An | By the rule its string |
A class declaring operator < | By that operator. |
An implementation compares with a two-way predicate rather than deriving a three-way result: a comparison sort needs to know only whether one element precedes another, and deriving "less, equal, or greater" from operator < would call it twice per comparison to no purpose.
The order comes from the element TYPE, so it is fixed once for a whole sort rather than decided per pair.
For Map.<K, V> and WeakMap.<K, V>, getOrInsertComputed takes a key of K, a callback with contract (K) => V, and returns V. The callback's established parameters and produced result must satisfy these boundaries even when a particular map already contains the key. A fresh callback literal receives V as its contextual return type, preserving numeric-literal adoption. A callback result of
A member contract determined by a
Map, Set, WeakMap and WeakSet take type arguments, and their members follow from them.
Where the collection carries type arguments, size has the size
size is an length is assignable because an array is POSITIONAL and dropping a suffix is well defined; a keyed collection has no such operation that clear does not already name, and a count that deleted entries would be a mutator wearing the spelling of a report. Having a type does not imply having a setter: capacity (
WeakMap and WeakSet have no size, and reading one from a value of either type is a
The accessor returns a VALUE of the capacity, whose algorithm returned
Where the collection carries type arguments, its entry type is the tuple type « K, V » for a Map.<K, V> and « T, T » for a Set.<T>, and the members that walk it carry the types below.
| Member | On Map.<K, V> | On Set.<T> |
|---|---|---|
keys | Iterator.<K> | Iterator.<T> |
values | Iterator.<V> | Iterator.<T> |
entries | Iterator.<> | Iterator.<> |
@@iterator | as entries | as values |
forEach | (V, K, this) => void | (T, T, this) => void |
clear | () => void | () => void |
A Map.<K, V> declares that it implements Iterable.<« K, V »> and a Set.<T> that it implements Iterable.<T>, in the sense of
On a Set, keys and values are the same entries yields a pair of the element with itself, which is what the language does and what a destructuring for-of over a
WeakMap and WeakSet have none of the members in the table above, nor size (
The return type is the Iterator of MapIterator or SetIterator, for the reason that clause already gives: the bare-argument shorthand carries the narrower
forEach takes its VALUE first and its key second, which is the order the language chose and the order a reader most often gets wrong. Stating it is most of what typing forEach is worth.
A Reflect.typeOf of a Map.<K, V> is that type, and of a collection with no type arguments is the bare Map, Set, WeakMap or WeakSet.
Carried rather than inferred, for the reason a window and a vector carry theirs: the type is not recoverable from the value. A Map.<string, uint8> and a Map.<string, string> have the same object Map has no own properties at all - so inference read the literal type of an empty object and reported neither the
A Map's or WeakMap's key, and a Set's or WeakSet's element, are identity-bearing positions. At such a position the conversions of Map's VALUE, is unaffected and converts as
This governs the positions wherever they are reached - the methods that store, the methods that search, and the entries a
A boundary is a store, and the conversion rule reasons about stores: nothing is lost, since 5 is '5' and reads back as 5. A key is a store whose value is also an IDENTITY, and a conversion that loses nothing as a value can still lose an identity by mapping two distinct sources onto one key. Without this rule, _m_.set(_theNumberOne_, 1) followed by _m_.set(*"1"*, 2) left a Map.<string, uint8> holding ONE entry where a Map holds two - so giving a collection a type changed how many entries it had, silently, and only for the programs in which both spellings happened to occur.
The numeric targets already have this property, which makes the rule a consistency correction rather than a new restriction: Map.<uint8, V> could never merge two keys. The string target was the one boundary in this specification admitting a conversion that does not preserve identity.
An ARRAY's element position is not identity-bearing and is unchanged: an array is addressed by an index, so _a_.push(5) on a [ ] . < string > still stores Set is that its element IS its key.
The has for any other reason propagates unchanged.
A test a user writes with a needle the Set.<uint8> and a Set.<string> is empty without iterating, and isDisjointFrom is
Stated for both operands, which the earlier arrangement was not. An operation walks the smaller operand and probes the larger, and the probe ran through the converting
At a proved intrinsic collection construction, a non-nullish participating seed must satisfy the
Each iterated Map or WeakMap entry must be an Object before its key and value are read at
A collection acquires type arguments only where the entries it already holds satisfy them. Acquiring the type is therefore the same operation as checking that the value was already of it: each key, value or element the collection holds is converted at the corresponding type as by
This is what checks a CONSTRUCTOR's seed. new Set.<uint8>(["a"]) is a new Set.<uint8>([1]) holds a uint8 rather than the Number the literal arrived as, so an element that entered through the add are of one type afterwards.
Where intrinsic construction, seed iteration and insertion behaviour are statically established,
This bounded proof requires the intrinsic-origin conditions of
A class whose heritage names an instantiation constructs instances of it: for class M extends Map.<K, V> { }, an instance of M carries K and V, as does an instance of any class extending M. A class extending a bare Map or Set names no type arguments and its instances are
Checking on the way in rather than stamping before the seed is consumed. The latter is the more obvious rule and is not available: there is no object to carry the arguments until the construction has produced one, by which point the let s: Set.<uint8> = new Set(["a"]), where the construction names no type arguments at all.
A collection that ALREADY carries type arguments does not acquire different ones, so annotating a Map of Strings as a Map.<string, uint8> is refused rather than silently re-labelled. Only a collection carrying none adopts, and only where its contents support the claim.
A value is of the type Map.<K, V> when it is a Map and the type arguments it CARRIES are K and V, and correspondingly for the other three collections. An argument written any admits any instantiation in its position, matching the family top (any rule is not the collections' alone: it holds for two instantiations of any one generic declaration, a user class included, so Box.<any> is the family of every Box.<T> (Map and of no instantiation of it.
This is what is, a when pattern naming an instantiation, a catch clause, and narrowing all read, so the four agree by construction.
Decided by the arguments the value CARRIES rather than by inspecting its entries. Inspection would answer a different question: what a container currently holds is not what it will ACCEPT NEXT, and the instantiation is a claim about the latter. An untyped map whose entries happen to be Strings and uint8s will take anything on its next store, so calling it a Map.<string, uint8> would state a guarantee it does not make - and would licence exactly the unsoundness the invariance rule of
The prototype chain cannot decide this on its own: every Map has one prototype, so without the argument comparison every Map satisfied every Map.<…>. That is the same reason the ranges and the window forms carry their own membership rules rather than relying on the chain.
Membership is asked at a CONVERSION BOUNDARY as well as by is, and the two want different things from it: the boundary asks whether a value is already of the type so that it may skip converting. A collection carrying no arguments ADOPTS the target's at such a boundary - which is what let m: Map.<string, uint8> = new Map() means - and is then of the type. A collection already carrying arguments does not adopt new ones, so a Map of Strings does not become a Map.<string, uint8> by being annotated as one.
A Map or Set written with no type arguments is unaffected by this clause and by every rule of size
This is the collection reading of the rule an array already has, that "an array with no
The rule is stated per family rather than as a general rule about an unparameterized built-in, because the families do not agree on what an unparameterized use means and a single sentence covering them would decide questions this clause has not asked. Proxy already has a different answer: without a type argument a proxy is of the
The abstract operation GetSetRecord takes argument obj (an
This operation reads the size of a
A count is CHECKED rather than coerced, which is the discipline
The ELSE branch matters as much, and is the existing steps unchanged. The argument to a size is an ordinary Number and whose failure modes have observable error kinds — a missing size is a
The
An extension may add types to
An extension may not revise the type-checking framework of
The last of these is the constraint worth stating explicitly, because it is the one an extension is most likely to want to break. A Meter to become float32, a decimal extension wants a Number to become a decimal128, and a SIMD extension wants a scalar of a type other than a vector's lane type to broadcast into it. Each of these is available without an implicit conversion: the first is a metadata parameterization, which is a subtype of its base and so is subsumed rather than converted; the second is written; and the third is an operator the extension declares. The framework holds because every extension so far has been expressible within it, which is evidence that the four implicit cases are the right four rather than merely the ones that were listed first.
| Extension | Relied on for |
|---|---|
| primitive metadata | The meta types the Dimensions of NumberBounds of the JSON Schema constraints, and the DecimalContext that fixes a decimal's scale and rounding. The protocol itself, its decomposition, its conversion, its narrowing, and the operator and cast declaration forms are delivered by that clause. |
| memory layout | The placement construction that lands an instance on existing bytes, and the descriptor generation the layout is read for. The layout properties, the placement rule, the explicit controls, the bit-fields, and the finiteness condition that |
| SIMD | The lane-wise intrinsics an engine lowers to one instruction each, swizzle, shuffle, lane, select, and the reductions, together with the accessor vocabulary. The comparison operators and the lane counts are delivered by |
| simd | Gather, scatter, and masked load and store, which vectorize an indirect access and need an addressing model this specification does not have. The |
| generics | The uint<N: uint32>); argument-bound value generics and inference from an expected type; the constraint-family forms int, uint, and enum.<TValue>; and the generic parameters a decorator declares, which the where clause, and declaration- |
| dependent record types | The where clause and a schema's dependentRequired, dependentSchemas, and if/then/else express one another, and the validator generation that correspondence is for. The is operator, the where clause, and their narrowing are delivered by |
| decorators | Delivered by addInitializer ( |
| complex numbers | The operators and Math overloads of the complex types. The type of an imaginary literal and the conversions are delivered by |
| operator overloading | The math library the |
| threading | Specified by |
| ranges | The range literal's grammar and its lexical interaction with ., the members of Range, and the extensions that take a range as a bound. The case ranges and the bounded for counter that |
| type programming | The tooling plan: an evaluator embedded in a language server, a computed type displayed as its application beside its expansion, and a builder stack on a budget diagnostic. Every other obligation recorded here is discharged: construction, relations, never, and evaluation by |
The extensions not listed, including composites, pattern matching, do expressions, the pipeline operator, serialization, structure of arrays, the regular expression types, Temporal, and the standard library, are specified in terms of the core and are relied on by nothing here. That is a claim about direction and not about importance: serialization is where a where clauses this specification already gives, adding no rule any of them must accommodate. The one place the direction reverses is threading, whose heap sharing decides what a
The dependency now runs in one direction without exception. The metadata parameterization that
Every document of the
| Document | Disposition |
|---|---|
| README | The core; specified throughout this document. |
| typeobjects.md | Specified: the |
| typeprogramming.md | Specified: builders, canonicalization, the evaluation budget, structural matching (keyof (typeof x, is not an operator of this specification: the type query is Reflect.typeOf ( |
| higherkindedtypes.md | Specified: |
| generics.md | Partially specified: the parameters that are types and values, where constraints, and variance are |
| dependentrecordtypes.md | Specified: |
| primitivemetadata.md | Specified: |
| memorylayout.md | Specified: new in |
| operatoroverloading.md | Specified: |
| operators.md | Specified: the result of an operator by family ( |
| decorators.md | Specified: addInitializer, the retrieval forms, and the resolution and order of a decoration. Decorator syntax itself rides the decorators |
| complex.md | Specified: |
| rational.md | Specified: |
| decimal.md | Specified: |
| composites.md | Specified: |
| patternmatching.md | Specified: where chains it consumes in |
| The iteration types | Specified: |
| pipelineoperator.md | Specified: |
| doexpressions.md | Specified: |
| simd.md | Specified: the vector type's formation is |
| standardlibrary.md | Partially specified: the numeric library and the overloaded library rules are Iterable relation are Set.<any> and Map.<any, any> as the family tops are Map.groupBy and Object.groupBy are Promise statics, and Array.fromAsync are deferred. |
| errorhandling.md | Specified: |
| references.md | Specified: the ref type is in the grammar and the borrowing rules are SoA element is ref member is |
| threading.md | Specified by callThread and cancellation, the shared modifier's placement and stability rules, shared Atomics over |
| ranges.md | Partially specified: the range literals, the Range value, and its iteration are uint8.<1..=6> bounds desugaring, slicing to a view, and the random and Temporal integrations are deferred. |
| serialization.md | Partially specified: JSON.parse.<_T_> overload and its converting, validating walk) is JSON.stringify, streaming, structuredClone, and the binary layout) is noted there. |
| soa.md | The type and its layout: SoA is |
| regexp.md | Partially specified: the |
| random.md | Deferred; a library extension. |
| temporal.md | Deferred; a library extension. |
| specnotes.md | The plan this document follows; not itself a source of requirements. |
A reference is a borrow: a handle to a storage location, a variable, an object property, or an array element, that reads and writes through to the original rather than to a copy. It is what lets a function mutate its caller's value and a loop write into an array in place. This clause specifies the borrowing forms of the ref argument and ref return, the ref parameter, the ref lexical binding and its rebinding, and the reference for-of loop. The ref
ref is a ref is claimed only when the token that follows it, with no line terminator between, can begin the operand as written — an this for a yield or await where those are identifiers) for the binding forms, whose operand is a name. Everywhere else ref is the identifier it is today: f(ref) and f(ref, x) pass it, ref(x) calls it, ref[i] and ref.x read from it, ref = 1 assigns to it, and ref: labels a statement. The rule is what makes every addition below conservative: each new alternative derives only source text that is a Syntax Error in the base language.
The operand is a ref a, ref a[i], ref o.x, and ref this.buffer are borrows, while ref (x) and ref [0] keep their base readings as a call and an element access of an identifier named ref. In argument position one further token is admitted, the pipeline %, not to make ref % mean anything but so that its refusal (
An argument may be a borrow.
A return may be a borrow. This is the first restatement of
return ref followed by a line terminator keeps its base meaning: the restriction inside return ref x derives only through the new alternative, since two adjacent identifiers have no expression reading.
A lexical binding may be a borrow. ref alternative requires its := infers a binding's type from a value and the initializer of a ref binding must denote a location.
It is a Syntax Error if the ref alternative of
The per-iteration environment of a for statement copies each head binding by value into every iteration, which would silently decay the alias into a copy after the first. The form is refused rather than given that meaning; lifting the restriction awaits per-iteration semantics for a location binding, which no
A mutable ref binding may be re-aimed by a statement of its own. This is the first restatement of
The claim is the exact =, and otherwise the identifier reading stands — ref = 1 assigns to a binding named ref, ref(x) calls one, and ref: labels a statement, all as today. The = itself may begin a new line, since ref b followed by anything but = has no reading in the base language either; an implementation that reads the name before deciding restores it on any other token. The production derives only strings an
A reference for-of head marks its binding. ref is of, so for (const ref of a) continues to bind an identifier named ref.
It is a Syntax Error if the ref alternative's ref alternative of for-in or for await alternative of for-of of for-in walks nor the values an
A parameter's ref is already in the grammar (ref and its (ref a) member form (ref and its ? remains admitted, as the function type grammar already admits it; a call that omits an optional ref parameter fails the borrow at that call, a
An arrow function takes ref parameters through its ref parameter directly, refined exactly as (ref a: int32) => {} and the zip(a, b, (ref x, ref y) => { x = x + y; }) are async (ref a) => {} is the one form reached by the other road. A cover containing a ref parameter has no expression reading, so claiming it is arrow-only and conservative — (ref a) without a following => is a Syntax Error, as it is today. The Initializer Early Error above applies to the refined parameter as to a declared one.
A nested annotation in a formal binding pattern contributes a binding obligation even when the formal has no whole-value annotation. Retain each obligation in its declaration's type and alias environment and apply
It is a
A member of an object binding pattern may state the type of the binding it introduces, and may borrow the property rather than copy it. The annotation is written inside parentheses, because the position after a member's : is already taken: { a: uint8 } binds property a to a new name uint8, so an annotation cannot follow the colon. The parenthesized member leaves that colon free, so a member may be annotated and renamed at once.
Without a rename the parenthesized name is both the property read and the binding introduced, so { (a: uint8) } binds a to the value of property a, enforced at that binding as an annotated declaration is ({ (a: uint8): b } reads property a and binds b. An
A member marked ? uses | undefined, including a renamed binding. A checked default known not to produce
An object rest may state the type of what it collects, { (a: uint8), ...rest: object }, which is an object type rather than a member type, as an array rest's annotation is the type of the array it collects rather than of an element. It is written without parentheses: the parentheses of a member exist because the position after a member's : is already taken, and a rest has no such collision.
Object-rest collection produces a fresh ordinary, non-callable Object regardless of whether its source's
An object binding or assignment pattern consumes known property-read contributions from
A member marked ref borrows the property's location on the object being destructured, by the rule of ref parameter may not have one (?, and likewise for a ref parameter: the marker says the value may be absent, and a ref binds the location rather than the value, which is there whether or not the property is. A form that cannot honour what it declares is refused where it is written rather than at each use of it. An annotation on a ref member is checked against the referent and never converts it.
A member of an object assignment pattern may be marked ref in the same parentheses, and then re-borrows: the named binding, which must already be a ref binding, is pointed at the location of the property on the object being destructured. ( ref r ) : x borrows the property x, and ( ref x ) without a rename borrows the property of the binding's own name. A target that is not a ref binding, and a value that
An assignment member may not carry a type annotation. An annotation states the type of a binding the pattern introduces, and an assignment pattern introduces none; its targets already carry whatever type they were declared at, so an annotation here would be a second type for one target. A program that means to check a value writes is or :=.
The borrowed location belongs to the object, not to the caller's variable, so a function whose parameter is such a pattern takes an ordinary argument: g(o) suffices and g(ref o) is not required. ref o would state something different — that the callee may rewrite the caller's binding — and it could not reach the pattern in any case, since a parameter that is a
A Reference Value is a value produced by a ref expression that holds a storage location: a
A typeof, Reflect.typeOf, ===, and instanceof see the referenced value and never the reference, and a ref parameter and ref lexical binding below, which alias the location rather than store the reference. There is therefore no way to distinguish a reference from the value it refers to, which is what keeps it a location and an index rather than an object.
Decay is where the absence of identity is realized. A reference reaches a value position at an ordinary call boundary, an operand of an operator, an argument to a non-ref parameter, or a conversion, and at each the value at the location is taken. The reference is observable only through the aliasing behaviour of the binding that holds it, not as a value in its own right, so no reflected type and no equality can name it.
Decay is total, and the channels through which a
A ref parameter makes a ref parameter is therefore unmapped, and its elements hold the decayed values of the arguments; neither a read of arguments nor a store into it reaches the caller's location, and strict and sloppy code agree on this. The ref parameter itself is unaffected: it takes its location from the argument list, which the arguments object is a separate view of.
Naming the channels is not a list of special cases but the closure of one rule over the places a value can go. Two of them are worth stating for what they exclude. A rest parameter gathers into an array, and an array cannot hold a reference, so a borrow forwarded through a variadic wrapper arrives as a value — the alternative, refusing the call, would make (...args) => f(...args) hostile to a borrow for no safety gained, since the forwarded reference could not have been written into the array anyway. And the built-in boundary is what makes the reflective call paths decay channels without naming them one by one: Function.prototype.call, apply, bind, and Reflect.apply are built-ins, so a
A ref argument f(ref e) evaluates e to a storage location and passes a ref parameter binds its name to the location the argument carries, so a read in the callee reads through to the referent and a write in the callee writes through to it. It is a ref parameter is not a ref
At final binding of every reference rest, including a trailing rest reached through an unknown callable, check the actual run with
A type annotation on a ref parameter is checked against the referent and never converts it. The referent is of the annotated type or a ref parameter's binding is not re-enforced by the parameter type pass of
At that single-parameter receiving check, retain the normal referent read and its
If both that available location contract and the required referent type are other than
An unavailable location contract and existing ref-marked binding. A formal whose annotation is a ref alias.
Where the borrowed location's type is statically known, ref e contributes a return ref e is checked against the declared return type before the caller's value-context decay; reference compatibility uses the existing invariant target relation. At an ordinary value parameter the explicit reference argument instead contributes its referent type, after the specified decay, to argument checking, overload resolution and generic inference. Unknown location types retain runtime checks. These judgments read no location and do not alter borrow permission or reference liveness.
Reference-argument judgments apply wherever the static argument binding proves the receiving parameter. A spread of unknown count does not suppress the check on a preceding required positional argument whose destination is independent of that count. It is a
A function may return a reference with return ref e, evaluated as a ref argument is. The operand may denote storage of any lifetime, including a binding of the returning function itself: an environment is an object like any other and stays alive while anything refers to it, so a reference to a local outlives the frame that made it, exactly as a closure over that local does. At an ordinary call the returned reference first(a)++ write through to the element;
The abstract operation RequireBorrowableReference takes argument V (an ref argument, the ref return, and the ref binding — obtains its location through it, so all of them refuse the same operands. It performs the following steps when called:
It is a super property, a property with a known primitive base, or a known
For a property selected from a union receiver or a statically established
What counts as a storage location is decided at the
The exclusions are the cases where no location exists rather than the cases where one is inconvenient. A private member and a super property are not property references over an ordinary base; a property of a primitive is a property of a fresh wrapper that the write would discard; a
For a let ref binding and a reference parameter are rebindable; a const ref binding is not. The classification belongs to the nearest declaration, including binding-pattern and iteration declarations, and is independent of the referent's var redeclaration does not change an existing reference parameter's binding kind. Where name resolution remains unknown or may be intercepted by an object
A ref lexical binding aliases a storage location for its scope. let ref b = e and const ref b = e evaluate e to a location by ref argument applies, and bind b to it; it is a ref binding to omit its initializer. A read of b reads through to the location. A let ref binding may be written through, so b = v writes v to the location, and may be rebound; a const ref binding may not be written through, so b = v is the ordinary assignment-to-constant const. An annotation on a ref binding is checked against the referent without conversion, as for a ref parameter.
These read-through rules apply to direct reference bindings in a
An export of a binding, including export { b as default }, remains export default b evaluates its expression and exports that result under the ordinary value-consuming rules; it does not create a reference binding.
Preparing this binding's contract for a
The ref binding is its annotation where it has one, and otherwise the ref parameter, a ref return type and the binding of a reference for loop already state for themselves, said once for the form that writes no type of its own. Reassigning a let ref binding with ref b = e rebinds it: b is redirected to the location e ref binding.
let a: uint8 | string = 1; let b: uint8 = 2; let ref r = a; ref r = b;, the binding r retains uint8 | string. A subsequent r = "s" is assignable to that view, but the write crosses b's uint8 boundary and throws a ref parameters, and every access remains subject to
A reference binding's
Local alias chains preserve known origin restrictions. A reference to a reference binding follows that lexical binding's current location; rebinding that binding therefore changes the location reached through such an alias. Rebinding replaces the relevant origin without changing the alias's fixed declared
Reference iteration requires array storage or the SoA storage specified by
A for ( LetOrConst ref p of e ) loop binds p to a reference to each element of the array e in turn, so the body writes into the array in place. The iteration is index-based: it reads the length of e once, evaluates the body once for each index below that length with p bound to a reference to the element at that index, and does not go through %Symbol.iterator%, so patching the let ref element binding may be written through and rebound and a const ref element binding may not, as for a ref lexical binding. It is a
The length read at the start of the loop is pinned for its duration. An operation that changes the length of the array while a reference into it is push, pop, shift, unshift, splice, or a store to length, is a ref loop makes easy to reach since it holds a reference across the whole iteration. The rule is the loop half of SoA column ref a[i] to a callback with a ref parameter composes from the argument and parameter forms above, which is how the zip writes through both arrays.
The liveness rules are the whole of what the borrow needs, in place of a borrow checker with lifetimes. A reference cannot dangle in the Rust sense because the storage it points into is reachable from it and a garbage-collected object stays alive as long as anything refers to it; what remains to prevent is a reference into storage that moves, which
A location-consuming context is a position that consumes the storage location an expression
The first is the operand of ++ or --. The Early Error of the base language refuses a call there, because a call has no assignment target; that error is relaxed for a
function first(a) { return ref a[0]; }
first(a)++; // Writes through to a[0]
The second is a borrow source: the operand of a ref argument, the initializer of a ref lexical binding, or the source of a g(ref first(a)), let ref p = first(a), or ref p = first(a). The location the callee returned is passed on unchanged, including its existing liveness obligations; nothing is copied and no second borrow of a different location is created. Parentheses around the source preserve this context. Its referent type supplies the binding or rebinding type judgment, rather than treating the reference itself as a stored value.
It is a ++ or --, a borrow source as described above, or an assignment target to be a call whose
The third is an assignment target. first(a) = v stores through the location the call returned, and so does every form that assigns to a target: a compound or logical assignment, which reads the referent and stores the result back through the same location; an element or property of a destructuring assignment, at any depth; and the target of a for-in or for-of head. These all follow from one static semantic rather than from a rule stated per position.
Where an explicitly
The static semantic
Which of the two the program gets therefore depends on what the checker can see of the callee, and that boundary moves as more of a program is
A borrow of an element of an SoA (
This is a location like any other, and every borrowing form produces it: ref s[i] as an argument or a return, and let ref or const ref as a binding, all yield the same reference, and all of them obtain it through ref parameter therefore accepts a borrow of an SoA element exactly as it accepts a borrow of an array element, which is what lets one function serve either storage: a parameter declared with a
Reading through the reference yields the element, whose s[i] produces, so no aliasing survives a boundary that takes a value.
For an ordinary reference these two coincide, since the referent of a variable or a property is just the value stored there, and nothing distinguishes reading a borrow from decaying one. An SoA element separates them: the element that
The liveness rules of SoA is the container in this specification that relocates, so a borrow into one is for-of over an SoA, an operation that changes its length is a
Validate the original element location before a reference read, whole-element write, value copy or decay; gathering from the current index does not discharge that check. An ordinary call that returns a
A write through a borrowed element retains that borrow while obtaining source fields and performing conversions. Recheck its liveness after those effects and before each resulting storage write. If an earlier source operation completes abruptly, propagate that completion before attempting the subsequent write. Earlier successful column writes and ordinary effects remain visible when a later operation fails; the scatter is not a transaction.
Removal permanently invalidates the removed element's identity. Regrowth at the same index does not revive an old reference. A
A reference is valid while the storage it aliases is where it was borrowed from. Two rules make that so, and they apply at different moments: the loop rule refuses the offending operation, and the relocation rule refuses the later use.
The loop rule is the one above (for-of, an operation that changes the length of the iterated container throws a
The relocation rule governs a reference held outside such a loop — a ref binding, or a ref parameter whose caller borrowed an element. A reference into storage that can be relocated is invalidated when that storage is relocated, and a read or a write through an reserve and withCapacity.
An implementation is not required to move an allocation that it need not move, but a program may not depend on its staying put: the rule is stated so that an implementation backing a [ ] . < T > with a packed allocation is conforming without having to track which references are
The rules divide this way because they can detect different things. A length comparison can see push but never reserve, which changes an allocation's capacity while leaving its length alone; a relocation test sees exactly the event that invalidates a reference, whatever operation caused it. Together they leave no gap: within a loop the length change is refused before anything moves, and outside one a reference that survives a move is caught at its next use. A reserve performed inside a reference loop is not itself a length change, so the loop rule does not fire, and the relocation rule then refuses the loop's own reference at its next use.
Storage that cannot relocate needs no rule beyond the loop rule. A reference to a property of an a.shift() while a reference to a[0] is a[0], whose value the shift has changed — the same thing the expression a[0] has always meant.
The alternative
The SoA of SoA is a [ ] . < T > is governed by the same rule. It has a backing allocation whose capacity is distinct from its length, reported by capacity and grown by reserve; growth beyond the current capacity, whether by reserve or by adding elements, relocates the allocation and invalidates every reference into the array, so a use through one is a
A growable [ ] . < T > has a backing allocation whose capacity is distinct from its length (
Array.prototype.capacity is an
An accessor and not a method: as a method, a.capacity answered the if (a.capacity > 1000) was silently true and never reported the mistake. A fixed extent returns the extent itself and not the length, since such an array's storage holds exactly that many elements whether or not they have been written.
The three returns produce a value of the capacity among the counts explicitly, and its closing sentence is that naming the type once is "what keeps capacity comparable with length without a conversion — the invariant that a capacity is at least a length is unstateable if the two are not one type". An algorithm returning sec-collection-size records the same distinction for size and points here.
Unconditional, unlike size. A keyed collection reports a Number where it carries no type arguments, because an untyped Map is an ordinary Map and capacity has no such form: it is reached only through a [[TypedElement]] capacity at all is a
The abstract operation ToIndexType takes argument value (an
A COUNT passed to one of these operations is checked as a count rather than coerced like an ordinary argument. length and capacity READ at the a.reserve("4") would be accepted while a.length could never be a String.
This is stricter than ToLength, which these clauses previously specified: it refuses rather than converts.
This method grows the backing allocation to hold at least n elements without changing the length. It performs the following steps:
A request for room the array already has is a no-op and not an error, so a caller need not know the array's capacity to ask about it — which is why the comparison starts at the length rather than at zero. Growth increments [[TypedGeneration]], and that is what invalidates a reference into the array; a reserve that does not grow must not increment it, or a borrow into storage that has not moved would be refused.
The ArrayCreate's limit. The check is on the growth reserve within an existing capacity is unaffected.
An implementation unable to reach a count the
This method releases the backing allocation down to the length. It performs the following steps:
It is the mirror of reserve and takes its [N] . < T > needs no case of its own — its capacity is its extent and its length is its extent, so it is always already at fit and the step above returns.
Where it does release, the allocation RELOCATES, so it invalidates every
This is a BINDING release and not a request an implementation may decline, unlike the similarly named operation in some languages. An optional relocation would make an optional invalidation, and a program correct on one implementation would raise on another; the liveness rules can only describe an operation whose effect on references is fixed.
A call established to select this intrinsic has the [].< T > as its result. A statically established invalid argument or result boundary is a
The array type [ ] . < T > has a withCapacity method that constructs an empty array of that type with room for n elements. It performs the following steps:
It reserves rather than fills: the result has length 0, so a zero-filled array of a known length is a fixed [N] . < T > and not this. The array it returns is growable, the capacity being a property of the allocation and not of the type.
A regular expression literal is a
The
undefined type otherwise. A group is in the second case when it is quantified with a minimum of zero, when it lies within a disjunction of more than one alternative, or when it lies within a lookahead or lookbehind, and when any group that encloses it is.A non-capturing group, a lookaround, and a backreference add no RegExp.<Captures, Groups> annotation whose arguments differ from a literal's inferred RegExp, the raw library type, is the supertype of every such parameterization, so it holds a literal of any
An optional string | undefined and not string | void. The two are not interchangeable here: void is the type with NO VALUES, "written to say that a result must not be depended on", while undefined "has the one value /(a)?/.exec("")[1] is
An earlier draft said undefined to undefined unassignable to undefined, which took the whole T | undefined optional idiom with it" — and an optional
The analysis is syntactic and reads only the pattern, which is why it costs nothing an engine does not already spend: the same parse that reports an invalid pattern yields the ?, *, or {0,n}, a group in one arm of an alternation whose other arm a match may take instead, and a group inside a lookaround are the cases a match may complete without entering, and each widens the
The empty-RegExp.<[], {}> names: a bare [] [].<T>. An earlier draft made it the empty ARRAY type, which was correct when written: [] denoted the dynamic array then, and was reassigned to the empty tuple afterwards. The reassignment did not reach this clause, so the choice made to let RegExp.<[], {}> name a no-indices a d flag adds and the match overload a g flag selects, are part of the deferred match-result surface, so the inferred type carries the two arguments the annotation forms in common use write and not a third for the flags.
A generic declaration introduces parameters that a later application binds to types or values, and each distinct application is a distinct type with its own
A generic parameter is a type (V where a value is required, as 2 ** V, reads the [[Value]] of the literal type, and a body that reads V where a type is required, as Reflect.typeOf(V), reads the type itself.
A generic declaration's type parameters are in scope, lexically, for its signature and body, including every function and class the body contains. Resolving an uint32: type, therefore shadows that type in the body as it does in the signature, and let T outside function f<T: type>() does not reach its body. For a class the scope is its class scope, where the class binding is.
Arguments to a computed type's builder are expressions and use this same lexical resolution and value view. A bare
A runtime parameter read retains the parameter declaration and its actual binding instance selected by lexical resolution. Obtain its argument from that instance, not from a same-named parameter of a caller or another application of the same declaration. A nearer ordinary binding retains its shadowing and initialization rules. A parameter whose argument is not yet available does not fall through to an older binding. An otherwise unresolved reference remains unresolved, including for typeof.
Declaration computations performed before a function activation exists, including argument defaults, inference trials, alias computations and ref can borrow, and grants no additional reference permissions or liveness.
Two applications name one A.<0> and A.<-0> are distinct and A.<NaN> is usable, following from identity rather than as special cases. A reference value cannot be an argument, since
Applying a generic signature uses two distinct source environments. The written arguments belong to the application; the domains, bounds, defaults and where clauses belong to the declaration. A returned function or an instantiated member retains the enclosing substitutions of its signature. Determine the obligations of each application as follows:
An evaluated generic interface retains the context of its actual declaration binding, including
where clauses in their original declaration environments under the applicable any. Infer from the actual argument under the existing runtime rules and enforce the constraint. A written type argument any remains an explicit argument and is judged by the existing gradual admission rule; this clause adds no prohibition on it.Callable admission uses the effective constraint descriptions of both signatures under their respective declaring environments and any type. The signature's own unbound parameters remain opaque: this preparation does not apply the signature, choose its type arguments or evaluate an unused parameter default. A source still requiring unavailable inputs retains its obligation under the existing deferred-computation rules.
Apply
The following program evaluates to outer, both the returned function and its V. The earlier missing description of the computed alias constraint does not make the target generic parameter unconstrained. A function value constrained this way cannot satisfy a generic target admitting arbitrary types or uint8 merely by crossing through
function identity(t: type): type { return t; }
function outer<T: type>(x: T) {
type R<U: type = T> = identity(U);
return function f<V: type extends R.<> >(y: V): V { return y; };
}
const fn = outer.<string>("x");
fn.<string>("ok");
Instantiating a function value's
Use that same instantiated publication for return admission and for the contextual result of a concise function body. A retained helper continues to denote its original closure and environment. Do not rebind the helper to make the source agree with an incorrectly instantiated target. Independent activations retain independent bindings, and failed admission does not alter another function value's
This program evaluates to V retains the T declared by outer, bound to String; the independent T of middle is bound to uint8. The runtime source and T does not change the result.
function outer<T: type>(x: T) {
function bound(): type { return T; }
return function middle<T: type>(x: T) {
type R<U: type = bound()> = U;
return function f<V: type extends R.<> >(y: V): V { return y; };
};
}
const mid = outer.<string>("x");
const fn = mid.<uint8>(1);
fn.<string>("ok");
A selected method case retains the bindings of the receiver's instantiation of its declaring class. Apply those bindings to its parameter and result descriptions along with the selected case's own bindings, preserving the identities of the case's own parameters and captures. Selection does not restore an unspecialized declaring-class result after member lookup has established the receiver's arguments. An inferred binding initialized by that result is subject to its resulting contract, not to a fresh lookup of the declaring class's parameter names at the binding
At a boundary with an instantiated class result, membership follows
A default description follows the same free-parameter substitution as a constraint description. Substitute enclosing bindings wherever the default description refers to them, including within arrays, tuples and callable signatures, while preserving binders introduced inside the description. A default that itself
The explicit argument in R.<T> occupies U even while T is unavailable to an earlier description pass. This program evaluates to fail. Using R.<> instead requires the default and fails at the prescribed checking boundary.
function identity(t: type): type { return t; }
function fail(): type { throw new Error("default ran"); }
function outer<T: type>(x: T) {
type R<U: type = fail()> = identity(U);
return function f<V: type extends R.<T> >(y: V): V { return y; };
}
const fn = outer.<string>("x");
fn.<string>("ok");
A recursively encountered default source supplies no completed result merely because its description is already in progress. Describing or publishing a generic declaration, including a class, does not apply its defaults. A default is required only when its parameter remains unbound at the default step of the applicable binding operation. If description is suspended, retain that application's source, parameter identities, preceding bindings and
For type R<T: type = R.<> > = T, declaring R alone or using R.<string> does not require the default. An application requiring R.<> cannot complete argument binding and exhausts the evaluation budget. In contrast, type R<T: type = R.<string>> = T has the string. An implementation may suspend speculative description on source-node re-entry, provided that marker is not used as a completed query key or a source-level cycle verdict and the required application is still discharged.
Determine whether a type source depends on an unbound parameter by its lexical binding dependencies. A declaration-local alias does not remove such a dependency: follow that alias's declaration and, for an applied generic alias, its own argument bindings. Repeat through dependent aliases while respecting binder identity and the existing recursive-type rules. An unbound dependency is retained for the application that can supply it; it is not a closed type computation with a missing global name. Once the application supplies the required bindings, discharge its constraint normally. Alias indirection does not weaken admission or postpone a statically established refusal.
For a required constraint or omitted-argument default that remains computed, determine its inputs by lexical declaration identity, including dependencies reached through a helper function or alias. A helper retains its own declaration environment: a parameter of the applied signature with the same name does not replace the helper's
A generic alias's
try statement cannot catch a failure that must be reported before body evaluation.These requirements do not prescribe cache data structures, synthesized environments or a particular compiler pass. They do not introduce eager evaluation of unused defaults, deepen the admission of any, or establish initialization or reference liveness from the successful computation of a type.
The outer makes it participate in result inference. Its R, including the computed alias body. The stored application in use completes its default and has a String parameter, so this program has a use is never called. Replacing g(1) with g("ok") admits the consumer.
function identity(t: type): type { return t; }
function outer<T: type>(x: T) {
type R<U: type = T> = identity(U);
return function inner<V: type = R.<> >(y: V): V { return y; };
}
const fn = outer.<string>("x");
function use<T: type>() {
const g = fn.<>;
return g(1);
}
The omitted default reads the uint32 value 16, so its array has two elements. This program evaluates to N has the same result.
function sized(n: uint32): type { return type [n / 8].<uint8>; }
function outer<N: uint32>() {
return function inner<U: type = sized(N)>(x: U): U { return x; };
}
const fn = outer.<16>();
fn.<>([1, 2]);
"ok";
The earlier parameter N belongs to the signature being applied and shadows the outer binding while its default is evaluated. The written argument 16 is resolved at the call
const N = 8;
function sized(n: uint32): type { return type [n / 8].<uint8>; }
function f<N: uint32, U: type = sized(N)>(x: U): U { return x; }
f.<16>([1, 2]);
"ok";
Each inherited method retains the receiver's instantiation C.<8>. The derived class adds no storage, so the results cross their declared base boundaries and this program evaluates to 4 is also valid and uses C.<4> throughout.
class C<N: uint32 = 4> {
begin(): C { return this; }
m<T: type>(x: T);
m<number>(x: number): C { return this; }
end(): C { return this; }
}
class D extends C.<8> {}
new D().begin().m.<number>(1).end();
"ok";
Membership in the generic base does not permit losing the subclass's additional storage. The unannotated parameter supplies C as a reference class instead admits the derived value.
class C<N: uint32> { x: uint32 = 1; }
class D extends C.<8> { y: uint32 = 2; }
function take(v): C.<8> { return v; }
take(new D());
The helper's T is Number; the parameter named T in inner is independent. The shown call is valid. Replacing its second argument with
function outer<T: type>(x: T) {
function captured(): type { return T; }
return function inner<T: type, U: type extends captured()>(x: T, y: U): U { return y; };
}
const fn = outer(1);
fn("s", 2);
The explicit empty argument list requires the omitted argument's default. This program has a fn.<number>, does not evaluate that default. The same distinction applies to a default written [T, fail()].
function fail(): type { throw new TypeError("default"); }
function outer<T: type>(x: T) {
return function inner<U: type = fail()>(x: U): U { return x; };
}
const fn = outer(1);
fn.<>;
The first application below retains Number as the bound of U. The final call is a fn.<string> is also a
function outer<T: type>(x: T) {
return function inner<U: type extends T>(y: U): U { return y; };
}
const fn = outer(1);
fn("s");
The T in the written argument list belongs to caller; the T in inner's bound belongs to outer. The expression evaluates to caller("s") instead fails the
function outer<T: type>(x: T) {
return function inner<U: type extends T>(y: U): U { return y; };
}
const fn = outer(1);
function caller<T: type>(x: T) { return fn.<T>(x); }
caller(2);
The stored U and evaluates to applied("s") instead is a
function outer<T: type>(x: T) {
return function inner<U: type extends T = T>(y: U): U { return y; };
}
const fn = outer(1);
const applied = fn.<>;
String(applied(2));
The local alias retains the enclosing parameter dependency. The call evaluates to T into a global lookup.
function outer<T: type>(x: T) {
type Limit = T;
return function inner<U: type extends Limit>(y: U): U { return y; };
}
const fn = outer(1);
String(fn(2));
Return inference retains the array bound when examining the nested declaration. The shown application is valid; substituting [].<any> instead fails the existing adjacent-rest contract. The bound constrains T but is not its argument.
function outer<T: type extends [].<any>>() {
function f(...a: T, ...b: [].<uint8>) {}
}
outer.<[].<uint8>>();
Creating fn and then explicitly supplying Number do not use the computed default. This program evaluates to fail() is an element of a tuple default.
function fail(): type { throw new Error("default"); }
function outer<T: type>(x: T) {
return function inner<U: type = fail()>(y: U): U { return y; };
}
const fn = outer(1);
String(fn.<number>(2));
The abstract operation BindTypeArguments takes arguments params (a new, a heritage clause, an explicit call, and the explicit half of a call whose remaining parameters are inferred. Its result is the bindings that
Type aliases and interfaces perform this binding before expansion or canonicalization, just as classes and functions do. Parameter domains and bounds are obligations of the application even when the body does not use the parameter. Named arguments, defaults, packs and dependent bounds use the same
Retained application descriptions remain associated with their producer's source declarations when another Script or Module specializes that producer. A consumer's bindings do not replace the producer's descriptions or the creating context of another application. Retaining an obligation does not create a distinct nominal type for a transparent alias, demand a bypassed default, execute an ordinary function body during type preparation, initialize a binding, or establish reference liveness.
This Script is refused before its body is evaluated. R.<uint8> violates U's bound even though the alias expands to string, and no call of fn is needed. Applying outer.<string>("x") instead is valid.
function outer<T: type>(x: T) {
type R<U: type extends string> = string;
return function f<V: type extends R.<T> >(y: V): V { return y; };
}
const fn = outer.<uint8>(1);
During checking, distinguish a parameter with no supplied argument from a parameter assigned an argument whose description is not yet available. The latter remains supplied and pending: do not select its default, infer a
If an unresolved spread or argument-dependent distribution prevents establishing the supplied runs, retain the pending assignment; an empty
For a function application, select defaults only after the applicable inference rungs. Retain each selected default's obligation with that application, its argument assignment, preceding bindings and declaration context, even if an unrelated supplied argument is still open or the callable's result does not mention the defaulted parameter. Availability depends on the selected default's determining inputs, not on all arguments of the application being closed. When enclosing
Completing an application's defaulted bindings does not discharge value-argument checks that depended on those bindings. Retain the dependent consumer with its source-position inputs and argument-to-parameter assignment, and apply the ordinary admission judgment when the receiving contract is determined, including when an enclosing
This Script is refused before its body is evaluated. The default completes W as uint8, after which the String argument fails the ordinary admission judgment. Applying outer.<string>("ok") instead is valid.
function identity(t: type): type { return t; }
function outer<T: type>(x: T) {
function inner<U: type, W: type = identity(U)>(y: W): W { return y; }
return inner.<T>("bad");
}
outer.<uint8>(1);
This Script reports a default-evaluation U is explicitly supplied, so its default is bypassed; W is omitted and its closed default is required. If W instead has a default that depends on U, that obligation is retained and checked when the enclosing application supplies string.
function fail(): type { throw new Error("default ran"); }
function outer<T: type>(x: T): T {
function inner<U: type = fail(), W: type = fail()>(y: U): U { return y; }
return inner.<T>(x);
}
outer.<string>("ok");
where clause of declaration, evaluate it over bindings as A value parameter takes a value (uint8 supplied for N: uint8 would otherwise pass
An ordinary concrete constraint is checked with any is "the type of every value ... the type of a binding that carries no annotation" (
The positional half is the rest-parameter rule verbatim: id.<T: uint8> has no positional argument for T and is not unmatched. A pack whose bound reads a parameter declared at or after the first pack cannot help split, so it admits everything for the split and is checked in full once the assignment is fixed, the conservatism argument binding already applies to a parameter type that mentions a not-yet-bound type parameter. A literal is a type too: an unconstrained type parameter admits 0 positionally, so in <T: type, ...I: [].<uint32>> applied .<0, 1> the fixed T takes the literal type 0; the result is deterministic, and writing T: uint8 says what was meant.
Two applications whose bindings are pairwise the same —
A any type, which completes the
Within the specialized body, a variadic value parameter — one whose [[Kind]] is I.length, I[k] for a constant k, and the array methods over them are compile-time expressions (I === I across calls of one where clause over a
A pairUp<T: type, ...Rest: [].<type>>(p: [T, ...Rest]), by the assignment f(...xs), binds a xs is statically known, as the type-argument spread of N: uint32 = I.length — it is a computed default of
Transforming a pack is not a mapped-type sub-language. A value pack is an array in the body, so I.map(i => i * 2) inside a compile-time function transforms it; a type pack restructures by spread in a tuple type, [...Ts, T], and transforms per element through a builder (ref on a rest parameter ((e: Entity, ref ...refs: Cs) => void. Most inversions are better avoided than declared: a declaration that binds the pack as what the caller passes, all<...Ps: [].<type> extends [].<PromiseLike>>(...ps: Ps): Promise.<awaitedAll(Ps)>, needs only the structural rung and yields the types the inverted spelling would, types being structural.
Three rules follow the binding. A spread ARGUMENT, tup(...xs), binds a pack only where the spread's ...xs for xs: Ts is when a pack forwards — and a spread of a dynamic array into a rest d.<0, 1, 2>() for d<...I: [].<uint32>, M: uint32 = I.length> binds M to 3. And typeof applied to a parameter's name reads what the name resolves to in the body: a
A read<T: type>(): Reader.<[...Ts, T]> — is
A generic function applied in expression position, f.<uint8>, f at the bindings f.<uint8> === f.<uint8>, f.<uint8> === f.<T: uint8>, a Map keyed on one round-trips, and arr.map(f.<uint8>) runs the specialized body; two spellings of one binding list are one value because identity is the bindings and not the text. A where clauses are evaluated when it is created, as the last step of obj.m.<uint8> is the obj.m obj.m.<uint8>(x) supplies obj as this exactly as obj.m(x) does. The bare name keeps the generic signature: Reflect.typeOf(f) is <T: type>(x: T) => T, and Reflect.typeOf(f.<uint8>) is (uint8) => uint8.
Determine the any type nor a missing explicit argument. Complete that dependency and reconsider its consumers at the prescribed checking boundary. This includes a := declaration whose initializer is the application. A later call uses the completed non-generic signature and does not infer the already bound parameters again. The requirements for available inputs,
When a binding is established to hold that application, retain the dependency for judgments consuming the value through that binding or an established alias. Apply the ordinary binding identity, stability, conversion and effect rules when establishing this relationship; a dependency of an initializer is not by itself evidence that its result is the same callable. Retaining the relationship does not publish an incomplete callable
The producing application's enclosing generic bindings are identified by their declarations. A same-spelled parameter introduced by a consumer does not replace those inputs. The original application retains ownership of its default evaluation; storing or aliasing its result does not select defaults anew. These requirements execute no ordinary function body during checking, initialize no value binding, and supply no independent evidence of reference liveness.
Successful evaluation of a stored application retains with its normal result the selected declaration's instantiated callable contract, including its any remains erased. Reading this contract executes no ordinary function body or parameter initializer and does not bind the application's arguments again.
When describing a selected case's parameter and result types, treat its captures as binders owned by that selection. Apply the completed selection bindings after the declaration's
Before admitting that result to an inferred immutable binding, complete only corresponding unresolved dependencies of the independently established destination contract by the callable correspondence rules of
For argument binding, a
An implementation forwarding a
function identity(t: type): type { return t; }
function outer<T: type>() {
function inner<U: type, W: type = identity(U)>(y: W) { return "ok"; }
const fn = inner.<T>;
return fn("bad");
}
outer.<uint8>(); // Type error before the containing Script body runs.
const N = 8;
function sized(n: uint32): type { return type [n / 8].<uint8>; }
function f<N: uint32, U: type = sized(N)>(x: U): U { return x; }
const g := f.<16>;
g([1, 2]); // U is [2].<uint8>, determined before this call.
A call written with type arguments, f.<uint8>(x), creates or finds the swizzle.<0, 1>() and the reflection API's getReflection.<Reflect.Class, T>() are this form. Nothing here is new to a type parameter in a type position: a class application A.<uint8> has been a distinct type with its own
A generic function that crosses into a binding of a concrete function type is specialized at the crossing: the bindings are those the instantiation step of where clauses are checked there, and every call through the binding is a direct call of one body, so the crossing moves the decision to the assignment and costs the call nothing. A generic function that crosses into a binding of a generic function type crosses as itself. A call through such a binding, m.<uint8>(x), selects the
For a crossing from one generic signature to one concrete signature, retain the bindings inferred by signature instantiation. Bind them against the held function's own declaration and materialize the same interned
Compatibility and materialization are distinct operations. A successful where clauses; a receiving operation must use the converted value even when a compatibility check can be elided. The callable view's parameter names and defaults continue to govern calls through that view without changing
For a union receiving a generic callable, retain the first compatible member selected in canonical order by the successful membership judgment. If it is a concrete callable contract, materialize at that contract; if it is generic, retain the generic value. Several compatible members do not form a combined callable contract. An abrupt declaration check after selection propagates before publication and does not cause selection to continue at another member. Retain the selected member as local admission evidence rather than repeating membership: unsuccessful alternatives contribute no selection, and a constituent property's selection is not a selection for its enclosing value. Check elision preserves this choice and its required materialization. A callable view with one non-nullish member retains that member's parameter names and defaults when invoked after nullish alternatives have been excluded; this does not combine multiple callable alternatives into an overload group or make a nullable value statically callable without the required proof.
The cost is that of an indirect call together with a lookup keyed on callee and bindings, the dispatch C# performs for a generic virtual method, and it is paid only at calls through a binding of generic function type. The common case — a generic function handed to a arr.map(id) — pays nothing beyond the assignment. An implementation may cache the lookup at the call
The abstract operation SubstituteType takes arguments t (a
The [[Declaration]] identities in bindings determine substitution throughout a nested signature. An enclosing application supplies no binding for an inner signature's own parameter, even if the names are equal. A bound parameter's
Substitution and evaluation are one pass: a deferred application becomes evaluable exactly when substitution has closed every parameter it read, at which point ... whose [[Type]] substitutes to a [...Ts, T] under Ts bound to [uint8, string] is [uint8, string, T] and not a tuple holding a tuple;
Resolve a type name to its nearest binding under the ordinary lexical and module rules. If that binding is a source class declaration binding, its type denotation is the class associated with that evaluated binding instance, rather than the value currently stored in the mutable binding. This applies equally to an annotation, a type argument and the operand of the type operator. An ordinary read of the binding, including the operand of new, a heritage expression or an argument to a reflection operation, still produces its current value. Reflection of a
Publish the outer declaration's completed denotation only after class evaluation, including decorators and static initialization, and initialization of its binding have succeeded. Associate it with that binding instance and retain the original evaluated identity,
The outer binding remains uninitialized while runtime class decorators and their registered initializer callbacks run; a decorator receives the created class through its context. After successful decoration, initialize the binding with the
A later Script in the same
This is not a separate type namespace. A nearer ordinary binding, including an uninitialized binding or a binding selected by dynamic lookup, blocks an outer class denotation. It
Keep the
V. In the last Script, type V V and new V() use the
// Script 1
class V {}
class W extends V {}
// Script 2
V = class {};
// Script 3
const value: V = new W();
Each evaluation that creates a source class declaration or expression introduces a distinct evaluated class identity. For a generic class this is its family identity; a nongeneric class retains the identity with an empty [[Arguments]]
Retain evaluated class identity when a nongeneric class occurs as an argument of another type, including nested application arguments and structural type components.
Within one evaluated family, the same
A checked generic binding whose class dependencies still name declaration templates must instantiate those dependencies for its executing use from the corresponding declarations' lexical binding instances. Apply this to nested application arguments and containing type shapes, retaining the any argument view and an exact any entry in an anonymous family constraint retain their respective rules.
When checking uses a class declaration established through a module import, retain the resolved exporting
For an ordinary immutable binding initialized directly by a class expression, retain the class evaluation corresponding to that initializer only after the binding initializes successfully. Parenthesized initializers preserve this correspondence. Retaining the dependency requires the created class and the initializer to have the same source declaration; an arbitrary
Completed-
A bare family's instanceof test considers only that family's specializations and their actual prototype chains. Partial extensions and the runtime contracts they add belong to the family reached by the declaration's binding, including applications already created for it. Selecting a
"The class is the type a construction yields" (new C new . Reflect.construct whose target is such a new C.<…>
A generic function call also checks completeness after its explicit, contextual, value-argument and default inference steps, including a call with no value arguments. A required parameter that these steps provably leave unbound is a any binding is invented.
A parameter binds, taking the first source that fixes it:
any binding nothing. A binding so made is fixed before the value arguments are looked at and is treated exactly as an explicit one, so the arguments are then checked against it: const b: Box.<uint8> = new Box(1) binds T to uint8 and reads the literal 1 at uint8, and const b: Box.<uint8> = new Box("s") is a new Box((1 := uint8)) binds T to uint8, new Pair((1 := uint8), "s") binds A and C, and new G(4) for a value parameter N: uint32 binds N to the literal 4 converted to uint32. An untyped literal argument contributes its LITERAL type, and a parameter binds the JOIN of what every argument contributes to it: add(200, 100) for a parameter constrained uint8 binds 200 | 100, and pair("a", "b") for one constrained "a" | "b" binds 'a' | 'b'. The join is then checked against the parameter's evaluated constraint, which admits a literal that fits it on the terms 200 satisfies uint8 and 300 does not. Where the parameter has NO constraint the binding widens to the literal's base, so new Box(1) with no context is Box.<number>, as f(1) is for a generic function; a CONSTRAINED parameter keeps the literal, the constraint being the Within the construction new.target is the new C.<…>, and the instance's prototype and super() from a class extending a Reflect.construct with the declaration as target and an unrelated newTarget is a
Inference beats a default: new A(5) for a class A<T: type = uint8> { constructor(a: T) } is A.<number>, since the argument reaches T before the default is consulted; new A.<>(5) and let a: A = new A(5) are A.<uint8>, the default binding where nothing reaches the parameter or the context names it.
The contextual rung is read BEFORE the arguments, not after them as a fallback, and the order is what makes the ordinary spelling work. An untyped literal binds number when it is looked at first, and Box.<number> is not a Box.<uint8>; read first, the context fixes T and the literal takes it, which is exactly what writing new Box.<uint8>(1) does. The context-first reading accepts strictly more than the argument-first one and never something the other would accept for a different reason: everything it produces has the context's type, and its arguments were checked against that type's parameters. The one visible difference — an argument whose own type contradicts the context — is reported at the argument, where new Box.<uint8>((1 := uint16)) reports it.
The two sides of the checker and the run time compute the same bindings from the same inputs, the checker over Static Types and the run time over values, and a construction's Reflect.construct — the run time binds alone, by the same ladder.
Collections are unaffected. Map's new Map() remains the untyped collection of let m: Map.<string, uint8> = new Map() remains the adoption of
A generic declaration may carry a where clause, written with the where over value generics, as where I.every(i => i < N) on a variadic pack, is the where that inspects a runtime value rather than a generic parameter is the dependent record type extension's, checked at the boundaries where naming return on a this is unbound in a clause over a function's parameters, whose subjects are the parameters themselves, and a predicate that reads it is a this is a different rule with a different subject. A result contract requires a declared return type or a void it is a where i > 10 refuses a call that omits i, and where i === undefined || i > 10 is how a clause exempts the omission.
Evaluability of a
Source checking of a generic type alias application uses the declaration selected by lexical name resolution and the bindings produced by the ordinary type-argument binding rules. Resolve named arguments, defaults and variadic or spread arguments before judging a clause. A same-spelled declaration in another scope contributes no clause to that application. Omitting an argument list where defaults supply all arguments creates the same checking obligation as an explicit empty argument list.
A closed
If an application's bindings still depend on an unbound generic parameter, retain its clause obligation along with that dependence. Apply the obligation when substitution establishes the required inputs, even if the alias's resulting type no longer mentions the parameter. A successful application with different bindings supplies no evidence for this one. Successful verification may be reused only for the same clause and semantically equivalent inputs, preserving type and value identity distinctions, including nominal declarations and signed zero. Identifier spelling and formatted diagnostic text do not establish that equivalence. Runtime-subject and unavailable-input cases keep their existing deferral rules; a resource limit follows
The clause is evaluated by where is a check, not an inference: it narrows which applications are legal, and never changes what a parameter is bound to.
At a source-checking boundary, a closed
The required imported-input rule follows a dependency reached through a helper even when the generic declaration and its predicate belong to the module being checked. Preserve the helper's original lexical environment through direct imports, namespace accesses and re-exports. A helper may in turn import a binding from the checking module through a cycle; an attempted uninitialized read of that binding remains a required-input failure even if a handler replaces its completion. An unselected operand performs no read. These rules neither run a producer's initializer early nor replace its
These rules classify the completion of a required checking-time evaluation. They do not wrap an ordinary runtime exception from a predicate whose evaluation is deferred until runtime. A helper's own uninitialized local read, once the helper is legitimately being evaluated, is an ordinary evaluation failure; it is not an unavailable external input. Likewise, catching an artificial premature read cannot establish a Boolean fact about a predicate that required an unavailable environment.
A
Every required readable member must supply a type assignable to the primary's promised readable type. A method must preserve its callable signatures, including receiver, argument and result contracts. Reference parameters preserve their mode and invariant target type; reference returns retain their existing invariant compatibility and liveness rules. Every primary
Each substituted primary
Judge closed obligations when the declarations are checked. Retain obligations dependent on captures or unbound parameters and discharge them when an application establishes the bindings. Missing required members can be rejected without waiting for their types to close. No
This clause states what a variance declaration means; a parameter carrying none is invariant, which is the rule out and contravariant by in (readonly
| Position | Polarity |
|---|---|
| A method or function return type | Output. |
A readonly | Output. |
| A method, function, or | Input. |
A non-readonly | Both, so only an invariant parameter may appear. |
The target type of a mutable reference, including a ref parameter or a ref return | Both, regardless of the enclosing polarity. |
| An argument to a covariant parameter of another type | The enclosing polarity. |
| An argument to a contravariant parameter of another type | The enclosing polarity inverted; |
| An argument to an invariant parameter of another type | Both. |
Polarity composes recursively. Within a function type, its parameter types invert the enclosing polarity and its return type preserves it; a writable position imposes
The variance of a nested application is read from the declaration resolved in its lexical scope, including a type alias. Named type arguments are associated with their declared parameters before polarity is composed; changing the written order of named arguments does not change variance. This judgment applies to a function or method's own generic parameters as well as to the parameters of classes, interfaces and aliases.
This is the variance of C# and Kotlin, sound because the well-formedness rule confines a covariant parameter to positions a value is read from and a contravariant one to positions a value is written to. A Producer.<Dog> is usable as a Producer.<Animal> where Producer is declared covariant and only produces, which the container invariance of Map precisely because a Map also consumes. The relation composes with the others: an instantiation is a subtype along a covariant position by the ordinary subtyping of the arguments, so a covariant position holding another generic type carries the polarity inward, which the last two table rows state.
A partial class or interface
out, in.
A declaration is a claim rather than a derivation, so it can be wrong, and this is the rule that catches it: out on a parameter appearing in a non-readonly
A generic parameter with an [[Arity]] of zero stands for a type. A higher-kinded parameter has an [[Arity]] of n > 0 and stands for a generic declaration of n parameters: something that becomes a type once applied, rather than a type. This clause states how one is declared, what may be bound to it, what applying it means, and how the four clauses above read when a parameter is one (
A higher-kinded parameter's arity is written and never inferred. Each hole in its own
A parameter is higher-kinded when its _ tokens, as W<_> for an [[Arity]] of 1 and W<_, _> for 2. The count of tokens is the [[Arity]].
_ is the pattern % already is: % is the remainder operator between two operands and the pipeline
Within the declaration that introduces it, a higher-kinded parameter may appear only applied, or as the argument for a higher-kinded parameter of the same [[Arity]], which forwards it. It is not a type, so a type position naming one unapplied is a W.<X> means the same as writing the bound declaration applied to X directly.
Before any application has bound it — while the declaration's own interior is checked — a higher-kinded parameter resolves to itself, exactly as a parameter of arity zero does. This is what lets a declaration be checked once rather than once per application.
An application of a higher-kinded parameter supplies exactly its [[Arity]] arguments, positionally. It is a
A bound declaration whose parameters carry defaults does not relax the count. W.<> for W<_> is refused even where every declaration the program binds to W happens to default its parameter, since that is a fact about the bindings and the rule is a fact about W; judging it per binding would need every admissible binding, the search the quantified constraint below is refused for. Rust reports the same mismatch on a generic associated type, whose applications name their full argument list.
A higher-kinded parameter may carry a default, on the same terms as any other parameter and with the same well-formedness rule: a parameter carrying a default may not precede one that does not. This clause adds no exception, so a declaration wanting a defaulted wrapper places it after its required parameters, and Iterator.<T> reads as it did before the parameter existed.
The rule is what decides the parameter's position rather than the other way round. A wrapper defaulting to the identity alias is the least interesting parameter at most use sites, so placing it last costs nothing and keeps every existing annotation naming the type unchanged; a type AsyncIterator<T: type, R: type, N: type> = Iterator<T, R, N, Promise> — so what is deduplicated is the members, not the names.
An argument bound to a higher-kinded parameter must be a generic class, interface, or type alias whose parameter count equals the parameter's [[Arity]], or a higher-kinded parameter of that [[Arity]]. Two failures are distinguished, because they are different mistakes and a single diagnostic naming neither would leave a reader to work out which occurred:
Determine a written argument's role from the receiving parameter after spread expansion and argument assignment. In a higher-kinded
An already-applied type is not an unapplied declaration and cannot satisfy a higher-kinded parameter by discarding its arguments. Defaulted parameters still count toward a declaration's arity. An ordinary parameter of arity zero retains the empty-application meaning of a bare generic name. Selecting a declaration creates no initialization or reference-liveness evidence and does not discharge the domains and bounds of a later application of that declaration.
A where constraint (where W.<T> is
The quantified form is refused for a reason of correctness rather than of difficulty, and the reason is this specification's own. Type-position evaluation is metered (
Declared variance (
A higher-kinded parameter read as a value (
A higher-kinded parameter is bound only by explicit application. It is never inferred from an argument's type.
Inference is declined for the reason the quantified constraint is. Recovering W and T from a single argument of type Promise.<uint8> admits at least two answers — W is Promise and T is uint8, or W is Identity and T is the whole promise — and both are consistent with the argument. Choosing between them is a search, and this specification meters computation rather than search. Requiring the argument costs a reader one type argument at each application and buys an evaluation that terminates with the same answer under every budget.
A type alias declaration may carry where clauses, each a predicate over the value the type describes. What they state is what a property's own type cannot: that a credit card and a billing address are present together, or that a postal code's pattern follows from a country. The narrowing form v is T (
The
A this bound to the value under test, and it holds when match expression is an where match (this.status) { ... }, and in this position it carries an implicit default: *false*; clause: a predicate that matches no clause has answered that the value is not of the type, which is the answer a predicate exists to give. It is therefore default overrides the implicit one. The conditional form evaluates its else is present. It is an this, which is what admits the same predicate at compile time, where it decides a constant, and at run time, where it checks a value.
type N = T where P ; binds N to the where property whose value is P as a function of this }. Each further clause _T_ where _A_ where _B_ is the refinement by B of the refinement by A of T, and conjunction, subtyping, and membership follow from that nesting rather than from a rule of their own.
The where where key. Its validate of a value against a constraint is the evaluation of the constraint's where with this bound to that value; its subtype holds where the required constraint is unconstrained, so a predicate is shed, or where both carry the same predicate; its default is the unconstrained constraint, and it defines no other hook. A dependent record type therefore admits a value of its base only where every predicate holds of it, is a subtype of its base and sheds its predicates upward freely, and is reached from its base only by a crossing that runs them.
The refinement is Payment is usable wherever its bare object type is and that going the other way is a check. The string.<{ pattern: … }> in the same document that writes a payment's cross-where, and the two are one mechanism over two bases.
Because a predicate is a function, the structural equivalence of where text declare two types, where two that write stringPattern(/^[0-9]{5}$/) declare one. That asymmetry is deliberate and is not an accident of the encoding. A predicate is code and may close over its scope, so where this.total < LIMIT in two modules means two things whenever their LIMIT differs; comparing predicate source would make those one type and would be unsound. A regular expression is data and carries no scope, so comparing it structurally is safe. A program that wants one dependent record type across modules declares it once and imports it, as it would a class.
Conjunction by nesting also gives the diagnostics: a value failing _T_ where _A_ where _B_ fails at the innermost refinement whose predicate is false, so the clause that rejected it is the clause named.
v is T is v is of the type T is asks. Its grammar is given with the conversion operator (
is is the branch a program takes instead of the throw: if (payment is Payment) tests what example(payment) would have required. Against a dependent record type it runs the predicates, so it is also the only way to ask whether a value the program has been mutating is valid again. Against any other type it is the same structural test every boundary makes, so v is uint8 and v is { x: float32 } are ordinary questions with ordinary answers, and the accessor and higher-order reading of
A == or ===, between one member access on this and a else or its conditions exhausting that type. In the match form, when the subject is one member access on this whose declared type is such a union or or of them, no two clauses name one constant, and the clauses name every constant of the type; each clause is then a branch, its condition being its pattern and its predicate its arm body. Such a chain denotes a union: one member per branch, and where a branch tests several constants one member per constant, each the type's base with the discriminant narrowed to the constant tested and with each this is T or this.p is T, applied by where of its own and contributes no members.
The denoted union serves the exhaustiveness check of Reflect.typeOf reports it, and assignability compares against it. A chain that does not qualify, a nullish or ordering condition, conditions over two members, a chain with no else over a type it does not exhaust, denotes nothing but itself and its match writes a default.
The Address qualifies, one member tested against two literals with a final else, and it denotes exactly the discriminated union its document prints "for reference", so this clause writes down an equivalence the
Where the v is a dependent record type whose predicate is the conditional form, and the narrowing facts in force at a position decide that predicate's this as v: a branch predicate that is a narrowing form of this contributes what that form contributes over the corresponding v, so this is T contributes what v is T does, this.p is T what v.p is T does, and this.data !== undefined what v.data !== undefined does; a conjunction contributes both sides' facts; any other predicate contributes none. The conditional and match forms both take their branch this way, and the match form's chosen clause is the one whose pattern the facts decide.
An expression is decided by the narrowing facts when evaluating it is this as v and reading a
This is what lets if (addr.country == 'US') narrow addr.postalCode to USPostalCode without the program writing a discriminated union, which is the case the where against. The chain is short and each link is already specified: the literal-equality row of addr.country the 'US', that fact makes this.country == 'US' a compile-time-evaluable expression over a known value, deciding it selects the then branch, and that branch's this is { postalCode: USPostalCode } narrows exactly as the is would if the program had written it.
The rule is deliberately not a prover. Where a predicate is not decided, nothing is narrowed and the boundary check runs, which is the 'strict' name is assigned a 'loose' mode: the checker cannot know that one pattern's language contains the other's, so the check at the next boundary is what catches it. Sharpening that particular case is the pattern subtype judgment's business (
A metadata parameterization carries a value that float32.<{ m: 1, s: -2 }> is an acceleration whose dimensions combine under multiplication, float32.<{ bounds: 0.. }> is a bound that a comparison can narrow, and decimal128.<{ scale: 2 }> is a price that rounds where it is stored and not before.
The production below restates the
A default or subtype, a default whose value is not of the
| Hook | Required | Meaning |
|---|---|---|
default | Yes | The unconstrained constraint: what a value carries where it has no |
subtype(sub, sup) | Yes | Whether a value constrained by sub may be received where sup is required. This is the |
validate(value, constraint) | No | Whether a value of the base satisfies constraint. This is the |
narrow(current, op, value) | No | The constraint that a comparison op against value establishes, given current ( |
conversionFactor(from, to) | No | A from to to. Number, BigInt, sized |
rescale(constraint, factor) | No | The constraint constraint factor, so that a bound in metres survives a conversion into kilometres. |
quantize(value, constraint) | No | The value that value becomes under the representation constraint requires, such as a decimal rounded onto a scale. |
describe(constraint) | No | A String naming the constraint, for a diagnostic. |
meet(a, b) |
No | What the constraints a and b have in common: a constraint satisfied by exactly the values satisfying both, meet is read as declining, since the two answers mean the same thing to the caller: the intersection stands. uint8<{ bounds: 1..=10 }> & uint8<{ bounds: 5..=20 }> and uint8<{ bounds: 5..=10 }> are one type and one |
Every hook is required to be a pure function of its arguments,
A type violation the checker can decide is likewise an EARLY ERROR, and the error is a
The distinction it draws is WHEN, and it is observable: a let _n_: uint8 = "s" is decided without running and is the first; a value crossing a boundary the checker could not decide is the second.
Every violation this specification calls decidable is therefore reported alike by every implementation, at the same moment and with the same class of error. An implementation that deferred one of them to evaluation would run a program that others refuse, which is the fragmentation
The obligation is what makes one hook serve two phases. validate decides a literal at compile time and a value at run time; quantize folds a constant and rounds a computed result; subtype contributes only to the
meta is a is takes (meta T on one line is a Syntax Error today, and meta alone on a line is an expression statement that must stay one.
Before interning or comparing metadata, normalize every origin against the same instantiated
Normalization is idempotent and independent of origin. Equivalent metadata must have the same interning key, and reconstructing a reflected type must produce the same
A metadata value is drawn from a closed language, given by
The outer value supplied for a
Projecting a written metadata argument preserves every supplied member and list position. Distinguish a completed value, an unresolved value dependency and a completed invalid form throughout nested projection. An invalid leaf invalidates the supplied value; omitting that leaf or replacing its containing record with an empty record is not permitted. A closed invalid leaf is a
A metadata argument denoting a meta-type family
| Form | Equivalence |
|---|---|
| A Number, a String, a Boolean, a BigInt, or | |
| A numeric value of a sized | The numeric domain and its |
| A pattern, carried as its source and its flags | Identical source and identical flags. |
| A Symbol | |
| A record of metadata values | The same keys, and equivalent values at each, without regard to order. |
| A list of metadata values | The same length, and equivalent values at each index, in order. |
| A range, of one of the four shapes of | The same |
Nothing else is a metadata value. A function, an object other than the forms above, and
Admission precedes any copying that could erase the input's kind. Recognize supported numeric
Composite record and tuple values and Span values are not
For a record snapshot, obtain its own key list once, and in that order inspect each key's current own descriptor. For each enumerable property, perform
A cycle along the active record/list containment
When metadata reaches a hook or a reflected type description, materialize lists as Arrays, patterns as RegExp values, and ranges as Range values of their stored shapes. Ordinary records retain their ordinary member names: a property named __range or __pattern is not authority to create a different metadata kind. Reconstructing a reflected description uses the same admission rules and preserves the required
A range is admitted as a VALUE and not as an implementation of RangeBounds.<T>: the four shapes are the whole of what a metadata range may be. A class of a program's own that implements the interface has no structural comparison, could not be written into an RangeBounds.<T>, since that is what a hook receives and what a hook returns; what this table closes is the
Admission to the metadata value language does not bypass the declared Range.<T, S, E> RangeBounds.<T> 0..<10 and a default .. have different shapes; comparing their admission does not test origin independence. The generic argument rules of Range or RangeBounds.
A present endpoint of a metadata range must itself be an admitted metadata atom: one of the scalar or numeric values in
Each present endpoint is a RangeBounds.<T> RangeBounds.<uint16> uint16 endpoints on any
Every present metadata endpoint, including an open endpoint, must be a value of that declared endpoint domain after normalization. Do not wrap, clamp, widen the declared domain, rewrite inclusivity, or invoke a user conversion to obtain it. A 0..<256 bound in a RangeBounds.<uint16> metadata uint8 is valid; the same bound in RangeBounds.<uint8> is invalid. This judgment concerns stored metadata endpoints, independently of the produced-element fit judgment for ordinary range iteration. Defaults, written arguments, builders, captures, hook results and reflected reconstruction apply the same rule before publishing a completed parameterization; failures retain their existing checking phase and declaration-publication rules.
An empty range is a metadata value like any other, and it constrains its parameterization to no value at all: a validation admits nothing and the constraint lies below every bound. Equivalence over it remains structural, so two empty ranges with different endpoints are two types, both uninhabited. A range that constrains nothing is the full range, and a default, which is what makes a parameterization that omits the key and one that writes the full range under it one portion by the completion rule below, rather than two spellings of one thing.
The language is closed because three things depend on it and each would break for a different reason. Interning compares metadata rather than identity, which is what makes brand(uint32, 'UserId') written in two modules one type with no registry, and a form with no structural comparison would have no answer there. An
The representation is this specification's business and the cost is an implementation's. Metadata is settled where a type is written, so an implementation is expected to compare these forms directly and to materialize an object only where a hook is actually called. Nothing here is intended to cost a running program anything.
An unregistered Symbol is admitted deliberately, and it is the one form whose equivalence is not structural. That is what it is for: a module that mints a Symbol it neither exports nor registers has a metadata key no other module can write, so its parameterizations are its own, and two of them are one type exactly when they carry that Symbol. A registered Symbol behaves the other way, being the same Symbol in every
A
A meta NumberBounds<T: type extends Ordered.<T>>, which is bound to the base at each parameterization the uint8.<{ bounds: 1..=6 }> binds it to uint8 because uint8 is what is being parameterized — and it is the RangeBounds.<T> requiring an T that is
A
The unclaimed-key judgment applies at every completed parameterization construction, including a reflected description. A
Declaration-time key claiming resolves a generic
Prepare a meta declaration's
Conflict detection is distinct from protocol publication. Within one source check, an established
At the checking boundary, a completed declaration failure is a
For a local declaration pending solely on such an uninitialized direct source binding, retain any already established constraint keys as private source-checking evidence. Detect a conflict between established pending claimants before body entry, even when no parameterization consumes the key. This evidence postpones only the unclaimed-key judgment for the matching key in that source; it does not register a claim, publish a default or hook, or supply the base-form exemption. A different unclaimed key still fails. A failed declaration or unavailable imported input supplies no pending local claimant. When evaluation reaches a parameterization, apply the ordinary ownership and admission checks against successfully
The default expression, its called helpers and the snapshot's getter evaluations are type-position computation. Apply the evaluable-fragment restrictions and the existing shared budget throughout that computation, including when the declaration is reached during evaluation. Exhaustion abandons the computation and cannot publish any part of the declaration; during the source check the evaluation-limit diagnostic retains precedence over incidental declaration errors. Successful preparation does not repeat the default computation when evaluation reaches the declaration's position.
The abstract operation MetadataPortion takes arguments m (an
default.Throughout this clause, a portion is M's default when the two are equivalent as
A value's effective portion is total: a value carrying no metadata of M has M's default portion, whether or not it carries another
A default requirement applies at ordinary value boundaries as at operator arguments. When its effective source portion satisfies the required default without a conversion, the value is admitted unchanged, without acquiring a metadata carrier. Source metadata is inspected before considering the base or validation fallback: a value carrying a non-default portion cannot bypass that portion's subtype judgment merely because the required portion is the default. In particular, with exact dimensional subtyping, a plain scalar satisfies a written dimensionless requirement and a metre does not, including in an is test and after passing through a base-
This value admission grants no permission to convert a borrowed location. A ref boundary retains its invariant location-type judgment and its reference-liveness checks, including when its callee is reached dynamically. The current value of a location satisfying a default requirement does not prove that the location has that type.
Canonical metadata and the value presented to a program are separate views. A const D: Dimensions reads as a value of Dimensions; normalizing its numeric fields for metadata identity must not make D is Dimensions false. All metadata origins must use one normalization against the owning
The default, subtype of M holds of meta declarations (
The default, M defines validate and it holds of v and that portion. A validate therefore admits no bare value of the base at an annotation - the construction boundary, the cast or the const v: Velocity = 10; a number a way in.
Decomposition is why one flat object serves every float32.<{ m: 1, s: -2, bounds: 0.. }> is read by the dimensions default before any hook sees it: float32.<{ m: 1 }> is a metre because the remaining exponents default to zero, and a plain float32 is every default takes no part in a judgment or a conversion, so declaring a
Claiming is global so that a key means one thing wherever it is written, and the diagnostic is at the declaration because that is where the collision is, not at the unlucky program that parameterized on m last. The cost is that a
The where of a dependent record type (subtype is the identity of its tag and it defines no validate, a pattern's validate is a full match and its subtype is the judgment where's validate runs its predicate while its subtype holds only where the required side is unconstrained or carries the same predicate.
An implicit boundary to the bare base preserves metadata the supplied value already carries. An explicit conversion to the base may erase it. A bare annotation therefore supplies no proof that its value is plain. Neither value admission nor conversion changes the invariant type of a reference location or any reference-liveness requirement.
A required
Two parameterizations of one base that differ in their metadata convert by this clause, as specified in
At a numeric value boundary whose source and destination parameterizations are established, checking uses the admission and cast-selection steps below, without executing a cast body or the value-dependent stages. A required source-checking failure is the existing assignability diagnostic. When the determining inputs remain unavailable, retain the runtime obligation. Conversion availability does not make the source a subtype of the destination: Reflect.isAssignable, membership and invariant reference-location checks retain their existing relations. Selection does not supply an observed value, purity evidence, permission to replay a producer, or proof of reference liveness.
Complete each portion using its
The abstract operation ConvertParameterization takes arguments value (an
default, append M to participating.subtype of M to fp and tp.default, throw a describe, its descriptions of fp and tp.subtype hook to from and to.validate, thendescribe, its description of tp.conversionFactor, doconversionFactor.quantize and whose default, quantize of M applied to v and For each required number declaration has no additional source precedence for a parameterization of another numeric base. An exact whole-target declaration may supply several required owners once, including when other owners already passed subtype admission, but it cannot bypass an incompatible non-default source portion or the base-form judgment. It remains indivisible: selection combining it with another provider is ambiguous. An subtype does not require or independently select a cast. If no
The cast-stage validation above is additional to final validation after scaling, quantization and source-portion transport. An exact-target body undergoes cast-stage validation for every required
Where the required metadata leaves a rescale applied to that portion and f, or its default when no rescale is declared. If every contributing factor was a Number or a binary floating-point value of width at most 64, the legacy rescale argument is the product rounded to Number; otherwise it is rational64 when the reduced exact product's numerator and positive denominator both fit int64, and rational.<bigint> otherwise. This is an explicit protocol presentation policy, independent of generic defaults. MetaProtocol defaults its Factor parameter to rational64, and the rescale factor parameter admits number | rational64 | rational.<bigint>. Representability is tested before construction; a caught error is not a signal to select the unbounded presentation. This presentation to a hook does not change the exact factor used to scale the value. A failed hook, invalid result, exhausted evaluation budget, or failed snapshot is never treated as an absent hook. A rescale result must satisfy the instantiated quantize result must belong to the numeric base. These hooks propagate
The abstract operation ScaleMetadataValue takes arguments value (an
Each subtype admits it, or the value carries nothing of that Meter reaches Kilometer by the first, since the exponents agree and only the ratio differs; a bare number reaches Velocity only by the second, which is why the number the assignment is a validate is what the second way costs, and it is where a bound is enforced: subtype cannot prove an unconstrained value non-negative, so the cast into the bounds
The rest runs in the order above: the factor once, as the product over every rescale on the portions that flow, so a bound stated in metres arrives stated in kilometres; and quantize after the scaling, so a value rounds onto the representation it will be stored in rather than one it passes through. Folding and check elision remain subject to
Kilometer(5) + Meter(300) is the worked case. The parameter is the receiver's dimensions, so the argument's metre constraint must pass subtype, which it does since the exponents agree; conversionFactor yields 1/1000, so the value becomes 0.3; the argument's bounds are not pinned by the parameter and so flow through rescale with that factor, arriving in kilometre space; and the operator body adds 5 and 0.3 with both operands in one
Taking the factor as a product over the meta types that contribute one, rather than applying each in turn, is a decision this specification makes where the rescale is not linear, and an order-dependent type is the one thing the determinism invariant cannot have. Only the dimensions
A comparison against a narrow is the result of narrow of M applied to >= with <, > with <=, and == with !=, in both directions.
This is what lets if (v >= 0) give v the type float32.<{ …, bounds: 0.. }> inside the branch, and a further if (v <= 343) intersect that bound to 0..=343, so that a return v into a type requiring both bounds passes subtype and needs no check at all. It is a narrowing and not a proof: a narrow learns nothing from a comparison and keeps the constraint it had, which costs a check at the next boundary and nothing else.
A declaration of the form primitive T P { … }, where T names a primitive type and P is up to two
Each list of P is decided as a float32 declares no parameters, so primitive float32<const D: Dimensions> captures metadata in its only list; complex declares its component type, so primitive complex<_><const T: P> matches every component and then captures metadata, and primitive complex<const E><const T: P> does the same while naming the component, a complex.<float64>.<{ phase: 1 }> supplies its component and then its metadata; and primitive vector<float32.<const D: Dimensions>, const N: uint32> matches vector's element and lane count, capturing the element's metadata within. It is a Syntax Error for P to hold two lists where T declares no type parameters, since the first is already the metadata, or for a list of P to be a
These captures use
Before consulting block definitions, a unary operation on a plain primitive value, or a binary operation on two plain primitive values of the same type, takes the primitive operation directly. No definition, including a definition without a body, contributes to that operation. A vector is plain for this purpose when its lanes carry no metadata. Compound assignment applies this rule to its underlying operation before checking the store. Mixed and parameterized operands continue through block dispatch.
A bare
The
When only some captures are known, retain a result's numeric family, component type, lane count, and any result metadata independent of unknown captures. Widen metadata that depends on unknown captures. Join the possible successful return types of applicable value bodies and the built-in
Operator evaluation first evaluates each original operand once, prepares candidate operand requirements, and selects a value definition. Receiver-block precedence and strict operand specificity take priority. For equal instantiated operand types, the existing fixed-over-
Convert the selected operand once before evaluating any metadata-only return builder. Rebind fresh operand captures from this effective operand, then check
The rule is the one a type position already applies, so a block's header reads as the type it primitive complex<const T: P>, restates the component's domain type as P and is refused by primitive complex<_><const T: P>. Which lists a primitive's parameters make components is known from the primitive alone, since every primitive is predefined.
An operator body evaluates on raw values: no operator declared by any block is re-entered within one, so return this + rhs; inside operator+ is the primitive addition rather than itself. The metadata of a result comes from the return type annotations alone. For one operator invocation, definitions are compared by the block that declares them: a block over the receiver's own primitive, primitive uint8, is more specific than a block over the family that primitive belongs to, primitive uint<const W>, since a fixed component is more specific than a rhs: uint.<16>, is more specific than one naming them, rhs: uint.<W>; where no matching definition is the most specific, the invocation throws a default where no matching definition mentions it.
The raw-body rule removes result metadata obligations, not the declared base result obligation. The raw result type of a declared result is that type with its metadata parameterizations replaced by their bases, including through union and intersection alternatives and in vector lane types. Preserve numeric widths, component types, lane counts, literal restrictions, and ordinary structural member contracts. Judge each return, including implicit
When established operand contracts determine a selected definition's component and metadata captures, a computed declared result is a checking obligation in that definition's lexical environment with those captures bound. Retain the identity of the definition, the operation and the determining arguments when reusing its answer. An open annotation at declaration does not make this closed application unknown. Resolve newly determined result dependencies, including an enclosing operation or a later binding that consumes a computed result, before admitting a source text. Unavailable inputs remain unresolved; the
After the selected value body completes normally, apply
A String receiver consults its primitive string definitions before the built-in fallback, including when its representation carries a literal or refined
A registered block that may apply prevents a
For this recovery, retain the identity of the declaring block and definition, its fixed operand and result descriptors, and its lexical and private environments and
Use the same plain-value protection, receiver precedence, operand admission and specificity as dispatch before applying built-in numeric-family exclusions. A definition that admits only part of a static operand domain is not disjoint from that domain: retain that possible selection instead of choosing a broader definition or fallback as if it were absent. Unknown metadata remains unknown. A statically established unique value definition supplies its declared result; a result of an unselected value definition need not be evaluated to answer this query and cannot cause its failure. Definitions without a body retain the result-
A required dependent result whose inputs are established is evaluated by the existing pre-evaluation fragment in the
Two definitions declared for one operand are found at their declarations: it is an primitive uint<const W> { operator +(rhs: uint.<W>) … } and primitive uint<const V> { operator +(rhs: uint.<V>) … } declare one operand twice, while rhs: float64.<X>, the receiver's own metadata, and rhs: float64, any float64, are different operands.
A definition chosen for an invocation is the meaning of that operator for its receiver and operand, not a conversion of either, so the rule that two different numeric types do not mix applies only where no definition speaks for the pair: with primitive float64 { operator *(rhs: uint8): float64 { … } } declared, (2 := float64) * (3 := uint8) is that definition, and its type is the definition's return type, while (2 := float64) * (3 := uint16) still does not mix. A static check chooses the definition as the invocation does, from the declared operand types, and counts every block of the program, as the operators are defined before any code runs.
A block definition with a body replaces the operation for its pair, so the rule of primitive uint8 { operator +(rhs: uint8) … } redeclares uint8 addition and is an rhs: uint.<W> or rhs: uint.<16> in primitive uint<const W>, coincides with some member's own pair and is refused alike. A parameterization is a type of its own, so rhs: float64.<X> declares an operator the base does not define, as do another type and another width; a definition without a body contributes metadata and replaces nothing. The comparison operators are dispatched as the arithmetic operators are, and != is the negation of the == a block defines, as it is for a class, so neither is declared on its own.
Whether a definition matches an operand that its annotation writes with the block's captures, rhs: float32.<X> in primitive float32<const X: Dimensions>, is judged by the portions those captures speak for: the operand's effective Dimensions portion must be admitted by X under the
Such a declaration may also define an implicit cast operator, written as an operator whose name is a parameterization of the primitive, which supplies the conversion of const v: Velocity = 10; compiles exactly where number declares a cast into the dimensions validate.
For an implicit crossing from a bare numeric value, determine complete cast coverage before invoking a cast body. Each
Select a provider independently for each required portion. For a bare Number source, consult declarations on number, then on the destination's fixed numeric primitive, then on its family; for other supported bare numeric sources, consult the latter two levels. Use the first level containing an applicable candidate for that portion. At that level an exact whole-target declaration is more specific than a meta-type-wide declaration, and several equally specific candidates are ambiguous. An exact-target declaration is indivisible: if the selected providers include it and another declaration, the plan is ambiguous. A missing provider or ambiguity is a
Deduplicate the selected declarations and invoke them in their original declaration-establishment order, including across lookup levels. Destination metadata property order does not order the bodies. A whole-target declaration selected for several portions runs once. Start with the supplied raw value; each body receives the preceding body's raw result after admission to the actual destination base.
For a source already parameterized on the same numeric base,
Before invoking a selected implicit cast, establish its own binding instance for each header
An implicit cast likewise admits its raw result at the base of the actual target before attaching target metadata. Apply the base's ordinary checked conversion, without re-entering the metadata crossing. The metadata validation required by the receiving boundary still follows. The cast's target metadata is therefore not a return-boundary requirement inside its body, but its base representation remains a requirement.
A block's COMPONENT primitive complex<const E> { operator +(rhs: complex.<E>): complex.<E> { … } } a complex128 receiver binds E to float64, and a uint16 receiver of primitive uint<const W> binds W to 16. A block over a primitive represented as an object, such as complex or rational, is consulted as any other is, before that primitive's own operation.
A cast's name is a type, read as every type is (complex.<T> names a complex whose component is T, never a metadata parameterization. A primitive complex<const E><const T: Phase> { operator complex.<E>.<T>() { return this; } } the one cast serves a crossing into complex.<float64>.<{ phase: 1 }> and into complex.<float32>.<{ phase: 1 }> alike, and primitive float32<const D: Dimensions> { operator float32.<D>() { … } }, over a primitive that declares no parameters, needs no component.
A cast's declared parameterization names what its result BECOMES, and is not a boundary its body must already satisfy. The raw-body rule above applies to a cast as it does to any other operator, so the body evaluates on raw values and the usual whole of one is return this;: its result is admitted at the target base and then taken AT the target metadata, rather than checked against that metadata inside the body. Reading the annotation as a boundary instead makes the plainest cast anyone will write fail inside its own body, because the return would re-enter the very crossing the cast defines - which is the case the raw-body rule exists to prevent, arriving from the other direction.
What a cast does not do is replace the check. Its result reaches the boundary as any other value does, so a validate hook still judges it: a cast is how a value gets IN, not a way past what the metadata requires. A bounds const v: Velocity = 10; and a bounded type behave differently though both cross through one.
The raw-body rule is what makes the whole protocol terminate: an operator body is the definition of the operation, so an expression inside it cannot mean the thing being defined. It is also why the boundary conversion of
Splitting a value definition from metadata-only definitions is what lets two meta types share an operator without either knowing of the other. The dimensions block computes the sum and says the exponents are unchanged; the bounds block says the sum's bounds are the sum of the bounds and computes nothing; the result carries both. A default, which is the correct answer for a constraint that the operation does not preserve.
The reflection of
A Token Record is one token of source, as the lexical grammar produced it. It has the fields of
Tokens are what a decorator reads of the syntax it decorates, and what a
| Value | Meaning | |
|---|---|---|
| [[Kind]] | Which lexical form the token is. | |
| [[Value]] | a String | The token's source text. For a |
| [[Span]] | a | Where the token came from. |
| [[Tokens]] | a | For a |
| [[LineTerminatorBefore]] | a Boolean | Whether a |
[[LineTerminatorBefore]] is carried because a LineTerminator is semantically significant: automatic semicolon insertion depends on it, so text produced from tokens that emitted a space where the source had a newline would join a statement to the one above it. A separator is enough to keep two tokens from merging; it is not enough to preserve what they mean.
/ differently, so a token stream is parse-informed rather than purely lexical. A token a
Delimited runs are grouped rather than flat, so a macro cannot produce unbalanced output: the delimiters of a
A Span Record says where a token came from. It has the fields of
| Value | Meaning | |
|---|---|---|
| [[Source]] | a | Which buffer the token's text is in. |
| [[Start]] | a non-negative | The index of the token's first code |
| [[End]] | a non-negative | The index after the token's last code |
A Source Reference Record names a buffer. It has the fields of
| Value | Meaning | |
|---|---|---|
| [[URL]] | a String or | The module's URL, for a token from written source. |
| [[Macro]] | a String or | The |
| [[Generation]] | a non-negative | 0 for written source; n for the nth expansion that produced the token. |
A
The abstract operation TokensOf takes argument node (a
{, [, or (, thenThe lexical / is already resolved to
A TokenStream is an
Comments and whitespace are recoverable rather than discarded: consecutive tokens of one buffer have the text between their spans, so the gap IS the trivia. This is why no separate source-text
%
Parses a range of this stream's source text and answers its tokens.
This is what lets a
It is the one thing a macro cannot do for itself. Whether / begins a regular expression or a division is not decidable lexically: after } it depends on whether the brace closed a block or an ObjectLiteral, which needs a parse. A macro re-lexing a range gets four tokens where there is one regular expression, and no way to tell it from a division.
The context parameters come from where the REGION sits rather than from the caller. await, yield and return are legal only under a context the enclosing parse established, and a macro has nothing sensible to pass - passing anything would let it parse await into a
The abstract operation TokenStream.gensym takes argument hint (a String) and returns a
This is the hygiene mechanism. A Date still meets a shadowing Date — which no mechanism short of syntax contexts on every binding addresses, and which the spans above make detectable even where they do not prevent it.
A decoration whose {, takes that brace and its match as a region rather than decorating an ObjectLiteral. No attribute declares this: being a preprocessor decoration is what makes the braces a region, and preprocessor is as lexically visible as any other key - so a tool that does not resolve imports recognises a region exactly as it would have from a dedicated one.
The cost is stated rather than left to be found: a preprocessor name cannot decorate an object LITERAL, so @m { a: 1 } is a region for every preprocessor m. Parentheses do not restore it, @name ( beginning an argument list, and the only way out is a second import of the same name without the attribute.
A decoration on a declaration is unaffected. @m class C {} decorates the class, and @m function f() {} the function; only a { following the decoration directly is displaced.
A TokenStream (TokenStream and return a TokenStream.prototype.parse (
The declaration is what is read, rather than a property TokenStream and returns a
It also settles WHEN the answer is available. A property must be read from an evaluated module; a parameter's type is in the text. The engine still evaluates a
A macro that annotates no context takes a parsed capture took. No program written against an earlier edition changes meaning by this rule alone.
The engine provides no grammars, so a decorator is not choosing among them. A macro whose region is not ECMAScript - a query language, a markup dialect - captures it, reads its text, and hands back the ranges that ARE ECMAScript through
A
How the macro reaches the parse: the importing module's source is scanned for preprocessor imports before it is parsed, which is possible because an import declaration is at the top of a module and lexes as ordinary ECMAScript. Each module so named is loaded, linked and evaluated, and the value a decoration is spelled with is the export its import BINDS. import { jsx as h } declares @h and asks the module for jsx, and import jsx from declares @jsx and asks for
The load is synchronous: an implementation completes it before the parse that is waiting on it, and a
A
This is a stronger rule than ECMAScript modules have, which permit cycles, and the difference is worth stating rather than leaving a reader to assume the module graph's behaviour carries over. An ordinary cycle works because evaluation can observe a partially-initialised binding. Here the PARSE depends on the evaluation, and a partial parse is not a thing: to parse A you evaluate its preprocessor B, to parse B you evaluate its preprocessor A, and to evaluate A you parse A. Template Haskell's stage restriction refuses the same
A
A
Evaluability is the whole of the sandbox. An evaluable function reads only its parameters, constants, and other evaluable functions, so it cannot NAME the clock, randomness, I/O, or ambient state — Date.now, Math.random, WeakRef, and locale-sensitive operations are all unreachable by construction rather than withheld by a
The constraint binds evaluation as well as expansion: a module that could read the network while evaluating would close over what it read, and its decorators would not be pure however pure their bodies were.
Local mutation is evaluable — a Set of seen keys, an accumulator — and shared module-level mutable state is not, which is the same line where clauses. A
A captured region is the { that follows the decoration, optionally preceded by do, and ends at the matching } as
@m { <p>a } b</p> } ends at the brace in the text and not at the one the author meant, which is a property of capturing rather than of any grammar a decorator implements.
The do is optional in every position and means nothing to the decorator: the token stream begins at the @m do { ... } and @m { ... } deliver the same tokens. Both spellings are available wherever the other is.
do { } exists because a { beginning an expression is an ObjectLiteral and a block that produces a value needs distinguishing from one. A captured region is parsed as neither - its extent is found by matching delimiters and its contents are never read as ECMAScript - so the distinction does not arise, and requiring do would buy nothing.
It has a cost, which is stated rather than left to be discovered: a preprocessor name's { always begins a @name { a: 1 } - is unavailable for that name. Importing a name as a preprocessor therefore changes what @name { ... } means throughout the module. Parentheses do not restore it - @name ( begins an argument list, so @name ({ a: 1 }) is a decoration taking an object as an ARGUMENT - and the only way to decorate an object literal with a preprocessor name is to import it a second time without the attribute.
A decoration's arguments are read as ECMAScript even where its
A captured region is never evaluated. It is replaced by whatever its decorator returns, and what a decorator returns is ECMAScript however its a < b and a type argument list are read as they always were.
Without this a < cannot begin an expression, so <div/> fails at the < before the closing tag is reached - the same failure < 2 gives, and not the ambiguity the / in a closing tag raises. That second ambiguity is real and downstream of it: a </div>/ parses as a less-than the regular expression /div>/.
Because a captured region is found rather than tokenized, an implementation records its source range and the tokens the
A captured region's text reaches the
The abstract operation ScanBalancedRun takes arguments source (a String) and start (a non-negative
(, [, or {.(, [, or {, then), ], or }, thenRecognising comments and string forms is not optional: a } within one is not a delimiter, and counting braces without them ends a
A regular expression is deliberately not recognised, and this is not an omission. Within a / is whatever the decorator says it is - in a JSX-shaped syntax it opens a closing tag - and a { within it is a real delimiter. Outside a a </{/ scans a regular expression and swallows the brace. No single scanner serves both readings, which is the reason a
A replacement decorator is a decorator whose name was introduced by a
A decoration names a
The
A namespace import and a bare import jsx from "./jsx.js" with { preprocessor: "true" }; followed by @jsx { ... } is the
The first makes a name introduced by a preprocessor import unshadowable, which is what lets the second be decided without resolving a binding. Both are computed from the module's own text, so neither depends on anything expansion produces.
The rule is syntactic rather than scoped because a scope query would be circular: a
A consequence for the decorator's own output: a _jsx.
The arguments of a @f(0) passes a @f(Serialize) passes an
This is an exception to the rule that decorator expressions are evaluated in document order (
A name
A name may nonetheless carry an ORDINARY decorator as well, since the two are told apart by their signatures rather than by their arguments: a TokenStream and returns a
Overload resolution selects on argument types, and the arguments of a
A
A
Expansion is the phase in which
Nothing is re-scanned. What a
names is fixed before the loop and nothing in the loop changes it, so a decoration that an expansion introduces cannot name a
The expansion limit is an implementation limit, but a specified one: a program that expands in one implementation and not another because the limits differ is the failure a limit exists to make loud.
A module whose
Type checking follows parsing, so it sees only expanded code and never an unexpanded decoration. This ordering is normative: a program's validity depends on it, and an implementation that checked before expanding would reject syntax a
The abstract operation ApplyReplacementDecorator takes argument d (a decoration) and returns either a
target is everything the decoration ENCLOSES, not the declaration alone.
A token the decorator COPIED from what it was given keeps the Span it arrived with, so it still names the position a program was written at however many expansions move it. Only a token the decorator CREATED is attributed to the decorator, which is what makes a diagnostic inside generated code point at the macro that generated it rather than at the splice — and why no offset map is needed anywhere: attribution travels on the token.
A
A
A (tokens, context, args). Its TYPE is that context: Reflect.Block and have it mean what it says, which is what lets a name's overloads be told apart by the position each takes. The tokens argument likewise has the TokenStream type, not the type of whatever backs it.
Where a name declares SEVERAL @jsx { … } expands and @jsx class C {} decorates.
Decided by resolution before the call rather than by reading the error one would throw. A macro whose own body calls an overloaded function with arguments no signature accepts fails for a real reason, and the two cannot be told apart by their messages.
Only where the name declares more than one. A name whose single
The context is an kind, whose value names the position - the same name its "Class", "ClassMethod", "IfBlock" and the rest, with "Region" for a label, which is absent otherwise.
label is carried because it is the one syntactic fact the tokens cannot hold. A label PRECEDES the decoration - lbl: then @m then the statement - so a span reaching back to include it would contain the decoration being expanded.
Two positions are unreachable for a { always begins a "Region" rather than "Block", and one on a do block reports "Region" rather than "DoBlock". That is the same rule that makes @m { a: 1 } a
Nothing else, because a static, a for's binding, a match arm's pattern are all in those tokens already, and a
Typing the context parameter declares WHERE a decorator applies, and a decoration in another position is then refused where it is written rather than inside the macro. It is optional: a decorator declaring no context is usable wherever its return parses, which is what lets one serve several positions without being told which it is in.
A decorator answers a token sequence: an array-like of Token
An empty sequence is a
What a decorator RECEIVES is a
There is no way for a decorator that RUNS to decline what it was given. A decorator that wants to inspect without rewriting returns its input, and that is the whole of the no-op - which is why
The tokens a
Where the decorated position was a Statement, the
This is a rule about TEXT rather than about how the returned tokens are read, and it has to be. f() where a statement stood leaves f() against whatever followed it; with no LineTerminator between them, automatic semicolon insertion does not apply and the program does not parse. Reading the return as an ExpressionStatement would not help, because the adjacency is in the text.
Every decorable position may be syntax-replaced, including the positions that do not admit value
This is a grammatical constraint where value do block's value may be replaced by a runtime decorator and its syntax by a
An empty result needs no separate permission. It parses where an empty token stream parses — as a statement, or as a class member — and does not where one does not, so removing a decorated statement is legal and removing a parameter from the middle of a list is the same error as returning a class declaration for one.
A reflection context is a type in the Reflect namespace naming a kind of declaration or a part of one. Reflect.getReflection.<C, T>(…) reflects the part of T that the context C names, and is resolved by the specialized overloads of any in it.
| Family | Contexts | Reflects |
|---|---|---|
| Type | Type | A type's structure ( |
| Layout | ClassFieldLayout | A Type, a |
| Class | Class, ClassField, ClassAccessor, ClassGetter, ClassGetterReturn, ClassSetter, ClassSetterParameter, ClassMethod, ClassMethodParameter, ClassMethodReturn, ClassOperator, ClassOperatorParameter, ClassOperatorReturn | A class and each part of one. A ClassMethod whose name is signatures has one entry per declared super call that initialises it, because the class builds the instance and only the body that runs is chosen. |
| Function | Function, FunctionParameter, FunctionReturn | A function declaration, its parameters, and its return. |
| Binding | Let, Const | A declared binding. |
| Object | Object, ObjectField, ObjectGetter, ObjectGetterReturn, ObjectSetter, ObjectSetterParameter, ObjectMethod, ObjectMethodParameter, ObjectMethodReturn | An object literal and its members, keyed on the instance rather than on a declaration, since that is where an object's metadata lives. |
| Block | Block, IfBlock, ElseIfBlock, ElseBlock, WhileBlock, DoWhileBlock, ForBlock, ForInBlock, ForOfBlock, MatchArmBlock, DoBlock, DoGeneratorBlock | A statement's block, and the block of a match clause's arm (match, countable. |
Enum, EnumEnumerator | An | |
| Structural | Tuple, Record | A tuple or record declaration. |
A context that names a
The contexts are the reason the facility needs no separate function per kind of declaration: getReflection.<Reflect.ClassField, Point>('x') and getReflection.<Reflect.Type>(Point) are the same call resolved by its selector, and the selector is an ordinary type. It is also the
Reflect.Type, is given by
The rules of this clause hold of every reflection; the family clauses that follow give the fields.
Every reflection has a kind property whose value Reflect.ClassField and so on for each context of
The fields a family clause lists are all of a reflection's properties, and every one is present. An implementation may not omit a host, a single reserved property that is absent unless the implementation puts something there. So every bare property of a reflection is one this specification gives, and an implementation's own vocabulary is under one key a reader can see and ignore.
A java.lang.reflect, System.Reflection, and inspect each do, and what makes a reflection worth reading at all.
Reflect.getReflection returns a new
A
Identity belongs to the thing that carries state and not to the report about it. The Block contexts show that it could not have been otherwise: a block decorator runs on every entry to its block, so its reflection is produced once per evaluation and there is nothing for an interning rule to key on. A rule that interned some contexts and not others would be worse than either.
Where a family's reflection has a metadata property, the property is present and holds a Reflect.Type and the Binding, Block, and Structural families of metadata property, which is a different thing from an empty one.
Present-and-empty is what keeps a read of a key that nothing
An initial property holds a declared default only where that default is a constant, and initializer as a
Every reflection of this family has the fields kind of type initial initializer
| Context | Fields |
|---|---|
Class | name, a String or type, the class itself; abstract; metadata. |
ClassField | type; name, a String or Symbol; static; private; protected; readonly; initial; initializer; offset, a signed byteLength, a non-negative metadata. |
ClassAccessor | type; name; static; private; protected; readonly; initial; initializer; access, an object with get and set methods; metadata. |
ClassGetter | type, the getter's function type; name; static; private; protected; metadata. |
ClassGetterReturn | type; metadata. |
ClassSetter | type, the setter's function type; name; static; private; protected; metadata. |
ClassSetterParameter | type; name, a String; initial; initializer; metadata. |
ClassMethod | type; name; static; private; protected; abstract, signatures, a any parameters, an unannotated declaration remaining unannotated for the same reason an unannotated binding remains any; metadata. |
ClassMethodParameter | type; name, a String; index, a non-negative initial; initializer; metadata. |
ClassMethodReturn | type; metadata. |
ClassOperator | type; operator, the operator the declaration overloads; static; signatures, as a method's, a return-type overload appearing as a further entry; metadata. |
ClassOperatorParameter | type; name, a String; index; initial; initializer; metadata. |
ClassOperatorReturn | type; metadata. |
A signature reflection, the element of a signatures parameters, a return, the return reflection of that family. Where the signature declares type parameters it has typeParameters, a name, kind ('type' or 'value'), variadic, variance, arity, and constraint and default as the type objects the declared ClassMethodParameter, ClassOperatorParameter, ClassSetterParameter, FunctionParameter, and ObjectMethodParameter differ in which declaration they reflect and not in what they report, and ClassSetterParameter carries no index because a setter has exactly one parameter.
A ClassMethod whose name is
A reflection and the decorator context built from it are not the same object. A context is a reflection with the fields a decorator needs to act rather than to read: addInitializer where
offset and byteLength are
A ClassFieldLayout reflection reports where a Reflect.getReflection.<Reflect.ClassFieldLayout, T>(name). It is a byteLength from a string is (hasLayout first.
| Context | Fields |
|---|---|
ClassFieldLayout | name, the name the offset, a signed byteLength; bitLength, the packed bit size; alignment; offsetBit, bits from the start of the allocation; isBitField. |
It carries no metadata, no type, and none of static, private, or protected: those are facts about the declaration and are reported by the ClassField reflection (
The two are separate contexts because they answer different questions and are wanted by different readers: a decorator asks what a FieldInfo beside Marshal.OffsetOf, C's offsetof connects to nothing else, and Rust had no offset_of!.
An accessor reports the name it was declared under rather than the private
The two also answer differently for a class with no layout, and both are right. ClassField reports offset and byteLength as
A decorated function literal receives a Function reflection of its created closure. Its
| Context | Fields |
|---|---|
Function | type; name, a String, a Symbol, or signatures, a metadata, whose inverse is the declared inverse where the function is a builder that declares one ( |
FunctionParameter | type; name, a String; index, a non-negative initial; initializer; metadata. |
FunctionReturn | type; metadata. |
FunctionParameter and FunctionReturn report one signature's parameter and return, which is what a decorator on a parameter of an overloaded function needs: the decoration is written on one signature and reflects that one. signatures on the Function reflection is the whole
| Context | Fields |
|---|---|
Let | type; name, a String; initial; initializer. |
Const | type; name, a String; initial; initializer. |
Neither has a metadata property, this family being one of those
A const initialized with a constant always reports an initial, where a let's may be const with a non-constant initializer being as possible as a let with one. The two reflections have one
These reflections are reached from an instance rather than from a declaration: Reflect.getReflection.<Reflect.ObjectField>(instance, name) takes the object itself, where the Class family takes the class. An object literal has no declaration a second evaluation of it would share, so its metadata lives on the instance and so does the reflection that reports it (
| Context | Fields |
|---|---|
Object | type; metadata. |
ObjectField | type; name, a String or Symbol; metadata. |
ObjectGetter | type, the getter's function type; name; metadata. |
ObjectGetterReturn | type; metadata. |
ObjectSetter | type, the setter's function type; name; metadata. |
ObjectSetterParameter | type; name, a String; initial; initializer; metadata. |
ObjectMethod | type; name; signatures, as a class method's (metadata. |
ObjectMethodParameter | type; name, a String; index; initial; initializer; metadata. |
ObjectMethodReturn | type; metadata. |
The family mirrors the Class family member for member and reports less of each: an object's members have no static, private, or protected to report, and its fields have no offset or byteLength, an object literal not being laid out. What is left is the same questions asked of a different kind of container, which is why a decorator written against one family reads almost unchanged against the other.
Two objects of one literal are two instances and reflect separately, so what a decorator wrote about one is not visible through the other. That follows from the metadata living on the instance rather than on a declaration and is the one place this family's answer differs in kind from the Class family's rather than in detail.
A block reflection reports the syntax around the block rather than a type: every index and async holds a
Every reflection of this family has label, a String naming the block's label or block, the tokens the block delimits. A for without an update clause reports update as
| Context | Fields beyond label and block |
|---|---|
Block | None. |
IfBlock | condition. |
ElseIfBlock | condition. |
ElseBlock | None. |
WhileBlock | condition. |
DoWhileBlock | condition. |
ForBlock | initializer, condition, update, each |
ForInBlock | binding, the tokens of the binding the head introduces. |
ForOfBlock | binding. |
DoBlock | None. |
DoGeneratorBlock | async, async do * and do *. |
MatchArmBlock | subject, the tokens of the match's argument; pattern, default clause; guard, index, a non-negative |
No reflection of this family has a metadata property, the Block family being one of those
A block reflection is produced on every entry to its block (
| Context | Fields |
|---|---|
Enum | type, the name, a String; valueType, the size, a non-negative metadata. |
EnumEnumerator | name, a String; value, the enumerator's value, of the valueType; index, a non-negative metadata. |
An name and a type, as a class's does and for the same reason: the name is what a reader wants directly, and reaching it by reflecting the type and reading its display would be an indirection with nothing to recommend it. An object literal has no name to report, which
An enumerator reflection reports its value and no type: the type is the index is the declaration order rather than the value, which are different whenever a program assigns values explicitly.
| Context | Fields |
|---|---|
Tuple | type. |
Record | type. |
Neither has a metadata property, this family being one of those
These two reflect a composite VALUE where Reflect.Type reflects a type, and which of them a decoration selects is decided by the value rather than by the syntax: a composite built over a Tuple and one built over a Record, the same division a composite's own kind already makes. A reader wanting the structure walks type with Reflect.Type (
For a decorated function literal, check each returned
A decorator may return a value that replaces what it decorated. Returning
| Context | Replaces | Type admitted |
|---|---|---|
Class | The class | The class or a subclass of it |
ClassField | The | The |
ClassAccessor | The accessor's get and | An object with get and set methods over the accessor's type |
ClassGetter | The getter function | A function of no parameters returning the getter's type |
ClassSetter | The setter function | A function of one parameter of the setter's type returning |
ClassMethod | The method | The method's type, signature for signature |
ClassOperator | The operator function | The operator's type, signature for signature |
Function | The function | The function's type, signature for signature |
ObjectGetter | The getter function | As ClassGetter |
ObjectSetter | The setter function | As ClassSetter |
ObjectMethod | The method | As ClassMethod |
DoBlock | The do expression's value | The type of that expression |
DoGeneratorBlock | The do * expression's generator | A Generator of the expression's yield, return, and next types |
Every other context admits no do's.
The Class row admits a subclass because a
The two do rows are the reason the block exclusion is stated over what a block produces rather than over blocks: a block produces nothing, so there is nothing to replace, and the exclusion was never about blocks being structural. A do expression produces a value and a do * produces a generator, so those two have something a
This table and
addInitializer(callback) registers callback to run once the declaration it was registered from is fully decorated. It is a
Only the contexts of addInitializer property. On every other context it is absent, so a decorator written for a position that cannot register one finds nothing to call rather than a call that does nothing.
| Carries it | Does not |
|---|---|
Class, ClassField, ClassAccessor, ClassGetter, ClassSetter, ClassMethod, ClassOperator, ObjectGetter, ObjectSetter, ObjectMethod | Every other context of Function, ObjectField, the Binding, Block, Type. |
The list is closed rather than derivable, and Function is the case that makes it so: a function declaration admits a addInitializer, so no rule of the form "whatever can be replaced can be initialized" holds. What the ten have in common is a declaration
ObjectField is the other case worth naming: it is a member of an object literal, and the literal is finished when the
Callbacks running after every decorator of the declaration is what lets a decorator register work that depends on what a decorator applied closer to the declaration did, since application is innermost first (
Reflect.getReflectionByIndex.<C, T>(…) answers the same reflections Reflect.getReflection of the same context and arguments would, as a
It is defined for the contexts that name a
Reflecting the members of a class answers the members it inherits as well as those it declares. Passing { own: *true* } in place of a member name answers only the members the class declares itself. Passing { static: *true* } answers the class's static members; an enumeration with neither option answers its instance members.
An enumeration answers members of ONE staticness, because its result is keyed by name and a name does not identify a member: m and static m may both be declared in one class body, and both reach the same constructor names it. A program wanting both asks twice and merges the results BY VALUE, each reflection carrying its own name and static; merging them by key would lose one member per shared name, which is the collision the option exists to resolve.
The enumerated methods of a class include its constructor (
Inherited by default because a decorator asking what fields a class has usually means all of them: an inherited
A parameter of an overloaded function or method cannot be reached by name or by position, since neither says which signature it belongs to. Reflect.getReflection and Reflect.getReflectionByIndex of the ClassMethodParameter, ClassOperatorParameter, FunctionParameter, and ObjectMethodParameter contexts therefore throw a signatures of the declaration's own reflection, each entry of which carries its own parameter and return reflections.
This is the rule an ambiguous overloaded reference already follows at a call
Reflect.getMetadata.<C, T>(…) performs the following steps when called:
Reflect.getReflection.<C, T> of the same arguments.metadata property, throw a metadata.It is defined only for the contexts whose reflection carries a metadata property (
Sugar over Reflect.getReflection, and specified as such rather than independently, so the two cannot answer differently about the same declaration. The
A decorator may be written before a TYPE only in a position that has a Reflect.Type is a reflection target and not a decorator context - a bare type expression carries no decorator - so a decorator before the type of a binding, a
The rule is stated over positions rather than over spellings because the same text means different things in the two places: in m(): @f T the decoration is of the RETURN, whose context is ClassMethodReturn, and in let x: @f T there is nothing for it to be a decoration OF. Admitting the second and dropping it would read as support for a feature that does not exist.
A metadata property holding the metadata object of what it names; the Type, Binding, Block, and Structural contexts have none, their reflections carrying no metadata. A %ClassMetadata% for Reflect.Class, %ClassFieldMetadata% for Reflect.ClassField, and so on for each context of those four families; this specification declares the interfaces, and their names resolve in type position as the built-in type names do (partial declaration over one resolves. A program adds to one by declaring a partial interface over it whose members are
Reflect.getMetadata.<C, T>(…) returns the Reflect.getReflection of the same context and arguments would report, and is the reading half of what a decorator writes.
A
%ClassFieldMetadata% sees exactly the vocabulary the program's decorators established. This is the same discipline the metadata protocol takes for a
Prototypal inheritance of the @tag('base') on a A visible on B's inherited copy and invisible once B redeclares and retags it, without either class knowing of the other. It also means metadata is read the way a property is read, so a key absent from a derived member's own object is not absent but inherited, which is usually what a decorator's reader wants and is worth knowing when it is not.
A decorator is an ordinary function whose last parameter is annotated with a @f, @f(0), and @f('a') may name three declarations of f and select among them the way any call does. @f and @f() are one form: both resolve with no explicit argument, and a signature taking the context alone is preferred over one whose remaining parameters are satisfied by defaults. Where no such signature exists and more than one is satisfied by defaults alone, the decoration is ambiguous and a
It is a
A decorator runs when the declaration it decorates is evaluated, and what it may do there is write to the metadata of its context, register an initializer, and return a
A block is the exception, and it is the one position in this extension whose decorator runs more than once. A block decorator runs on every entry to its block rather than once where the block was written, so a block inside a loop runs its decorator once per iteration and a block inside a function once per call. This follows from the rule above rather than qualifying it - a block is evaluated each time control reaches it, and a decorator runs when what it decorates is evaluated - but it is worth stating, because the cost is real and is not visible at the decoration.
What it buys is the reason the family exists: instrumentation, tracing, and scoped resources are per-entry questions, and a decoration that ran once at the declaration could not answer them. What it costs is a context and a call per iteration, which a decorator in a hot loop pays and the reader of the loop cannot see. A
This is also why a block reflection carries no metadata and cannot be interned (
Types are what remove the (value, context) return from a decorator that takes arguments: an erased system cannot tell @f from @f(0) without one, since it cannot see which f is meant, and this
Both rules above are rules for decorators that run when the declaration they decorate is evaluated. A
A signature carries two facts that no rule of the language computes and that a program states instead: the this a function expects, and what a call of it establishes about its arguments. They are data the checker reads, never code it runs, which is what distinguishes them from the meta protocol's hooks (
The receiver-presence rule also applies on the const f = c.m; f?.() supplies no receiver, whereas c.m?.() and (c.m)?.() preserve the property reference. A comma expression, binding alias or destructuring extraction does not synthesize a receiver. No call occurs on a definitely nullish skipped this.
At a call with a selected signature carrying an explicit [[ThisType]], the supplied receiver must satisfy that type. A statically known incompatible receiver is a
A signature's [[ThisType]] is the type its this is required to have. It is contravariant, as a parameter is, so a signature with a [[ThisType]] is usable where one requiring a NARROWER this is required and never the reverse. A signature with none supplies no this rather than accepting any, so it is usable nowhere a this is required at all, and one that has a [[ThisType]] is usable nowhere a this is absent (this is not of the type its body assumes, and the extraction is a this, rather than a
A method's signature has a [[ThisType]] although no syntax declares one: the self [[ThisType]], which stands for the receiver the method is reached through, whatever that receiver's declared type. Two methods agree on it, so a class satisfies an interface that declares the same method, and a method does not agree with a function type that has no [[ThisType]], which is what refuses the extraction at a boundary. It is not the class's own type: a method expecting its class would be narrower than the interface method it implements, and contravariance would refuse the implementation, although the interface's method is reached only through an object that has it.
It is a this to a callee whose every selected signature has a [[ThisType]]. A call supplies a this where its callee, once parentheses and explicit type arguments are removed, is a member access, an optional member access, or a super property; any other callee supplies none. So const f = c.m; f(), a destructured m(), and (0, c.m)() are type errors, while c.m(), (c.m)(), c.m.<T>(), f.call(c), and a call of a bound function are not. The boundary rule above refuses a method that reaches a position requiring a function type without a [[ThisType]]; this rule refuses the call through a binding that kept one, which crosses no such boundary.
Where a non-arrow function literal's this within the body has that type, and the literal's own signature has that [[ThisType]]. An arrow adopts nothing, since it has no this of its own to give a type to, and the this it closes over is already
This is the whole of what an erased system's ThisType<T> marker does, said as data rather than as a marker the checker recognizes by name. It is also why the options(Data, Methods) builder is three lines: it constructs an intersection of the data and the methods, and calls withThisType to put it on every method's signature, at which point a method written in the object literal has this
A call consumes the effective selected signature after type-argument substitution. Substitution applies to [[Narrows]] and [[ThisType]] as well as parameters and return type. Each narrowing target names the argument actually bound to that parameter by ordinary named, positional, default and rest argument binding; no fact is attached to an omitted value or an unestablished place. Method signatures retain the same declarations as free functions. If overload selection remains unresolved, no arbitrarily chosen signature supplies a narrowing. Boolean facts apply only in the corresponding guarded paths; void assertion facts apply only after successful return in positions dominated by the call, using the completion and join rules of
A Narrowing Record has a [[Target]], a String naming a parameter of the signature or
Where the [[Return]] is boolean, the call is a test and its narrowings apply where its result is v is T applies (if (isFish(pet)) narrows pet in the branch it guards and narrows it away in the other. Where the [[Return]] is
A narrowing declared by a signature is checked against nothing. It is a claim the declaration makes and the body is not required to honour, in the way that v is T is a question the language answers. This is the same reading a declared type of an unchecked boundary gets, and the same one an erased system's type predicate gets; what makes it sound here is that a value crossing a boundary is checked against its declared type regardless (
A fresh function literal whose return position is unannotated may adopt the contextual signature's narrowing declaration under
Callable conformance preserves both branches of a Boolean test. A predicate for uint8 does not implement a predicate for uint8 | string over uint8 | string | boolean: it may answer
The two forms are the type predicate and the assertion function of an erased system, and they are one asserts operator appears here.
[[Narrows]] has a source spelling: the second form of function isFish(pet: Pet): pet is Fish declares a signature whose [[Narrows]] holds one is. The signature's own [[Return]] is boolean: the annotation says what a
A signature may also acquire [[Narrows]] and [[ThisType]] by construction (withThisType writes the latter, and a program uses the result by annotating with it: a binding declared of a constructed guard type narrows at every call through it. [[ThisType]] still has no spelling of its own, and joins the index signature and the symbol literal type as types this specification admits and no declaration writes.
A spelling is what makes the [[Narrows]] step of
The lookahead is one token: a is, so the form is unambiguous against an annotation whose type happens to be named like a parameter. The [no LineTerminator here] restriction is the one the is operator already carries (
A [N].<T> a contiguous buffer rather than a list of references, what gives repr attributes together with the
EVERY REFLECTIVE QUANTITY THIS CLAUSE DEFINES IS A uint64: a type's byteLength and alignment, an array's length, byteOffset and byteLength, and a window's length. They are the same kind of quantity and combine with one another, so _a_.length * _V_.byteLength is the size of a run and needs no conversion.
They are
An exact width is still required of the OTHER operand: _count_ * _V_.byteLength where count is a uint8 is refused, as any mixing of widths is (
Install these fixed own type supplies neither a particular layout nor a general prohibition on writes to other members. Types with no layout retain their separate read behaviour.
A bitLength is the size in bits, which is what an arbitrary-width byteLength is the laid-out size in bytes, the bits rounded up to a byte and then to the type's alignment, so that every element of an array of the type is aligned; and alignment is the byte alignment. Each has the attributes { [[Writable]]: [Vertex.byteLength * 1024].<uint8> is an array extent and T.byteLength inside a generic is a constant once T is specialized.
| Type | Layout |
|---|---|
A numeric type, boolean, or a SIMD vector | Yes. An int.<N> or uint.<N> has bitLength N; a named type has the bitLength its width states. |
| An | Yes, its |
| A | Yes, by |
A fixed-length array [N].<T> | Yes: N times the byteLength of T, aligned as T is. |
A fixed-length SoA.<T, N> | Yes, but not T's: each T is a column of N elements, padded and aligned on its own, and the size is the sum of the columns. An element's fields are not adjacent, so there is no interior padding between them and the size may be smaller than the [N].<T> of the same T. |
| A parameterized type | Yes, its [[Base]]'s exactly ( |
bigint, string, any | No. The size is a property of the value and not of the type. |
| A | No. A reference's width is the implementation's business. |
_T_ | *null* where T is a | Yes, the inline optional of |
A dynamic-length array [].<T> | No as a type. An instance has a byteLength, its length times its element's. |
A window Span.<T> | No. A window names a run of elements in storage it does not own ( |
A class with an untyped dynamic | No. |
| A union of value types | No. It has no one layout. |
A hasLayout member answers whether such a read is valid without throwing.
A type's alignment is its byteLength rounded up to a power of two, and there is no upper bound on it: uint.<4> aligns to 1, uint.<24> to 4, uint.<128> to 16, and a float32x4 to 16. A
A family, a String naming the kind of value the type describes. family is how a program asks the same question.
family | Types |
|---|---|
uint8 through uint128, int8 through int128, the general uint.<N> and int.<N> forms, and boolean1, which is a one-bit unsigned | |
float16, float32, float64, float128 | |
decimal32, decimal64, decimal128 | |
rational.<T> | |
complex.<T> | |
every SIMD type, vector.<T, N>, and the bit-vector booleanN forms for N above one, boolean8 being vector.<boolean1, 8> | |
bigint | |
string | |
boolean, the ordinary two-valued type | |
the type type | |
| a class or an interface | |
| an | |
| each of those types |
A type carrying metadata reports the family of the type it parameterizes, so float32.<{ m: 1 }> is uint8.<{ bounds: 1..=6 }> is family at odds with instanceof, with the operator table, and with bitLength beside it, each of which already answers about the parameterized type.
family names the family and nothing further. For a class it is
A program wanting the metadata a type carries rather than the family it belongs to reflects, where the base and the metadata (
family is not a substitute for is, for instanceof, or for Reflect.isAssignable. Whether a value may be stored at a type is those operations' question, and this
A min and a max, the least and the greatest value the type admits. They are the answer to the question a range check asks and a saturating operation obeys, and they are determined in this order.
bounds metadata (min is the least value of r and max is the greatest, each being a value the range ADMITS: an exclusive endpoint reports the last value on its side rather than the endpoint itself, so a bounds of 0 .. 10 over an max of 9 where 0 ..= 10 has 10.min and max are the least and greatest values that width admits: 0 and 2w - 1 unsigned, and -2w-1 and 2w-1 - 1 signed. They are Numbers where w is 53 or fewer and BigInts above it, a wider min and max are the min is the MOST NEGATIVE max is its negation.byteLength from a string does.A binary floating-point type additionally has a minPositive, the least positive value its format represents, and an epsilon, the difference between 1 and the next value above it. An
The order of the first two steps is the substance: a refinement narrows what a type admits, so a narrowed type reports the narrowed answer, and a program asking what values a type holds gets one answer from one member whether the constraint came from the width or from the declaration. Without it, every reader that wants the question answered keeps a table of widths beside the code that reads the metadata, and the two say the same thing in two places.
These are min and max and not MIN_VALUE and MAX_VALUE, though the Number properties of those names are the nearest existing thing, because Number.MIN_VALUE is the least POSITIVE Number rather than the least: the most negative is -Number.MAX_VALUE. A float32.MIN_VALUE would have two defensible readings and no way for a reader to tell which was meant, and the minPositive gives the other reading a name of its own.
min and max report values and carry no exclusivity of their own. A program that needs the endpoints as they were WRITTEN, which a schema emitter does in order to distinguish an exclusive bound from an inclusive one over the value below it, reads the range from the bounds metadata, where it remains whole. The two are not alternatives: the members are a projection for the reader who wants the extremes, and the metadata is there for the reader who wants the range.
A isPlainData property, hasLayout it asks rather than asserts, so it never throws; a type with no layout is simply not plain data.
isPlainData and hasLayout answer different questions and neither implies the other in the direction a program needs. A class every one of whose fields is string is a D | null for a dynamic class D has a layout - a reference's - and is no hasLayout therefore reaches the wrong answer in exactly the case that matters, the one whose layout is a reference's width, and a program that laid such a type over bytes it controls would read a forged reference back out of them and disclose a real one writing them the other way.
The two questions a value is the type every plain the type every plain-data type is assignable to. plain is the narrower: every plain-data type is a string is a extends, a class-level where not being admitted, so a pool has no other way to state what it requires of its element. The three are separate because they vary independently, and the pair a given position needs differs: an array stride needs a layout, an assignment needs value-ness, and a byte view needs plain data.
A hasLayout property, byteLength, bitLength, or alignment from a type with no layout throws a hasLayout is for the program that is asking rather than asserting, and answers without throwing.
A generic serializer walking a heterogeneous structure asks this of every type it meets, and without a predicate its only route is to read one of the three and catch - control flow by exception for a question that is not a mistake, and a catch that swallows a misspelled type along with the answer. Testing for the property does not substitute: 'byteLength' in T is
hasLayout sits beside the three it guards rather than in a function of its own, so it is offset is
This is the rule of C and of Rust, which the layout of this clause follows: each scalar is aligned to its own size, and Rust's u128 is 16-byte aligned in order to match C. Capping the alignment at 8 was considered and is not what this specification does, because it would make two types of one width disagree for no reason a program could observe - a 16-byte align is for (#[repr(align(N))] is in Rust, and lowering it to 1 is what packed is for.
These are properties of an interned sizeof operator, which is why they need no grammar and work wherever a type does, including on the result of Reflect.typeOf. The layout they report is the declared one, so the controls of byteLength is what the program asked for, not what an implementation would have chosen.
The alignment of a named numeric type is its byteLength. For an int.<N> or uint.<N> of a width no named type has, the alignment is the smallest power of two that is at least the bits rounded up to a byte; the uint.<4>, whose byteLength is 1, and this rule agrees with it and with every named width. It is the general rule above restated for the widths no name int.<96> aligns to 16 and pads to a multiple of it, which is the only reading on which an array of them stays aligned and the only one on which two types of one width do not disagree.
The layout of a
bitLength under 8, it is a bit-field: it is placed at the cursor, or at the bit its offsetBit names, and the cursor advances by its bitLength.offset names, where it has one; at the cursor, where the class is packed; and at the next multiple of its alignment otherwise, that alignment being the one its align names where it has one and its type's otherwise. The cursor advances to the end of the The class's alignment is the one its alignAll names, or 1 where it is packed, or the largest alignment among its fields otherwise. Its bitLength is the cursor's furthest extent, and its byteLength is the size its size names, or that extent rounded up to a byte and then to the class's alignment. It is a size fixes, or for an inherited
A Reflect.getReflection.<Reflect.ClassFieldLayout, T>(name) reports its placement, ClassField context beside it reports what the offset and byteLength because those two are what a decorator commonly wants; the bit-level placement is meaningful only for a class that has a layout at all, which is why the two are separate contexts rather than one. This is the offsetof a serializer, a placement construction, or a vertex attribute descriptor needs.
Rounding a class's byteLength up to its alignment is what makes an array of it an array: without the trailing pad, the second element would be misaligned. class A { a: uint8; b: uint16; } is therefore 4 bytes and not 3, and [10].<A> is 40.
Because layout follows declaration order and this specification never reorders, { a: uint8, b: float64, c: uint8 } occupies 24 bytes where { b: float64, a: uint8, c: uint8 } occupies 16. Reordering would be sound for a program that only reads its own fields and wrong for every one that does not, and views, serialization, and interop are exactly the cases that do not. Since byteLength is a constant, a program that cares can assert the size it meant to have.
Inheritance appends, so a base's fields keep their offsets and a subclass's follow under the same rule, and class B extends A is laid out as the flattening of both. That is what lets a B be passed where an A is expected without copying: the prefix is an A already.
Seven reserved names control the layout above. Each is a property-descriptor key and each has a decorator of the same name that sets it, so a program may write either and a tool that generates classes need not write decorators. A control's argument is a numeric literal: a layout is fixed at the declaration and byteLength is a constant a program may assert against, so the value has to be known there, and a literal is the form that guarantees it without bringing an evaluation order to a type position. This is #[repr(align(N))]'s rule in Rust for the same reason.
| Name | Applies to | Effect |
|---|---|---|
packed | A class | Each |
alignAll | A class | The alignment of the instance as a whole. It composes with packed, which decides the fields' placement while this decides the instance's. |
size | A class | The allocated size, zero-padded to it. |
align | A | Requires the |
offset | A | Places the |
offsetBit | A | Places it that many bits from the start of the allocation, which is what fixes bit order exactly. |
endian | A multi-byte |
These apply only where every dynamic class may gain a Object.defineProperty with a type key, and that
An align replaces its float32x4 at @align(4) follows a float32 at byte 2 at byte 8, not at byte 16. That is what makes the control useful in both directions, and it is the program's to get right, since an implementation may hold a
Two fields given one offset occupy one memory, which is the C union, and a negative offset overlaps a base's. That is deliberate reinterpretation, for reading a float32's bits as a uint32, and it is not how a program should write a tagged union: a sealed abstract class hierarchy is that, and the language checks it for exhaustiveness (
Overlapping fields are the one place a bits is checked against bits's type and a store through value against value's, and neither knows the other shares its bytes. So a program that writes a float32 through one uint32 through another gets the bit pattern reinterpreted, which is exactly what it asked for and is the reason the C union is expressible at all. The type system's guarantee is over what a DataView over a offset placing a offset.
Reading or writing a boolean8 and its family are one bit per lane and belong to the vector types, where a
Automatic packing reaches only a uint.<12> occupies 2 bytes unless an offsetBit places it, which is worth knowing for a wire format whose fields are 12 bits wide. The bitLength rather than a byte is a question it should answer.
The layout of a
A
A T | null for a reference to close it.
A _T_ | *null* for a T that is a
The discriminant is a declared byte rather than a spare bit pattern of T, EXCEPT where T's declaration excludes a pattern: an bounds metadata (
A bounds range excludes a pattern where an endpoint is open, which excludes its own value, or where a closed endpoint does not reach the corresponding extreme of the base. A range with neither endpoint, or one whose closed endpoints reach both extremes, excludes nothing and declares no niche. Only an
This is Option<T> in Rust and in Swift, std::optional<T> in C++, and Nullable<T> in C#. All four lay the payload out inline and all four reserve a separate spelling for the case reached through a pointer.
The representation was a reference, and the change is a layout decision rather than a semantic one because the indirection was never observable: storing into such a === compares fields, and the value cannot be held weakly. Those are the channels through which a separate allocation makes itself known, and each is closed, so what remains is the layout this clause defines. A one-byte payload cost a reference's width and an allocation carrying an implementation's header; it now costs two bytes, and an array of a thousand of them one allocation rather than a thousand and one.
An inferred niche, as Rust performs one, is deliberately not taken. byteLength is a NonZeroU32 rather than through inference.
A cycle is not broken automatically. An inference that laid out inline where it could and indirected where it could not would have to choose an edge in a mutual cycle, and any choice makes one of those layouts a function of declaration order or of the order an implementation walks the graph; worse, it would make the choice invisible, so that a program adding a
A window's length is a non-writable, non-configurable own property. It is a
Span.<T> is a FIXED-LENGTH WINDOW over a run of elements of T that it does not own. A second argument is optional and states the length: Span.<T, N> is a window of exactly N elements, and Span.<T> one whose length is not stated. Its elements may be read and written; its length cannot change. An array view (Span.<T> over the bytes of a buffer, and a projection of an SoA column (Span.<T> over that column; both were this type before it had a name.
The array types say what is known about an EXTENT: [N].<T> states one and [].<T> states that it varies. Ownership is a separate question, and Span.<T> answers it: the value does not own its storage, which is why it has no capacity, no reserve, and no shrinkToFit (new Span.<T>() is a Span.<T>(buffer) reinterprets bytes that already exist, which is why it is a call and not a construction.
A Span.<T> is INVARIANT in T, for the reason the array types are (Span.<uint8> used as a Span.<number> would admit a Number into storage uint8. Span.<any> is admissible on the same ground the array types admit [].<any>, that a store through it is checked against the storage's own
The window is writable, and calling it a read-only view would give the wrong answer here: a read-only type could be covariant in T and this one cannot, because a write through it reaches storage whose
An access outside the window's length is a [N] . < T > is attempted GROWTH and a [ ] . < T > GROWS the array, and only an array with no
[].<T> and [N].<T> are both assignable to Span.<T>, and a tuple is assignable to it when every position's type is T. The reverse never holds: a window is not assignable to either array type, since neither the storage nor the right to grow it is the window's to give. Span.<T> is a member of the array and tuple family, so a type parameter bounded by [] admits one.
A source reaches Span.<T, N> only where its length is known to be N: a [N].<T> does, and a [].<T> does not, its length being a run-time fact. Span.<T, N> is assignable to Span.<T> and not the reverse, since forgetting a length is always safe and inventing one is not.
The length is optional rather than always present because the two cases are genuinely different. A view over a resizable buffer cannot carry its length in its type — that is why
std::span<T, Extent> and &[T; N] are the same
A coercion MATERIALIZES: it produces a window value distinct from the array coerced, and two coercions of one array need not be the same value. An implementation may elide the construction where the window does not escape. Views and SoA projections already answer this way, and a coercion that instead passed the array through would make one static type stand for two different kinds of value, which is the confusion this type exists to end.
A window does not confer ownership, so obtaining an owned array from one is explicit: a spread or a slice copies the elements into a new array, and no implicit conversion performs that allocation.
A window names a run of elements in storage it does not own, so it is a reference in the sense of Span.<T> over a growable array is INVALIDATED when that array's allocation relocates — by a push that outgrows it, by a reserve that reallocates, or by a shrinkToFit that releases — and a read or a write through an reserve for room the array already has, and a shrinkToFit on an array already at fit, leave every window over it valid.
A window over a [N].<T> is never
This is the same rule an SoA projection already states and the same one a ref into an array already obeys, which is what makes it one rule rather than three: a window is a reference to many elements where a ref is a reference to one, and relocation is what invalidates either.
A fixed buffer-backed Span, viewed SoA or placed allocation records its extent. At every access, require that its buffer is not detached and that the current buffer length
A length-tracking Span derives its length from the current buffer; a missing index follows the ordinary out-of-range rule. A reference through a byte view keeps the view's checks and follows the same recovery rule. A retained class element and each nested placed
Construction rejects an actually detached buffer with a
A view obtained by subarray, window (including its fixed-count form), or reinterpretation retains each original owned-allocation obligation carried by the source. Do not replace a
Before publishing a derived view, validate the source under its existing liveness and current-bounds rules. An invalid source throws a
After successful derivation, an explicit byte view uses its own extent together with the inherited allocation obligations. It does not require all ancestor byte extents to remain
Acquiring the buffer of a
Span.<T>(buffer, byteOffset, count, byteElementLength) is a VIEW over bytes that already exist, as distinct from the allocations of Span.<T> ([].<T>(buffer), which named the growable array type to produce a value that owns nothing and cannot grow; the spelling now says which type it makes. Each is a call on the type rather than a new, because nothing is constructed: a view aliases storage a program already has.
buffer may be an ArrayBuffer, a SharedArrayBuffer, any [N].<U>, whose bytes are the ones its buffer accessor gives it - so Span.<T>(a) is Span.<T>(a.buffer), and naming the array rather than its storage is a spelling rather than a different operation. A DYNAMIC [].<U> is not a source: its length is a run-time fact, and a window over bytes sized to the length it happens to have would either freeze the array or come to describe storage it no longer uses. Two views over one buffer alias the same memory - which is the point of reinterpreting a buffer, and why an implementation may not use the element types to rule aliasing out. byteOffset defaults to 0. A view holds WHOLE elements: where the bytes available from byteOffset are not a multiple of the stride, the count is the byteLength and may be smaller or larger than it, so a view may read one %TypedArray% and DataView cannot address interleaved data at all, so a signature that puts the stride where they put a count would read as familiar and mean something else.
A view with no count is LENGTH-TRACKING: its length is the bytes available past byteOffset divided by the stride, so it grows and shrinks as a resizable buffer is resized. A view given a count is FIXED at that many elements and records its byte extent at construction. This is the distinction %TypedArray% and DataView already make by the same means — new Uint8Array(buffer, 0) tracks and new Uint8Array(buffer, 0, 8) does not — and it is spelled the same way here so that a reader who knows one knows the other. A fixed view whose bytes cease to exist reports a length of %TypedArray% in the same position does, and refuses every access; it is a
A store through a view is checked against T as any store to a
A length-tracking view answers a shorter length where a fixed one is out of bounds, and the difference is not a preference: a fixed view's extent is part of what it promised at construction, so missing bytes prevent access until its extent is
SoA.<T, Length> stores its elements as one array per [N].<T> is: no user-defined class can express it, because declaring one array per
Length follows the array types: 0, which is the default, is growable, and a non-zero value is a fixed extent. T must be a
The layout is the row byteLength is the sum of the columns rounded up to its alignment. The split is ONE LEVEL: a
An element's fields are not adjacent, so there is no interior padding between them, and a T whose interleaved layout pads to a larger stride has a smaller byteLength here than [Length].<T> does. A elementByteLength, the per-element sum of the column strides, which for the same reason is not the interleaved stride.
A growable SoA.<T> has no layout as a type, exactly as [].<T> has none; its instances have a byteLength.
Each column is aligned to its SoA a different size on different implementations and would cost byteLength its place among the compile-time constants of SoA's allocation is PLACED is a separate question and the implementation's own: the base's address is not part of what the type describes, so an implementation may put every allocation on a vector or cache-line boundary and is encouraged to.
Length is defaulted, so SoA.<T> and SoA.<T, 0> name ONE type, as a defaulted parameter does everywhere.
An SoA and a [N].<T> are distinct types with distinct layouts and neither is assignable to the other, whatever their elements. Conversion is explicit and copies.
fields projects each of T's immediate fields as a Span.<F> (SoA is INVALIDATED by a growth that reallocates, and an access through it afterwards throws a SoA does — a projection that kept reading the abandoned allocation would disagree with its container and say nothing. A projection of a fixed SoA.<T, N> can never be
Invalidating rather than preventing is what a garbage-collected language can express. A borrow checker refuses to compile a program that grows a container while a projection of it is alive, and pays nothing at run time for the guarantee; here nothing marks the end of a projection's life, so a container that could not grow while one had ever been taken could never grow again. The check is loop-invariant — the generation cannot change without a call — so it is hoisted rather than paid per element, and the fixed form has no check at all.
A function declaration may carry where clauses, the
The production below restates the one
Within a return is a return to occur as an expression anywhere else, which costs nothing, since the token is ungrammatical in expression position today and no program can be using it.
A contract is read two ways, and both are load-bearing. It is verified: at every concrete evaluation of the builder, once return bound to it, and a clause that is falsy is a
This is the trade C++ concepts make, recovered inside a function sanitize<T: type>(value: T): omit(T, ['password']) checks at its declaration because omit's contract says Reflect.isAssignable(T, return), and { ...value } is a T.
Direction is the thing to get right and is easy to invert. An upper bound, that the result is assignable to something, serves a consumer holding the result. Checking a body that produces the result needs a lower bound, and for omit the true one is that T is assignable to the result, since removing properties yields a supertype. A contract stating the reverse would be both true of nothing and useless to the body.
Two limits are worth stating rather than discovering. A contract is a proposition about one evaluation, so universally quantified knowledge, the variance of omit(T, K) in T or any parametricity fact, is not expressible as one and stays unavailable before
Inference reads plain parameter positions (
Known primitive object-spread contributions participate in the literal's final member facts.
The required-member presence judgment includes established Symbol keys as well as String keys. Compare Symbol identity, not its description. Reject a missing required Symbol member only when both the final literal contributions and the relevant prototype lookup establish absence; an inherited property may satisfy the ordinary structural read. Optional members permit absence. Unknown computed keys, prototype effects and
An object literal checked against an expected object type is checked freshly: an own property the expected type neither declares nor admits through an index signature is a f({ a: 1, b: 2 }) is checked f's parameter, and f(o) is not.
Without this an all-optional
A generic parameter that ordinary inference leaves unbound and whose constraint, or for a value parameter whose domain, M: 'fast' | 'safe' names the same candidates as M: type extends 'fast' | 'safe', so the two spellings infer alike. A constraint sealed class, whose candidates are the members in declaration order, the enumerators in declaration order, and the class with its direct subclasses transitively, the boolean, whose candidates are its two literals; a literal type, whose one candidate is itself; and an array of stated extent whose element [2].<boolean> proposing its four tuples — each tuple counted against the budget as a constructed
A parameter is trialed against exactly the arguments whose declared types mention it and no other unbound parameter; an argument whose type mentions two or more is not separable and takes part in no trial. For each candidate in order, that candidate is substituted for the parameter throughout those arguments' declared types (
The count is a sum over the parameters trialed rather than a product across them, since each is trialed alone. Trials are counted against a ceiling of 64, which is this clause's own and not the
A fixed number here and a
This is overload resolution's logic pointed at apply({ kind: 'circle', radius: 3 }) against apply<T: Shape>(patch: partial(T)) trials Circle, whose partial the literal satisfies, and Rect, whose kind it contradicts, and infers Circle because exactly one survived.
Separability is a determinism rule before it is a cost rule. merge<A: Shape, B: Shape>(a: partial(A), b: partial(B)) runs the members of Shape for A and again for B, never their product, and a pair(A, B) does, asks for explicit arguments rather than searching a space whose size is the product of two constraints.
A inverse decorator naming another, which the checker consults for a parameter that ordinary inference and trial
A
Four details complete the rule. The argument's type, for a parameter reached through a REST (ref argument contributing its referent's; the inverse of a builder over a pack receives that tuple and returns the pack's tuple, one rule for scalars and packs alike. A f declares no inverse, so T cannot be inferred through it; supply explicit type arguments" - and a f's inverse proposed X, which its forward evaluation does not accept for these arguments".
The association is recorded through the declaration's own decoration and nowhere else: the inverse decorator's primitive accepts only the inverse decoration of a function declared in the source text with its other type declarations (inverse decoration declares no inverse, which the pass knows from the declaration alone.
The association lives on the declaration because that is the only place with one possible author and no timeline: an imperative registration would make inference depend on module evaluation order and let unrelated modules contend for one builder's meaning, and a decorator has neither failure mode and is read where the checker is already reading. The
This rung is last on purpose. The first two need nothing from an author: a builder in a return position infers plainly, and a closed constraint trials. An inverse is for the remainder, and it should be adopted when porting demonstrates the need.
For numeric indexing whose effective selected operator [] is statically known, a store requires either a matching set operator [] or a read operator returning a reference location. A known value-returning read operator without a setter is not a writable location: assignment, compound fallback, update and destructuring or iteration stores to it are type errors. A declared compound operator that performs no store remains a call; a definitely skipped logical store is not judged as a write. Unknown selection defers to the runtime check. String-key property access remains ordinary property access, and a reference-returning operator retains all permission and liveness checks of
An operator is declared in one of three places, and they differ only in what the receiver is: on a class, where the receiver is the left operand; on a class reopened elsewhere, by the extension form, which appends operators to a type declared in another module; and in a primitive block (
An operator+.<T: type>(rhs: T) declares a parameter, operator+.<uint32>(rhs: uint32) fixes the argument, and a mix of the two is a selector-prefixed overload. The leading . stays because the operator token may itself end in < or >.
A
The ... productions are the Symbol.iterator method. Like any operator it may be overloaded on its return type, and untyped iteration uses the first declared overload. It yields values rather than references.
The last alternative is the abstract method of abstract abstract is what permits that absence. Writing the
The alternative is written out rather than expressed as a static, a get, set, async and generator forms of
An operator's own type parameters are written .<…> rather than <…> because the operator name may itself end in < or >: operator<.<D2: Dimensions> lexes where operator< <D2: Dimensions> would collide with the << token. This is the one place a declaration
Resolution reads the left operand's type alone. For a * b, the operators of a's type are searched for a signature whose parameter accepts b, and the operands are never reversed to find a match. Return type overloading applies as it does to any function (primitive block adds to a global operator table; two definitions for one pair of types is a static operator defines an operator on the class object rather than on its instances, so that A += 5 may mean something, and takes no part in any of the above.
|> may not be declared (
Never reversing the operands is the rule that makes Matrix4 * Vector4 and Vector4 * Matrix4 different operations, which in a column-major library they are: one transforms a column vector and the other a row vector. A language that reverses operands to find any match makes that bug unfindable. It is also why 2 * v needs a primitive block rather than falling back to v's operators: the left operand is a number, and the declaration belongs where the library author can write it.
The block is made generic by an interface constraint rather than written once per pair, so primitive float32 { operator*.<T: type extends ScalarMultiply.<float32, T>>(rhs: T): T } 2 * v reaches it because viability filtering precedes ranking (Vector4 satisfies ScalarMultiply.<float32, Vector4> and not the number instantiation, so the number candidate is eliminated before the number-first order could prefer it. Division does not commute, so no such block exists for it and 2 / v stays a valueOf, toString or Symbol.toPrimitive other than those of + with an operand that may be a String is concatenation and is not reached. Where the instance is the right operand, the rule applies only where no primitive block could supply the operator, since dispatch reads the left operand. A class that is not
A global table is why an operator-bearing type is a nominal class and not an alias. Were Vector4 an alias for float32x4, its operators would land on float32x4 itself and any other library aliasing that lane type would collide on every shared pair. Two libraries with two nominal Vector4 classes coexist, because the parameter types differ. The division the
A comparison between two vectors of one float32x4 comparison yields a boolean32x4 of lanes all-
Which comparison each operator performs on a lane is what it performs on a scalar. A hardware comparison of floating-point values chooses among predicates that differ on two axes: an ordered comparison is <, <=, >, >=, and == are ordered and != is unordered, which is the reading that makes a lane agree with the scalar operator of the same spelling: NaN == NaN is NaN != NaN is a == a && b == b.
The lane counts the shorthand names cover are the register widths, which is why float32x4 has a name and a three-lane float vector does not. A vector of any other lane count is written vector.<T, N>, which is what a narrowing accessor produces, and a library wanting a name for one declares it.
The three forms are three instructions, which is the point of the overloading rather than a consequence of it: the wide a < b has no defensible default among three instructions, and a coin flip here is a performance cliff a program cannot see.
The bit vectors are the one place a lane and a bit coincide, and the numbering is stated as a rule in movmskps and vpmovmskb number their results the same way, which is why the rule costs nothing to honour. They are not the bit-fields of
complex.<T> is a complex64 holds two float32 components; the bare name complex is complex.<number>.
A numeric literal in a complex position takes the complex type, with the literal as its real component and zero as its imaginary one, so let r: complex = 5 is complex(5, 0) and an imaginary literal beside a real one reads as the mathematics does. A real VALUE does not convert on its own: _z_ + _x_ for a complex z and a number x is a _z_ + complex(_x_), which is the rule that holds between any two numeric types here. The literal's representability is the component type's, so a literal no float32 can hold is no complex64 either.
complex(0, 0) is falsy as every other numeric zero is. A complex is not one of the existing language types, and
An imaginary literal (complex, with the literal's value as its imaginary component and zero as its real one, so 4i is complex(0, 4). Literal propagation applies as it does to any numeric literal (complex64 position is a complex64. complex64 and complex128 convert to and from complex, and to each other, explicitly and not implicitly, exactly as float32 and float64 convert to and from number and to each other, and the treatment of a value outside the component type's range is
Bare complex has number components for the reason the language keeps number and float64 distinct: number is what an untyped literal and every Math function produce, so a complex built from Math.PI composes without a cast, and the fixed widths exist for an interleaved buffer and for interop. Being a complex copies on assignment and lies inline, so a [].<complex128> is the interleaved buffer an FFT expects and
It is a
A range is a Range.<T, S, E> carrying both endpoints, RangeFrom.<T, S> and RangeTo.<T, E> carrying one, and RangeFull.<T> carrying neither. Each implements RangeBounds.<T>, which is the interface a consumer of an arbitrary range is written against and the type a bound-valued position takes. S and E are values of Bound, either Bound.Closed or Bound.Open, one for each endpoint the Interval, which a range exposes and a diagnostic prefers over the parameterization, and which is never itself a parameter: a case label and an argument list, without further rule.
The type parameter of a concrete range
Real numeric endpoints use their intrinsic exact order, including object-backed decimal, rational and float128 values. Their representation does not require a user-declared ordering operator or ordinary conversion. NaN and complex values are not
Without a construction context, a range's endpoint domain is the canonical union of the runtime types of its present endpoints, or the one endpoint's type for a one-ended range. A full literal takes its domain from construction context, or
Membership in a concrete range RangeBounds.<T> instead checks that the value is a range and that each present endpoint already satisfies T; it does not change the concrete
(0..<5) is Range.<uint8> is const r: Range.<uint8> = 0..<5 constructs const r: Range.<uint8> = 0..<256 is a uint8. Iterating the literal 0..<256 at uint8 remains valid under the separate produced-element rule.
RangeBounds.<T> carries the operations that are total over the four shapes, because it is what a holder of an arbitrary range has. contains takes either a value or a range: against a value it is membership, and against a range it is containment of one point intersect is the point-RangeBounds view with the endpoint domain specified below: the intersection of a RangeFrom and a RangeTo is a Range, and the intersection of any range with the full range represents the same point
Intersection, containment and emptiness are ordering operations. They use stored endpoints, not public endpoint property reads or an arithmetic reconstruction. Numeric comparison is exact mathematical comparison, including wide < first and, if that is < with exchanged operands. When both are
Intersection selects the greater lower endpoint first and then the lesser upper endpoint. An omitted bound contributes no constraint. At equivalent endpoints, retain the left input's endpoint value and choose an open bound if either input bound is open; otherwise choose a closed bound. Preserve selected endpoint values and their domains without conversion, rounding, wrapping or snapshotting. Disjoint operands retain the crossed endpoints and therefore produce an empty range.
The result endpoint domain is the canonical union of both operand domains, independent of which endpoints are selected. A full range whose domain is RangeFull.<T> values intersect to RangeFull.<T>. The concrete intersect<U>(other: RangeBounds.<U>): RangeBounds.<T | U>, subject to mutual ordering and the full-
For emptiness, an endpoint-free range is not empty. A one-ended numeric range is empty when its only endpoint is positive infinity at an open start, or negative infinity at an open end; a closed extreme remains non-empty. Other one-ended ranges are not empty. For two present endpoints, compare the start with the end. It is empty when the start is greater, or when they are equivalent and either bound is open. For range containment, first determine whether the inner range is empty and, if so, return
Commutativity and associativity of intersection concern the represented point sets. Domain union is also commutative and associative. Equivalent endpoint representatives may have distinct signed-zero representations or class payloads: selecting the left representative does not assert that these values have identical representations or reference identity. No new canonicalization of endpoint payloads is implied.
scale multiplies both endpoints by a scalar and is present on the instantiations whose Temporal.Instant has an order and no arithmetic, and has no scale. A negative factor exchanges the endpoints and the bounds that go with them, since the image of a half-open range under negation is half-open at the other end, and exchanges RangeFrom with RangeTo for the same reason. A zero factor yields the closed range at zero rather than the result of multiplying each endpoint, which for a half-open range would be empty, and leaves an already empty range empty.
This clause defines the built-in numeric scale operation and unary negation of a range, which uses the same operation with factor negative one. Supported bases are
A call performs these steps in order. The ordinary evaluation of the receiver and argument expressions precedes the call.
An
For a binary or decimal floating product, choose the greatest representable value no greater than the exact product for a final lower endpoint, or the least representable value no less than it for a final upper endpoint. The final role accounts for the factor's sign even though processing remains in original source order. If rounding moves the value, make its bound closed; otherwise retain its source bound. A
Exact positive-one copies retain decimal cohorts. Exact negative-one reflection negates a decimal coefficient while retaining its exponent. Other decimal products use the reduced coefficient/exponent representation of their directed result, with zero represented canonically. This rule does not infer a preferred precision from an arbitrary rational factor. Infinite decimal endpoints preserve their numeric base and infinity sign under the preceding sign rule.
To admit a manufactured endpoint, apply its full numeric parameterization's existing checked admission when it has one, propagating any exception. Admission must preserve the computed
The floating result is a conservative enclosure of the exact image. (1..=1).scale(rational64(1,10)) has Number bounds 0.09999999999999999..=0.1. Metadata rescale hooks using bounds.scale(factor) inherit this enclosure rule, including possible widening. Ordinary scalar metadata conversion retains its existing conversion and rounding rules. Binary
The built-in + and - operations on two ranges compute coordinate addition and subtraction in the supported real numeric representations of
After ordinary left-to-right operand-expression evaluation, perform these steps in order.
When either contributor to a result endpoint is absent, that endpoint and its bound are absent. Do not manufacture infinity or invoke endpoint admission for it. Otherwise both contributors are present. For
For present infinities, subtraction first reverses the right infinity's sign. Adding a
Exact cancellation produces positive binary zero. When both
Apply the agreed full numeric parameterization's existing checked admission to every manufactured endpoint. Propagate its exceptions and require it to preserve the calculated
An erased range with uniformly RangeFull.<uint8>; two full-RangeFull.<any>. Explicit union domains do not choose an arithmetic member. The result-domain rule selects the representation of a new computed range and does not convert either input. Multiplication and division are specified separately in
The built-in * and / operations on two ranges compute coordinate enclosures in one agreed supported real numeric endpoint type. They do not invoke arbitrary scalar operator bodies as an enclosure algorithm or infer a quantity's result length. Using the same range as both operands still supplies two independent coordinate choices.
After ordinary left-to-right operand-expression evaluation, perform these steps in order.
For candidate calculation only, describe each present
Visit the four pairs in this order: left-lower/right-lower, left-lower/right-upper, left-upper/right-lower, left-upper/right-upper. A candidate starts closed only if both contributing coordinates are closed. Its multiplication or division sign is negative exactly when one contributing coordinate has negative sign, including a negative binary zero or negative infinite limit. Apply the following rules without intermediate conversion through Number.
Select the mathematically least and greatest candidate, comparing exact fractions and signed infinite coordinates without narrowing. A non-empty operand pair passing the division guard supplies at least one candidate. At an extreme attained by several candidates, combine their closedness by disjunction. At a tied infinite extreme, combine absence marks by disjunction; a negative-infinite lower extreme or positive-infinite upper extreme marked absent produces no result endpoint or bound, even if another tied candidate was present. A positive-infinite lower extreme or negative-infinite upper extreme retains a present infinity bound even with absence provenance; it still constrains the result to that extreme, which a present source infinity necessarily established. Other selected infinities also produce a present infinity of their sign in the agreed type. At a tied exact
In addition to a closed corner, interior values can establish attainment of a selected extreme. A
Every manufactured present endpoint receives the agreed full numeric parameterization's checked admission, with the mathematical-value, binary-zero-sign and decimal-cohort preservation checks specified for numeric scaling. Propagate hook exceptions; a representation change throws a
(0..=0) * (0<..<1) is closed zero, and (Infinity..=Infinity) * (1<..<2) is a closed infinity singleton: endpoint exclusion does not make a constant image empty. (2..) / (2..) has an open zero lower bound and no upper bound. (0<..=1) * (1..<Infinity) throws because its explicit boundary coordinates are indeterminate; replacing the infinite endpoint with absence permits the private-limit calculation. (..) / (Infinity..=Infinity) is closed zero under this bound-information contract. Missing bounds do not enumerate implicit infinite inhabitants. These rules preserve the distinction between absent constraints and supplied endpoint values rather than defining an algebra of all subsets of IEEE values.
The full-range division fallback deliberately retains the existing conservative contract. A divisor such as 0<..=1 can have a tighter one-sided quotient enclosure, but this operator does not switch to a partial-image or split-
The bound is per endpoint rather than one four-valued RangeFrom parameterized by an
scale is conditional on the
It is a <, <=, >, or >=. A range's endpoints being
A range is written with a member of the .. family. The four two-endpoint forms a..<b, a..=b, a<..<b, and a<..=b produce the half-open, the inclusive, the open, and the open-start inclusive-end a.., a<.., ..<b, ..=b, and .. omit an endpoint. A range that has an end always marks whether it includes it, and a range marks its start only where the start is exclusive, so there is no a..b and no ..b: a bare .. appears only where the endpoint it faces is absent and has no inclusivity to state.
The family is six tokens, .., ..<, ..=, <.., <..<, and <..=, each taken whole by longest match and each decided by a fixed lookahead. At a . the second character separates the type argument list .< from the rest of the family and the third separates the spread ..., ..<, and ..= from ..; at a < the second character separates << and <= from the family and the fourth separates <..< and <..= from <... The .< that opens a type argument list and the ..< of a range are therefore told apart by their second character, before a third is read, and no input reaches a state where both are <.. costs the grammar one reading, a relational comparison whose right operand begins a range, and costs it nothing else, because generic application is spelled .< and a bare < is always relational or a shift.
The family collides with the numeric literal grammar, since 1. is already a complete numeric literal, and the collision is resolved by one lexical rule: a . is not the decimal point of a numeric literal when the character after it is also a .. So 1..<6 is the three tokens 1, ..<, 6 rather than 1. then .6, while 1., 1.5, and the spread ... are unchanged, and a form that marks its start needs the rule not at all, since the < of 1<..<6 ends the literal before a decimal point is in question. The one construction the rule changes is an .. and a member name, the 1..toString() idiom, which is a Syntax Error rather than a member access on a number, because 1.. is a complete from-range and an identifier cannot continue it. One further lookahead is extended by a character: ?. is not the optional chaining punctuator where a decimal digit follows it, and is likewise not the punctuator where a . follows it, so that cond?..<b:c is a conditional whose consequent is a range. The extension changes no program, since ?. followed by . is a Syntax Error under every production that consumes the punctuator.
A range binds tighter than assignment and looser than || and ??, so its operands are ShortCircuitExpressions, and it is non-associative. The pipeline operator (0..<10 |> f(%) pipes the range rather than ranging over a pipeline. Continuing: a..<b..<c is a Syntax Error rather than a nesting. Member access binds tighter than the range, so 0..<arr.length is 0..<(arr.length) and a range reaches its own members through parentheses, as in (0..<10).length. Because a range is an expression it appears in a case label, an object literal, and an argument list with no further rule, and a statement that begins with .., ..<, ..=, or <.. continues the preceding expression, joining the other leading-operator forms on the list of automatic-semicolon hazards.
Because each token is taken whole, the spacing of the family is significant at its edges. a..<b is the half-open range where a.. < b is a relational comparison against the from-range a..; a<.. is the open-start from-range where a < .. is a comparison against the full range; and a <.. < b is a comparison against a<.. rather than the open range, since <..< is one token and spacing it apart does not assemble it. None of the spaced readings is a program at all: a range binds looser than the relational operators, so a range is never a relational operand and each of these is a Syntax Error. Parentheses put a range back under them, and there a range is REJECTED as a relational operand: a range does not implement Ordered, so (0..<3) < 5 is a
The value carries its start and its end, and its bounds are in its type rather than in a start and end are the endpoints, absent for an omitted one, and interval is the four-way name derived from the two bounds of a two-endpoint range; contains tests a value against the endpoints and their bounds; An endpoint may be INFINITE, and an infinite endpoint is not the same as an absent one: 0..<Infinity has an end and 0.. has none, so they report different endBound and interval and only the latter is isFull when both sides are absent. They contain the same values and iterate the same, a positive infinite forward stop stopping length is the exact BigInt count of the members of a bounded isEmpty is whether it has none, a descending range being empty rather than reversed and a range whose endpoints are equal being empty unless both of its bounds are closed; and reverse iterates the same members in the opposite order, which is how a descending traversal is written given that a descending range is empty. The nth value of an iteration is the start plus n times the step, counted from 0 where the start's bound is closed and from 1 where it is open, since a start that excludes its own value cannot yield it. A range iterates through its members by the rule of step supplies a step where the
A statically established built-in range-family receiver contributes the normal-result property types in Range.Bound Range.Interval undefined. Retain T's complete numeric metadata, generic parameter identity or class identity. An erased
| Receiver | start | end | startBound | endBound | interval |
|---|---|---|---|---|---|
Range.<T, S, E> and its aliases | T | T | B, narrowed to S when known | B, narrowed to E when known | I, narrowed to the derived member when both bounds are known |
RangeFrom.<T, S> | T | U | B, narrowed to S when known | U | U |
RangeTo.<T, E> | U | T | U | B, narrowed to E when known | U |
RangeFull.<T> | U | U | U | U | U |
RangeBounds.<T> | T | U | T | U | B | U | B | U | I | U |
Every receiver in the table contributes boolean for isEmpty and isFull, and bigint for length. A known bound selects a singleton based on the same intrinsic Closed, ClosedOpen, OpenClosed and Open. When a bound argument is not established, retain the whole corresponding
Use the same
These are normal-result types, not proofs that the read completes normally. In particular, length retains the preconditions in
The public read uses ordinary property access and its effects and
A statically established built-in range-family receiver with endpoint type T contributes a callable normal-result contract of RangeBounds.<T> for each of scale, take and drop. Retain the complete endpoint type, including metadata and generic or class declaration identity. Use this contract for dot access, established computed String keys,
This clause supplies a result contract independently of argument admission and operation availability. The factor or count position retains the numeric validation, absence, failure and evaluation-order rules of the operation's runtime clause. In particular, do not convert a count to an
After applicable declared operator dispatch, the built-in unary negation of a statically established range-family operand with endpoint type T contributes RangeBounds.<T>. For built-in binary +, -, * and /, establish both operands as built-in range-family values and read their endpoint types A and B. If A and B are the same type, contribute RangeBounds.<A>. Otherwise, if either endpoint type is RangeBounds of the other endpoint type. If neither condition holds, this rule supplies no common result
For union operands, apply the result judgment to every alternative or alternative pair and join the results only when every alternative contributes. A cyclic or unresolved alternative supplies no independent result proof. The judgment examines types without evaluating operands, endpoints, generic computations, getters, comparisons or metadata hooks. It creates no reference authority. Applicable user-declared operators retain their declared return contracts.
These contributions describe normal results, not proofs of RangeBounds view contributes possible endpoint absence under
A method's static return contract does not establish the identity or behaviour of its replaceable property implementation. Perform ordinary receiver evaluation, property access, argument evaluation and invocation. Retain any required receiving or return boundary under
A statically established built-in range-family receiver contributes a callable boolean normal-result contract for contains. Its candidate position is
The receiver's intersect member contributes a generic range-result view. Read the receiver's complete endpoint type T and the argument's established range-family view with endpoint type U. Normally contribute RangeBounds.<T | U> with a canonical endpoint union. When the receiver is statically established as RangeFull.<any>, contribute RangeBounds.<U>; when the argument is statically established as RangeFull.<any>, contribute RangeBounds.<T>. Both neutral shapes contribute RangeBounds.<any>. A full RangeBounds.<any> view does not prove absence and is not neutral.
A candidate whose range view or endpoint domain is unknown contributes RangeBounds.<any> result. For an argument union, include every alternative's
Read these contributions from the established source types, independently of the destination annotation. A destination cannot remove a participating domain or cause endpoint conversion or reconstruction. Retain complete metadata and generic or class declaration identities. A method view retains its receiver
Apply the same contracts to dot access, established computed String keys,
These contracts do not prove the identity of a replaceable method. Preserve ordinary property access, argument evaluation, invocation and
A statically established built-in range-family receiver contributes callable normal-result contracts for step and reverse. The step parameter is by: any = undefined; reverse takes no parameters. Their normal results are helper-bearing next. The numeric cursor ignores incoming values. Omission of by supplies next result is the discriminated union { value: I, done: false } | { value: undefined, done: true }. Its self-iteration method contributes an .value read must include possible exhaustion.
Derive I without evaluating endpoints. For a concrete range number, bigint, sized int and uint, and float16, float32, and float64. A literal endpoint type contributes the endpoint-fallback
The endpoint-fallback number; a Number endpoint does not select a RangeBounds receiver uses endpoint fallback even for a single numeric domain, because this endpoint-reading view does not prove the hidden stored domain. Thus a concrete range RangeBounds view of it contributes number. Unresolved generic parameters contribute
Apply these contracts to dot access, established computed String keys,
The contracts do not prove intrinsic method identity. Retain required inferred-initialization and return admission under
A statically established built-in range-family receiver contributes a callable [Symbol.iterator] member with no parameters. Its normal result has the helper-bearing next. Derive I by that clause's concrete-domain, endpoint-fallback and uncertainty rules. The result's self-iteration method retains the same three protocol arguments. A direct next().value read includes possible exhaustion. Neither the destination context nor the member spelling invents a concrete stored domain.
The original method acquires a fresh forward numeric cursor on every call, leaving the range reusable. The cursor's own self-iteration returns itself. This does not make a range a cursor or add a next member to it. Acquisition, missing-origin and unsupported-domain failures, item admission, progression and fused completion retain
Apply the view when the well-known Symbol key is established under the ordinary property-key rules, including dot and computed access to the unshadowed Symbol intrinsic, constant keys and object-pattern extraction. Unrelated Symbols and a shadowed Symbol name do not establish the key. Optional reads retain short-circuiting; extraction does not bind a receiver. Whole gradual receivers and mutable callee bindings retain the ordinary participation and stability rules.
Supply the same default protocol to established static iteration, flattening and delegation queries. A stored range's endpoint-literal ClosedRange.<1 | 3> can yield 2. Derive a consumer's yielded
Preserve a fresh range literal's produced-element fit judgment for contextual numeric spread destinations, including array, tuple-rest and function-rest positions. Its stored endpoint domain is not a second, broader item judgment that overrides that fit. A written unknown endpoint can select the runtime numeric carrier; another known Number endpoint alone does not establish Number items. Retain uncertainty absent an independently established context. For uncertain spread routing to fixed numeric parameters, the broad literal protocol alone does not prove every argument count impossible; distinct positions can have distinct numeric domains. Retain ordinary receiving checks rather than imposing one position's bound on the whole source next returns; it does not extend the cursor's active reentry guard through the receiving operation.
The view is a property-read and normal-result contract, not a permanent storage declaration or proof of intrinsic identity. Preserve ordinary lookup, getter effects, invocation and required receiving admission under next call. An incompatible
A simple loop binding whose type was inferred from the source protocol receives the ordinary inferred-binding admission before the body executes, with
A statically established built-in range-family receiver contributes the following callable contracts, where I is derived by
| Method | Parameter | Normal result |
|---|---|---|
toArray | None | [].<I> |
forEach | callback: (value: I, index: uint64) => void | void |
some | callback: (value: I, index: uint64) => boolean | boolean |
every | callback: (value: I, index: uint64) => boolean | boolean |
find | callback: (value: I, index: uint64) => boolean | I | undefined |
Apply these member contracts to established computed String keys, uint64; an already created uncontextualized function or a written
find includes forEach does not denote a value a binding may hold. The original forwarding methods acquire a fresh numeric forward toArray and forEach retain the refusal of an absent or positive-infinite stop before acquisition; some, every and find retain their short-circuit behaviour. Empty traversal does not invoke a callback to check its contract. Numeric acquisition and item admission follow
Neither the range method view nor the inherited helper name proves intrinsic identity. Preserve ordinary access and invocation effects and the required inferred-initialization and return checks under
A statically established built-in range-family receiver contributes callable contracts for map and filter. Let I be its item callback: (value: I, index: uint64) => U, where U is its generic output type inferred from the callback under callback: (value: I, index: uint64) => boolean; its normal result keeps item I. Filtering does not infer a narrowed item type merely from the predicate's body.
Both results have terminal next. Their next results are respectively { value: U, done: false } | { value: undefined, done: true } and { value: I, done: false } | { value: undefined, done: true }. Self-iteration contributes those same protocol arguments. Apply the inferred output substitution to the full result protocol as well as its item argument; a closed result must not retain an unbound output parameter in its self-iteration view. The results satisfy structural receiving contracts without resolving an unwritten global helper
These judgments also govern mapping and filtering of an established helper-bearing
The original range methods acquire a fresh numeric
Contextual callback binding admission, written annotations and Boolean predicate result obligations follow
A statically established built-in range-family receiver contributes a reduce callable contract with callback (accumulator: U, value: I, index: uint64) => U and normal result U. Let I be the cursor item
Select one accumulator contract for the call after establishing argument routing. If there is no initial argument, let U be I. If an initial argument is present, a fresh callback's written non-
Check established initial values, callback inputs, callback results and completed results against the selected contract under the ordinary checking rules. Do not widen an
A fresh callback's adopted parameters and result are enforced at their ordinary declaration boundaries. An explicit
The original range method acquires a fresh forward numeric
A statically established built-in range-family receiver contributes a flatMap callable contract whose callback takes (value: I, index: uint64), where I is the item
Let F be the normal flattened item next. Its result is { value: F, done: false } | { value: undefined, done: true }; self-iteration contributes those same protocol arguments. Inner terminal values are discarded. Only one iteration level is flattened: a yielded array or Promise remains an item, and an async callback result is not awaited to discover another
This is an operation
Creation does not call the mapper. Traversal invokes it and acquires inner
This judgment describes normal items reached by
@@iterator method, inspect the method's established callable result as the next callable result. A proved non-callable selected method, non-Object acquired next absence is a throwing branch. A method signature whose argument or receiver contract proves that its actual zero-argument invocation cannot enter contributes no normal items. Use the same bounded proof as the eligibility judgment; a viable or unknown invocation is not excluded. Unresolved callable or generic protocol evidence is unknown. A done: true contributes no item. Otherwise contribute the read type of value, including never. Any unknown alternative makes the never, not unknown. Recursive protocol evidence without a completed A precise normal-item
A range literal consumed at an iteration a..=b produces a through b, a..<b produces a through b - 1, a<..=b produces a + 1 through b, and a<..<b produces a + 1 through b - 1. An endpoint that is
Where T is a binary floating-point type, a range whose endpoints are both whole
The rule is over the elements a range produces and not over its endpoints, because a half-open range's end is not one of its elements. 0..<256 produces every value of uint8 and no other, though 256 is not a uint8; a rule over the endpoints would refuse it. The rule refuses 0..=256, 0..<257, and 0..<300 at uint8, each of which produces an element the type does not hold, so it verifies the consumer's iteration element contract rather than a stored endpoint contract. It does not decide a range whose endpoint is known only at run time and has no sized 0..<n for an untyped n; a
A s..<300 at uint8, with s a uint8, produces 299 whatever s is and is refused, while s..<256 produces 255 at most and fits; reading the literal 256 at uint8 instead would refuse it, though 256 is the stop and never produced. 300..=e with e a uint8 produces nothing for any e, and fits as any empty range does.
For a floating-point T the rule asks for exact values because a range rounds nothing: it yields the float16 range reaching 2049 would yield 2049, which float16 cannot hold, and converting it would give 2048. A
The lexical rule is the whole of what the family costs the numeric grammar, and it is the trade Rust made in requiring 1.0 rather than 1. so that .. could be a range. The alternatives were weighed and ... reads as spread to every reader and Ruby's ../... pairing is a known hazard, and a ..< aside as well, on the ground that .< already opens a type argument list; that was an error, since the two are told apart by their second character and never compete, and the syntax specified here rests on the correction.
That every end is written is what makes for (const i of 0..<array.length) the loop written without thinking rather than one short. The reason is not that the reader recalls which reading of a two-dot form this language chose, but that there is no two-dot reading to recall: the languages that have a..b do not agree on what it means, half-open in Rust and inclusive in Kotlin, Ruby, Perl, Pascal, Ada, and Haskell, so a default would be a convention a reader has to carry rather than a fact the source states. The cost is charged where the disagreement is. An end is two characters longer in every range that has one, and a start, which every language spells inclusive, is marked only in the ranges that exclude it.
A case whose label is a range matches where the range contains the discriminant, which is what a switch over a float discriminant needs, since a float has no enumerable cases to list. Matching is by containment rather than by ===, and the cases of such a switch are tested in source order, so overlapping ranges resolve to the first that contains the value rather than being an error.
A range iterates where it has a step, which over an for over a range has the range's for (const i of 0..<N) binds i at a type bounded below by 0 and above by N minus 1. Where N is a [N].<T> by that counter needs no check, which is the case
The bound is the range's own for: the counter's type carries the fact, and the ordinary subtyping of a bounded parameterization discharges the check the way it discharges any other. That the range states its end is what makes for (const i of 0..<array.length) right rather than off by one, and it is why the
The nth value of a stepped range is the start plus n times the step rather than the previous value plus the step, and iteration stops against the value rather than a computed count. Both halves matter over a float
This clause specifies forward implicit iteration, numeric step, and reverse for number, bigint, sized int and uint, and binary float16, float32, and float64 item types. A parameterization over one of these types uses its base for numerical progression and retains the full type at item admission. Other
The numeric range item type is the stored endpoint domain when that domain is a concrete numeric type or numeric parameterization number. A plain Number endpoint does not supply a
Each acquisition produces a fresh
reverse require each present step performs forward traversal. Its argument must be a positive A numeric range next method performs the following steps:
Once exhausted, an
The cursor may use checked machine arithmetic when that produces the same sequence; mathematical take and drop and exact BigInt length are defined in
A newly manufactured cut carries its full admitted numeric endpoint domain after successful admission and representation checks, under the retention rule of
The length getter and range-returning take and drop operations use the supported numeric endpoint representations of
The length getter first requires a range receiver, then two present bigint, including a property read of a statically known range-family type by a dot, known String key, or object binding pattern. It is the cardinality of the length in this numeric contract; isEmpty retains its
A numeric take or drop call performs these steps in order. The usual evaluation of the receiver expression and argument expressions precedes the call.
Reflect.typeOf reports for the source. Every result below carries that domain, even when an endpoint contributing to an inferred union is replaced. A copy retains both original endpoint values and their bounds, including absence and the sign of infinity. The canonical empty result uses the stored start for both endpoints, with closed start and open end.take, or a copy for drop.take, or the canonical empty result for drop.take, publish a range with the original start and start bound, and the admitted or reused cut as its closed end. For drop, publish a range with the cut as its open start, and the original end and end bound. No result is The operations never mutate the source range or confer reference permissions. Validation-hook side effects retain their ordinary semantics, and later accesses to borrowed locations still perform their existing liveness checks. Only manufactured endpoints are newly admitted; skipped or selected interior items are not visited. A fresh call after a failure observes the same source range. Closed carrier maxima require no successor. The partial results of (0..<10).take(3) and (0..<10).drop(3) are 0..=2 and 2<..<10, respectively; fractional membership follows those bounds.
Reflect.inferSlot(name) returns a fresh slot, a Reflect.matchType(pattern, subject) first obtains patternRecord as ? Reflect.isAssignable supplies what an infer S extends string says.
A pattern is structural: it is a node, and its slots stand where a
This closes the question of whether anything an erased system's infer does is out of reach, and the answer is that it is not. A library author who wants an explicit inverse has one that is ordinary code (
The operation was proposed as optional on the measurement that the extends pattern of is stands to
A origin would change what a program observes about its own type when an unrelated module declared a structurally identical
A record a builder mints carries no [[Origin]], which is the correct answer rather than a missing one: nothing declared it. An implementation is likewise free to hold an origin as an opaque handle, a source and a position, rather than as a reference to the declaration itself, and is encouraged to: retaining the declaration would keep a parse tree alive for as long as the type is interned, which in an
The reason to want this is that partial(User) should not cost an editor its memory of where name came from. The reason not to put a documentation string in the node instead is worth stating, because it is the same argument this specification makes twice already: anything inside a node rides the round trip through
That union is also why [[Origin]] does not reopen what
Because evaluation is deterministic and its results are interned canonical forms, they may be computed once and shipped. An expansion artifact is a serialization of the interning table for the concrete types a module graph's public surface produces, every closed application among them pre-evaluated, keyed by a hash of the module graph that produced it. A consumer verifies the hash and reads the types out of it rather than evaluating them; on a mismatch it evaluates, and determinism is what makes the two agree.
An artifact contains no open application. partial(T) inside an exported generic evaluates against the consumer's T, at the consumer, so the evaluator is not removed by the artifact and cannot be: what the artifact removes is the common partial(T) is, since what
takes the parameter's place is the consumer's type. A name whose type carries one is
omitted from the artifact rather than the artifact being refused — an artifact contains
no open application, which is not the same as a module holding one being unable to have
an artifact — and the omitted names are carried, so a producer says what it left out
rather than a consumer inferring it from absence. A consumer that finds nothing for a
name evaluates, which is always correct.
This is what makes the standalone conformance of
The format is fixed here, because a consumer that can only read one producer's
artifacts is not reading an artifact, it is reading that producer. An artifact is a
TABLE of entries. An entry is the reflection node $ref and whose value is that entry's
index. A table rather than nesting, because a
Entries are
A consumer verifies the hash, and declines anything it does not understand — a
version above the one it knows, a name it cannot resolve, an entry whose
The key is the SHA-256 digest of a canonical encoding of the module graph: for each
module in the graph, reached transitively and sha256: followed by the digest in
lowercase hexadecimal, for the same reason and to the same end: a reader compares keys
as text, so a form left to the producer is a mismatch reported as staleness. Nothing
precedes the entries — no count and no version — since the entries are
self-delimiting and a version belongs to the artifact rather than to the graph it keys.
Replacing the digest later is a change of version, which a reader already
declines.
What is not fixed here is what carries the bytes and how a producer finds a module graph. Neither affects whether two artifacts are mutually readable, which is the whole of what fixing a format is for. What is fixed besides the format is the contract: the key is a hash of the module graph, the contents are canonical forms, a mismatch re-derives, and nothing in it can change what a program means.
A composite is a frozen object with a ===, Map, Set, and Array.prototype.includes compare composites structurally with no change to any collection or operator, because the comparison each already performs finds one object where the contents are one. This clause specifies the
Interning is defined over uint8 -0 by normalization cannot arise between types here, because values of two types are never -0 normalization is one instance.
const a = Composite({ x: 1, y: 4 });
const b = Composite({ y: 4, x: 1 });
a === b; // true, one object
new Map([[a, 'ship']]).get(Composite({ x: 1, y: 4 })); // 'ship'
Composite({ (x: uint8): 1 }) === Composite({ x: 1 }); // false: a uint8 field and a Number field
A composite object is an object contained in the
There are two kinds of composite object. A record composite is an Array.isArray answers typeof answers
Every composite object has a [[RuntimeType]] internal
Being frozen has a consequence at a boundary: a composite whose member needs CONVERTING to cross cannot be narrowed, because the conversion of an object's members happens in place (Composite.<{ n: number }> does not cross to { n: uint32 }; one whose members already hold the target types crosses, no write being attempted. The refusal names the member and the type rather than reporting a failed assignment.
A record composite holds what upstream's composites hold. The tuple kind is this Object.keys lists the elements, and whose iteration %Symbol.iterator%. The two kinds never intern together (Composite([1]) and Composite({ 0: 1 }) are different objects, because a record with the entry
A Composite.<S>(source), a parse target, the record Reflect.typeOf reports — Composite with no arguments, has [[Arguments]] « », states no shape, and is the type of every Composite joins the built-in type names of Composite.<T> is a Composite declares a parameter, so its arguments are types. The rule matters at this clause in particular because a composite type's [[Kind]] is Composite.<{ x: int32; y: int32 }> written anywhere is one delete, a member or element of a value whose Composite.<S>, and the top type states it for every member at once. The Composite.<T>(source); Reflect.typeOf on a composite returns the const binding whose initializer is such a const initialized by a construction does: in each case the type is one the program wrote at the initializer, not one inferred from a literal, so const t = Composite.<[uint8, uint8]>([1, 2]); let a: [].<uint8> = t; is the
Composite Type
The shape recorded for a any type, a type type. An
The subtype rules for composite types are stated in
A composite type is therefore a subtype of the const q: IPoint = p holds for a p of type Composite.<IPoint> with no cast and no write, since a frozen object trivially cannot be modified out of an implementation. Composite types are covariant in their shapes, which the frozenness of every composite makes sound: no store can later violate a wider reading. A composite type over a Composite.<[uint8, uint8]> is a subtype of Composite.<[].<uint8>>, since both are frozen and the array shape is the variable-length
[].<T> and the first mutation through the binding threw where the binding itself was the error. A uint32 members fails on those fields by the no-implicit-conversion rule of
Each heap has an associated Composite Registry: a === across every Symbol.for, and an Composite function created it; only its contents matter.
Under the
Membership in the registry does not keep a
The abstract operation IsComposite takes argument value (an
The operation is defined by searching the registry; an implementation is free to answer it any faster way, such as a bit on the object, that yields the same result.
The abstract operation CanonicalizeCompositeValue takes argument value (an
The rule generalizes the upstream -0, which is its Number instance. A
A decimal type whose metadata fixes the exponents of its values, such as one parameterized by a DecimalContext scale (
Each floating-point type of this specification has a single NaN value, so a NaN canonicalizes to itself and no step names it. Where an implementation nonetheless distinguishes NaN encodings, as the
A program that needs a signed zero, a specific cohort member, or a NaN payload to participate in a key carries it as data, a sign preserveNegativeZero option the upstream FAQ floats is not adopted.
A Composite Entry Record is a
The abstract operation CompositeCanonicalOrder takes argument entries (a
This is the ordinary own-key order except that the non-index String keys sort lexicographically rather than by creation order, which is what makes a
The abstract operation CompositeFieldType takes argument value (an
type (A value of a uint8 uint8 any rather than its structural runtime type, because that type is a mutable object's current
The abstract operation CompositeShape takes arguments kind (
The abstract operation FindOrCreateComposite takes argument entries (a
The search over the registry describes the required observable result and is not an implementation model. Interning must be implemented by hash tables or other means whose cost per creation is, on average, sublinear in the registry's population; a
No step of canonicalization, matching, or interning calls user code. The creation algorithms read the source's values once, getters included, and everything from
The abstract operation FindOrCreateTupleComposite takes argument elements (a
The abstract operation CompositeMatch takes arguments c (a
Both sides are canonicalized before this operation runs, so within one type
The Composite function:
new Composite(...) throws a The source parameter has the declared contract object (Composite is a global this specification introduces, so the contract applies everywhere, and a call whose argument's any type, or one whose type is unknown, is checked by
This function performs the following steps when called:
The kind is decided by Array.isArray on the argument: an array source creates a length - 1, and any other object creates a Composite(template) !== template for any non-composite template. Composite is idempotent as a consequence of interning: Composite(c) on a composite reads c's own contents, an array source for a tuple and enumerable entries for a record, and interns to c itself.
The upstream length, being own but non-enumerable, is dropped, so its Composite([1, 2]) yields the record { 0: 1, 1: 2 }. The tuple kind is therefore a behavioural change to an existing call rather than an addition, recorded in length and a missing %Symbol.iterator%, by making length own and non-enumerable and by supplying iteration without a prototype (
Calling the Composite.<T>(source) for the S that is T's Composite.<S>(source) with an source converts it, at the member's type, into the composite it must be, and the
The abstract operation CompositeFromShape takes arguments S (an
Composite.<T>. It is that algorithm literally: the read page?: uint8 = 0, is written here, before freezing and before interning, so Composite.<CacheKey>({ id: 7 }) and Composite.<CacheKey>({ id: 7, page: 0 }) are the same object; an optional member with no declared default simply stays absent. A default is a construction feature (
The Composite.<T> gives an unadorned Composite(...) initializer the same treatment through the return-type direction of overload resolution (Composite({ id: 7 }) in
That the read Object.assign, object spread, JSON.stringify, and structured cloning among them, reads the enumerable own properties. A composite is a copy, so it reads what a copy reads. The cost is that a member supplied only by a prototype accessor or a non-enumerable own property does not satisfy the
A rejection therefore throws part way through, after the getters preceding it in key order have run, exactly as the untyped creation's Symbol-key rejection does. A trailing tuple position the source does not supply is filled from its declared default, as an absent optional member is, and for the same reason: Composite.<[uint8, uint32 = 10]>([1]) and Composite.<[uint8, uint32 = 10]>([1, 10]) are one object. A position the source does not supply always carries a default, since a tuple's defaults are trailing and the admitted lengths stop only where one begins (Composite.<T> is not idempotent where T declares an optional member with a default, since Composite.<CacheKey>(_c_) fills page onto a composite c that lacks it and so returns a different object. That is the rule working rather than failing, because every creation through Composite.<CacheKey> fills it and they therefore agree with each other; a composite lacking the member satisfies the type by width subtyping and is a different key, which is what it is.
This function performs the following steps when called:
This function performs the following steps when called:
The Composite function has no value instanceof Composite would throw through instanceof against the bare function answers membership in the instanceof against Composite.<T>, a instanceof does.
The function performs the following steps when called:
Composite needs this method because it is a function, not a when Composite: would compare the subject against the function itself, a test nothing sensible ever passes. With it, the bare name means what Composite.<T> already means narrowed: is this value a composite. In an ecosystem whose default matcher instead invokes a callable as a predicate, the method is load-bearing rather than merely useful, since a bare when Composite: would there call Composite(subject), match every object, run the subject's getters, and intern a composite as the side effect of a test. A composite constant used as a pattern remains one pointer comparison, since a constant pattern tests
JSON.stringify requires no change: a
The JSON.parse.<T> of the same text — and only a validated value is interned. Converting first and validating never would let a uint8 member wrap 300 to 44 and intern the wrong document silently. Because the
JSON.parse.<Composite.<T>>(text) therefore validates against T by the walk of === between two parses. This answers the mapping the
A ===, and never across heaps, whose registries are disjoint; a composite is never the same object on two sides of a boundary that copies.
The abstract operation CanBeHeldWeakly takes argument v (an
The one operation gates every weak position, so this change makes each of WeakMap, WeakSet,
The abstract operation GetIterator takes arguments obj (an
A %Symbol.iterator% can reach it by lookup, and being frozen it cannot receive one. The inserted step recognizes the kind directly, so for-of, spread, and array destructuring iterate a Array.prototype.values.call(m), which builds the same
The non-mutating array generics apply to a Array.prototype.map.call(m, f), or through the delete and every store throw as
For feedback in both directions, the deltas between this clause and the upstream Composites
Composite({ (x: uint8): 1 }) and Composite({ x: 1 }) are distinct objects. Upstream has one number type and no such distinction to make.-0 normalization is the Number instance; the decimal cohorts and the per-width float zeros are further instances, the NaN encoding requirement extends upstream's to every width a byte view reaches, and the preserveNegativeZero option is not adopted.Composite on an array source, which upstream reads through the record length dropped.Composite.<T> types with their subtyping and interface satisfaction, and the [[RuntimeType]] a composite carries for reflection are added; they change nothing observable about an untyped creation beyond what canonicalization states.Composite[%Symbol.hasInstance%] is added so that instanceof answers membership rather than throwing, and Composite[%Symbol.customMatcher%] is added so that the pattern Composite tests membership rather than invoking the function as a predicate; the latter applies to the upstream Composite.isComposite, the weak-position refusal, the cross-This clause specifies the match expression, the pattern grammar, the widening of is to take a pattern, and the exhaustiveness and narrowing rules that make a match over a closed type checked complete. A pattern is a test, a destructuring, and a proof at once; the
The match, so return, break, continue, await, and yield inside a block arm mean what they mean in that function; the ; of an expression arm is subject to automatic semicolon insertion as the ; of an expression statement is.
The second form answers with every clause that matched rather than the first, as an Array in clause order. It differs from the first in three ways and in no others - patterns, guards, bindings, per-clause narrowing, arm bodies, and the single evaluation of the subject are all the same:
A default. default and the when _.
all is a ( follows it on the same line, and it remains an ordinary identifier everywhere, including as a binding, a property, and a function name. The [no LineTerminator here] restriction before it is what keeps match on one line followed by all(x) on the next meaning the two statements it means today.
It is a Syntax Error if a default
match, when, and, or, and not are match(x) {} remains a call followed by a block, because match (…) is parsed under a cover, as the type operator's parenthesized operand is (when: never reads as a clause, default is reserved and cannot label a statement, and when followed by a pattern is not a statement sequence. In expression position there is no overlap at all, since a call followed by { is a Syntax Error there.
A Match Cache Record has a [[Reads]] match reads of its subject, so that a property is read at most once and an match sees the same values.
const, perform ! env.default clause, let matched be The second form collects rather than returning:
const, perform ! env.The subject is evaluated once and the cache is shared across clauses, exactly as in the first form, so a property is read at most once however many patterns look at it.
An throws abort the collection: the clauses before it have already evaluated their arm bodies, and those values are lost with the expression. An arm that RETURNS a failure rather than throwing one is what collecting failures looks like, and needs nothing further - the value is the failure and the result is already the collection.
Reaching the end without a match returns an empty Array rather than throwing, which is the whole of why exhaustiveness is not required of this form.
MatchArmEvaluation of AssignmentExpression ; returns ? throw AssignmentExpression ;, it evaluates as the throw statement does. Of a do expression's
An arm's block was given a narrower rule than this while do expressions were outstanding - its value was its final statement's only where that statement was an if with an else or a try, and what it did badly was make an arm ending in a declaration a silent
The identifier _ is the _ and wants its value writes ${_}. A let x is x: let x. Within a ${ is recognized as a punctuator, a lexical goal the pattern context sets as the template contexts do; outside one it lexes as it does today. A MatchLiteralPattern is a type is refined (
+ or -; a true, false, or null; a MatchOrPattern do not have the same or is one binding.Box.<const T>, or under the nested parameter's name, Map.<K: string, V: const T>. It binds what stands in its position, and is meaningful only where a type is being matched rather than written, which is the type-subject pattern the Early Errors of BindingIdentifier : Type form of P tests membership, so P { ... } must agree with P about whether a P matches, and the head's test is also what gives the preceding rule anything to enforce.is, which is an ordinary expression, so without the restriction an expression statement followed by a block would read the block as the pattern's MatchNamePattern ArrayMatchPattern and an P [x] may be read as a juxtaposition or as an index into P, and unlike the overlap above the two readings do not agree. The juxtaposition is taken only where an element is something a _, and the index is taken otherwise. This is the rule the braced and bracketed patterns already carry on their own - a A 'GET' | 'HEAD' is one type pattern whose test is two comparisons. A
The abstract operation PatternMatches takes arguments P (a
MatchOrPattern : MatchOrPattern or MatchAndPattern, thenMatchAndPattern : MatchAndPattern and MatchUnaryPattern, thenMatchUnaryPattern : not MatchUnaryPattern, thenMatchPrimaryPattern : _, return MatchPrimaryPattern : extends TypePattern, return ${ Expression }, then+ or - whose value is a zero, return the result of the sameValueZero comparison of subject and v within their type, MatchNamePattern ( MatchPatternList? ), thenNaN needs no rule of its own: it is a name pattern holding a Number, so float32 NaN does not match a float64 one because their types differ. === performs. The bare-zero step is the one 0 matches both zeros of the position's type at every width, while an explicit +0 or -0 distinguishes them, the
The name form dispatches on what the name turns out to hold, so a range or a regular expression bound to a constant matches as the literal spelling would, by containment and by whole-subject match; the escape to identity in every case is ${name}. A boolean-returning matcher reached without parentheses is a membership test, the form Composite uses (
The abstract operation MatchObjectPattern takes arguments P (a in test, and its value must match its sub-pattern; the rest binding, where present, collects the remaining own enumerable members. It performs the following steps when called:
The cache is why arms agree: a getter named by three patterns runs once, and the presence test and the read are one cached touch, a
The abstract operation MatchArrayPattern takes arguments P (a [N].<T> need not be an
The abstract operation MatchTypePattern takes arguments P (a
The two halves answer different questions and both are needed. Without slots the test is assignability, so when extends string: matches a string and every refinement of one, which is what the infer S extends string is a check afterwards.
The consequence worth stating is that a slotted pattern matches structurally where an unslotted one matches up to subtyping: extends [].<const E> does not match a [5].<uint8>, whose extent is 5 where the pattern's is extends [].<uint8> does match it. Unification up to subtyping is a different and much larger operation, and this specification does not perform it anywhere; a pattern is written against the
The positions a
A float32.<const B: NumberBounds> binds the { ...B } in the constraintsFor an ordinary spread, while Map.<K: type, V: type> binds Type Objects.
The abstract operation MatchIteratorFill takes arguments it (a
The abstract operation MatchStructuralType takes arguments t (a
Three kinds of object type are excluded, and each is excluded because a member-by-member test cannot express it. An OPTIONAL member is satisfied by its absence, where this test requires presence. An INDEX SIGNATURE names no members to walk. A NOMINAL type rejects a value that merely has the right members, so it can never become a structural test; an interface is structural and is not excluded.
Without this, IsOfType reads the subject outside the cache, and two clauses of one match could see different values of one member. That is observable as a WRONG ARM rather than as a slower one: given a getter whose value changes, when { g: 2 } followed by when { g: 1 } reads twice and matches neither, where a single read matches the second.
The abstract operation MatchConstant takes arguments a (an
The abstract operation MatchRange takes arguments subject (an
contains performs on subject (The abstract operation MatchRegExp takes arguments subject (an
pattern constraint requires (The match result's groups: { let year } binds the
The pattern type of a pattern P, PatternType(P), is the number; a name denoting a constant of literal or enumerator type, that type; a binding with an annotation, the annotated type, and without one, string; an extractor whose head and, the or, the union of its alternatives'; an extends pattern, the type type (not, or an interpolation of non-literal type,
A pattern test divides the not Q is the match of Q. They are defined by the form of P, a parenthesized pattern being its
not Q: or R: and R: extends pattern, and an interpolation of non-literal type: Either operation applied to
For an interpolation pattern, it is a
An array pattern's PatternType of
Within a pattern, each sub-pattern's position type is the subject's and, a named member's type in the members of a union position the pattern has not eliminated, an array-pattern element's type from the subject's const B: NumberBounds binds a NumberBounds and const K in a type position binds a type; a or-consistency errors above. It is a or is a sub-pattern at the position of the or, so an alternative that can match nothing there is an error although another alternative can match, and a pattern that can match nothing is dead code wherever it is written, as an unreachable operand is. It is a and with a type or by an interpolation.
Impossible and
The rows this clause adds to when P, v is P narrows as when P does, in the positions the test governs. For ordinary match, the throw arm and a diverging block contributing nothing (match is the
For match all, each clause starts with the original subject type and applies only its own pattern and guard; no earlier clause subtracts from a later clause's subject type, and exhaustiveness is not required. Its throw arm or diverging block contributes no element; a block arm contributes [].<any> assignable to an array of a more specific
A match whose subject is a match to carry a catch-all. The atoms of a boolean, null and undefined, themselves; for a where chain, the atoms of the union that chain
An unguarded clause covers an atom a when: for a non-structural pattern P, not Q is null, and undefined atoms as their literal patterns and types; an object or record-composite atom is and covers an atom only when both operands cover it, and an or when an alternative covers it. A default covers every atom, and a guarded clause covers none.
A pattern is irrefutable when it matches every value: the and both of whose operands are irrefutable; and an or either of whose alternatives is. A clause is a catch-all when it is a default, or is unguarded with an irrefutable pattern.
A match is exhaustive when it has a catch-all clause, when the atoms of its subject's match (1) { when 1: ... }, whose literal type the literal pattern names. It is a match whose subject's match all form (match all need match, since its value is the match all is still a narrowing form, and it is a default whose preceding clauses cover every atom of a subject with atoms is that error's instance, so a match and a defaulted one cannot silently coexist. A default after an unguarded clause that matches every value the subject can hold, as when let x does, is another instance: the remaining type is empty, so the default can match nothing, and a match that already cannot fall through gains nothing from one.
The last two rows are derived forms, and they are derived for this check alone. A discriminating where chain Reflect.typeOf reports the dependent record type and the intersection, CanonicalizeType flattens an intersection without distributing it (A covers an atom A & C.
Requiring exhaustiveness of every match is what makes the match whose subject holds a value of its match needs a catch-all" and "this match must not have one" holds of it, since a closed subject default it does not need and everything else requires one. Naming the atoms is also what lets an implementation report which pattern is missing rather than only that one is.
Decoration cannot falsify a coverage claim, which is worth stating because a decorator may replace what it decorates and a reader has no reason to assume the interaction was considered. Three rules of the decorators extension carry it, one per way an atom
An atom let, and const contexts admit no return match covers are the ones the declaration lists; a decorator that mutates an switch or match subject that a narrowing has reduced to one enumerator literal is still a subject of the
A class Reflect.Class context does admit a singleton returns class extends T, but the declared return type is T and a sealed base rather than one of its cases, a decorator in another module cannot return a subclass at all, since extending it outside its module is what sealed forbids (
A Reflect.ClassField replaces the () => T and a setter at T, so a discriminant declared with a
What is outside the claim is what is outside every claim here: a decorator running arbitrary code may write a value that has drifted out of its type, and the backstop is the one match.
The switch exhaustiveness of switch changes; a switch remains able to fall through to no case, which is the difference between a statement that may do nothing and an expression that must produce a value. What patterns add is the members a pattern can provably blanket, which is what makes a discriminated union of object types and the T | null of every tryParse checkable with no default, and what a lone object or tuple type deliberately does not get, its atoms being where chain, because a subset pattern is the point of matching on one. Coverage is syntactic over declared types; the checker evaluates no guard and no predicate.
The right operand of is (is keeps its parse and its meaning. v is P evaluates v, a fresh is to occur where no position is governed by its truth, since the bindings would then exist nowhere.
This extension adds the well-known symbol %Symbol.customMatcher% (Symbol.customMatcher), the method a name and an extractor pattern consult. The null, or to a Boolean for the parenthesis-less membership form; its declared signature is the whole contract, overloads select on the subject's
A statically established custom matcher must be callable. Finding the symbol-named property alone does not satisfy the method requirement. It is a
This clause, like switch chapter, and match arm's type by it - "a throw arm and a diverging block contributing nothing" - without the notion having been defined.
A statement diverges when no
return, a throw, and a break or continue targeting an enclosing statement diverge.never has no normal successor. Apply callee and argument effects in order and preserve its possible exceptional never. Compose this absence of never has no normal resumption. Preserve possible rejection and the paths through handlers and finalizers. An unknown assimilation if statement diverges when it has an else and both branches diverge.switch statement diverges when it is default - and every clause diverges.while (true) and for (;;) diverge when no break targets them.do statement diverges when its body does and no break targets it, whatever its condition, since the body runs before the condition is read.try statement diverges when its finally block diverges; otherwise, when its try block diverges and, where it has one, its catch block diverges too. A try block that diverges says nothing on its own, since the break targets the label.Two consequences follow, both of which the switch clause whose body diverges needs no break and is not a fallthrough; and a function whose body diverges satisfies a non-return.
Apart from an established immediate or awaited never result, the listed syntactic forms bound the judgment: while (cond) does not diverge however plainly cond is while (true) and for (;;) as forms rather than describing a condition.
It is a for-of, for await, array spreads, array binding and assignment patterns, and yield*, using each consumer's
A nullish
The negative iteration proof also follows effective callable contracts through the stages reached by the consumer. A known non-object entry-hook result fails next or known non-object step result also proves failure. An empty array binding or assignment pattern acquires the next; an element, elision or rest requests a step. The result Object need not statically declare value or boolean done: an empty Object can satisfy the base-language step-result requirement.
For an actual asynchronous next. Do not await the entry-hook result. For
The bounded assimilation judgment of next result, including initial async yield* forwarding, and to its return result only where the closing judgment below proves that close is reached and its failure is not suppressed. During async-from-sync stepping, first require an Object raw value contract, even when done is value is not separately awaited by for await. Combine argument-boundary, assimilation and required Object-result failures within each selected alternative, and reject only when every selected then and intrinsic Promise paths retain their existing treatment. Arbitrary later delegation resume requests are not made unconditional errors by this initial-step proof. No hook runs during checking, and promise scheduling, cleanup precedence and reference liveness are unchanged.
return fails the method stage; a callable close with a known non-Object result fails the result stage. A nullish return is a valid no-op. An empty array binding or assignment pattern acquires and closes with break also supplies a bounded closing edge. So does an array binding or assignment pattern with no rest element whose every requested step definitely yields, since it ends with the let binding with no annotation. Possible exhaustion, unresolved control flow, or an incoming
For
The negative iteration judgment includes the yield* forwarding supplies «
This clause is owed to the iteration protocols the core already defines, not to any extension. for-of asks whether [Symbol.iterator] is callable and never asks what a value declared; the Iterator.<T> throughout and this specification has named no such type, so what a helper returns and what a hand-written
The iteration result type IteratorResult.<T, R> is the value of T and done of the literal type value of done of the literal type done, so a match over an iteration result binds value at T in one arm and at
Structurally, and therefore not by identity: a generator object, an Array, and a hand-written object with the right members all satisfy Iterable.<T>, and none of them was constructed as anything named Iterable. An implementation that also keeps a nominal type of that name — for the carrier a helper chain returns, say — must not let the two share a name, or one annotation means a
The protocols are interface types, satisfied structurally through their
Iterator.<T, R, N> declares next, taking an optional argument of N and returning IteratorResult.<T, R>, and optionally return and throw.Iterable.<T> declares [Symbol.iterator], returning Iterator.<T>.IterableIterator.<T, R, N> inherits both and its self-iteration hook returns Iterator.<T, R, N>, preserving all three arguments.AsyncIterator.<T, R, N>, AsyncIterable.<T>, and AsyncIterableIterator.<T, R, N> are the same three with [Symbol.asyncIterator] in place of [Symbol.iterator] and protocol methods returning promises of the iteration result; the self-iteration hook of AsyncIterableIterator preserves all three arguments.Iterator is also a built-in class, the one on which the IterableIterator. AsyncIterator is the same for the asynchronous protocol. Both join the built-in type names of
A bare argument is the first: Iterator.<uint8> is Iterator.<uint8, void, void>, and likewise for the other five. This is the shorthand
R and N default to
There are no per-collection iteration types. TypeScript names ArrayIterator, MapIterator, and SetIterator because it needs somewhere to carry a narrower return type; the shorthand carries it here, so the family stays at six interfaces and two classes. The absence is a decision rather than an omission.
The element parameter is invariant, as every generic in this specification is (Iterable.<Circle> is not an Iterable.<Shape> for a Circle that extends Shape. Covariance would be sound for these types and for no container: an
Where an operation's receiver is established to be a primitive String and the operation reaches a write to its fixed own length property, it is a
An unknown receiver, key or reached store does not establish failure. In particular, a logical assignment whose store is skipped is not refused by this rule. The rule does not classify every primitive member, structural lookalike, boxed object or configurable property as immutable; an inherited setter on another member can be callable. No getter is evaluated to establish a descriptor. Accepted code retains the ordinary runtime property operations and reference-liveness checks.
The abstract operation IteratorCompletionValue takes argument R (a
Substitution into the deferred operation substitutes its operand and applies IteratorCompletionValue again. The existing identity and reflection rules for deferred Type undefined completion remains undefined.
The next, optional return and optional throw methods share the next takes optional N, return takes optional R, and throw takes an optional gradual value. Normalizing the completed result does not widen either R or N at an input boundary. The nominal generator type retains its original arguments, and its body's written return obligation is unchanged. In particular this operation does not admit a non-
A nominal generator delegation's terminal IterableIterator and AsyncIterableIterator preserves T, R and N. Ordinary
The intrinsic Range and map functions declare one type parameter, U. Applying explicit type arguments uses the ordinary named, positional and fixed-extent spread rules. U has no default for a stored application. Resolve the actual callee by ordinary lookup, and bind its type arguments once before evaluating value arguments. A nongeneric
A stored intrinsic mapper name and length properties do not determine the declaration.
The completed callable signature takes callback: (value: any, index: uint64) => U and returns the helper-bearing U, completion undefined and optional U. An internal rename used to avoid
For the selected intrinsic U in the existing mapping closure. After Call of the mapper completes normally and before U by the ordinary value-boundary rules, including their permitted conversions. If admission is abrupt, perform the same
For U equal to
An explicit intrinsic Range mapping acquires its ordinary fresh cursor, performs ordinary lookup of that cursor's map, and forwards the completed U as an explicit positional type argument to the selected function, with the original callback and materialized positional argument U protocol by the existing receiving rules. This boundary neither executes
These contracts apply to the
The result domain of a flatMap callback next method is used when @@iterator is nullish; it excludes all primitive results, including Strings. It is a
Reduction follows
These
When inferring a generator's completion component, including for a generator do-expression, preserve an established undefined
For structural yield* operands, derive separate yielded and terminal contributions from the selected
This clause is owed to the generator forms the core already parses, not to the extension that follows it. Generator.<Y, R, N> throughout; the type itself has not been stated. It is stated here because
Generator names the built-in generic whose instances are the objects a generator function returns, and AsyncGenerator the same for an async generator. Generator.<Y, R, N> is the AsyncGenerator.<Y, R, N> for the second. Y is the type of the values the generator yields, R the type of the value it returns, and N the type of the value a caller sends to next. Both are generic classes, so each is invariant in its arguments by the rule of Generator.<uint8, void, void> is a subtype of no other instantiation.
A generator's return annotation is read as follows, which is the Generator instantiation, and an AsyncGenerator instantiation for an async generator, it is the declared type as written. Where it Generator.<T, void, void>, or AsyncGenerator.<T, void, void>, so that function* f(): int32 declares a generator of int32. It is a Generator instantiation, or for a AsyncGenerator instantiation.
The * is N, and it is a yield* is
A
When the running async generator has a resolved declared, contextual or yield* after a non-completed step. It does not add assimilation to untyped delegation or to a real async for await step's value.
For a concrete established done to the effective value. It is a then cannot accept the resolving callbacks under done or value contract, viable overload or intrinsic Promise
A generator satisfies the corresponding Iterable or AsyncIterable element view. Its more precise structural
Reading a bare annotation as the yield type rather than as the whole generator type is the void in almost every generator anyone writes, and requiring Generator.<int32, void, void> at each of them would make the common case the verbose one. The full form remains available and is what a generator with a meaningful return or a two-way protocol writes.
That yield* has the delegated generator's yield* drives the operand to completion and evaluates to its return value.
The abstract operation GeneratorCompletionValue takes argument R (a
undefined.The read signatures of next, return and throw on Generator.<Y, R, N> each return the union of the result done value of Y, and the done value of GeneratorCompletionValue(R). The corresponding AsyncGenerator methods return a Promise of that union. All three methods are present; their optional next and throw retain those inputs; async return uses the raw input contract in return cannot yield during cleanup.
The self-iteration hook returns the same nominal generator instantiation. Dot access, an established computed String key, optional access, object-pattern member reads and the actual well-known self-iteration Symbol use this read contract. Ordinary lookup and receiver binding remain in force; extraction does not bind the receiver. A shadowed Symbol, unknown key or opaque receiver supplies no intrinsic identity. These read judgments do not make the members readonly or execute a getter.
Derive structural _R_ | undefined, retaining the same operation result. This declaration rule does not change general signature identity or nominal invariance. In particular a body with R of string supplies the result view needed by Iterator.<Y, string | undefined, N>, but not the narrower Iterator.<Y, string, N>. Ordinary
A nominal generator delegation uses GeneratorCompletionValue(R) as its terminal
This operation does not widen a written IteratorResult, admit
A generator retains its resolved declared, contextual or
Admission occurs before this request changes the generator state, resumes a body or performs its cleanup or delegate lookup. A validation failure precedes admission effects. An admission failure propagates without this request's state transition; effects of ordinary admission, including permitted conversions and property access, are not rolled back. Since those effects can re-enter the generator, after successful admission repeat the ordinary validation and read the current state before performing the native state-dependent continuation. Do not resume using the pre-admission state or add a lock that forbids otherwise valid reentrancy.
The admitted
For
A non-completed delegate return result retains ordinary yield enforcement and suspension. Subsequent normal resumption follows the native delegation algorithm and can complete the delegation normally; a later return statement is a new body obligation. Neither a permanent closing flag nor payload equality transfers an external permission to that statement. No method, getter or callback is executed to prove a static judgment. Established request-input incompatibility retains the ordinary
This clause adds no runtime result wrapper, readonly member, reference authority or extension of a reference lifetime. Ordinary receiving, suspension and reference-liveness checks remain in force. It specifies
An async generator retains its resolved declared, contextual or
The optional raw input in the AsyncGenerator return member's read signature is the canonical union of direct R (the type of Promise.<any, any>, and a structural object with a required callable then member accepting the ordinary resolver and rejector arguments and having a gradual result. For
Preserve native receiver and brand validation, enqueue, state transitions and scheduling. Do not await or admit the raw operand before enqueue. In
In
Associate each admitted external completion with its owning generator as in
For async delegated yield receiving an admitted external completion, preserve native lookup and assimilation order. If the delegate has no return method, perform the native additional
These rules add no wrapper, public result property, global type operator or reference authority. Every native suspension retains the existing reference-liveness rules; successful admission cannot extend a lifetime or revive an
A bounded static proof may establish a later normal resume forwarded by yield*. Its initial supported case is a direct source-local generator with no parameters or decorators, whose first statement delegates to a direct source-local generator with no parameters or decorators and an immediate literal yield. The enclosing generator is created in a fresh top-level immutable local and directly resumed twice in source order. The selected
At that established forwarding boundary, it is a any cannot establish this mismatch.
An uncalled generator body alone does not establish future intrinsic identity and is not refused by this proof. Replacing the protocol in an earlier script also prevents the proof. No generator, getter or callback is executed while checking. This rule does not extend to arbitrary later requests, asynchronous delegation, or throw and return forwarding. Runtime conversions, cleanup, scheduling and reference-liveness checks at suspension and resumption remain in force.
A using declaration admits the same type annotations as a const declaration. Its initializer crosses the declared type boundary as an ordinary immutable binding does. The resource is still acquired and disposed by the base language's explicit resource-management semantics: Symbol.dispose method.
When an explicit annotation is present, use its declared post-boundary type for the acquisition and disposal-call judgments below, including explicit
It is a Symbol.dispose member whose type is known to be non-callable proves the same failure. Member lookup includes inherited members with the base's type arguments substituted, and a getter contributes its declared result type without executing the getter. For example, using r: { [Symbol.dispose]: uint8 } = value is a
It is also a
This is a check of known impossibility, not a requirement that every annotation explicitly declare a disposal member. An open object type,
For await using, a corresponding impossibility judgment must consider asynchronous acquisition, including Symbol.asyncDispose and the base language's
A do expression is a block that produces a value: its statements are evaluated and its value is the completion value, the value of the last statement that completed normally. This clause specifies the
The do, as a return, break, and continue inside it mean what they mean in the surrounding code; the [no LineTerminator here] is what keeps async a async on one line and do * on the next must remain an identifier reference followed by something else, which is the restriction the productions above already carry before function.
It is a Syntax Error for a do there begins a do-while. In statement position a program writes a
do is a match did (do * and async do * forms need no statement-position rule of their own, since * cannot begin a do-while body; they are refused there with the first form anyway, because a do * evaluated as a statement discards the generator it built, and a reader should not have to know which form the collision applies to.
The following apply to the first form only. A do * has no completion value, its body being a generator body whose completion is discarded, so nothing here restricts it: a do * may end in a loop or a declaration, and the
if with an else one of whose branches ends in a loop is a Syntax Error too.if statement with no else.break or continue to appear within a return to appear within a Each of the first two is about a completion value a reader would not predict rather than about a do { 'before'; let x = 'after'; } would be if with no else is its consequent's value or
The abstract operation CompletionTypeOf takes argument list (a
A completion summary records each possible completion's kind, target and undefined and from an unknown type. It is computed structurally, without executing expressions. Sequential composition evaluates a following statement only on normal paths; an empty resulting value inherits the preceding value as break retains the carried value, and the matching labelled statement converts that break into a
Blocks compose their reachable statements in order. Conditional branches are joined, applying the base language's do still apply.
any.undefined to members.The do { done: { 's'; break done; 1; } }, the string supplies the value; the numeric tail is unreachable. Reachability without the carried value would not suffice to type the expression.
A do all of whose paths diverge has the empty union, which is never (const port: uint16 = do { throw new ConfigError('no port'); }; is accepted. This is the first construct in this specification where ordinary code produces never rather than a type computation producing it, and it is the answer a program wants: the binding is unreachable, so its declared type constrains nothing.
The switch step is the one place this operation is not the obvious rule, and it is the switch takes no switch that maps a value to a type: "because switch used to map a value to a type do. A switch that is not const u: uint8 = do { switch (s) { case 'a': 1; } }; a
Exhaustiveness here is the switch's, which this specification reserves to match reads (switch over a boolean covering true and false is therefore not do ending in one has match is checked complete. The asymmetry is between the two constructs rather than introduced here: match was given a wider analysis when it was added, and widening switch to meet it is that clause's decision to make rather than this one's. A do that wants the narrower answer writes the match.
An empty do {} is void 0, and not do {} produces one.
A
Read each producing expression in its own lexical scope and incoming flow environment, with that
let n: uint8 = do { done: { 1; break done; "unreachable"; } };
let m: uint8 = do {
done: { try { "replaced"; break done; } finally { 3; break done; } }
};
Without this the form would be do would need the annotation the surrounding declaration already carries, so const level: uint8 = do { if (verbose) 3; else 0; }; would hold Numbers. It is the same propagation a match gives its arms, reaching the same kind of position, the one that carries the value out.
Narrowing needs no rule of its own. A test inside a do narrows inside it by
A do * evaluates to a generator object, as a call of a generator function does, and its body is a generator body. The do * { } is Generator.<Y, R, N> (async form AsyncGenerator.<Y, R, N>, where the three are found rather than declared, there being no annotation
yield*, and of the Y of each yield* operand's generator type.return operands, or A do * is the do's tail does.
Computing Y and R from the body makes distinct yielded-result and completion-result queries under
These equal component types are two answers owned by one expression. An implementation computing them exhausts a distinct-answer allowance of one before evaluating the Script. With sufficient resources the Script evaluates to "ok". Reusing either valid completed component adds no distinct answer; the carrier adds none.
const values = do * { yield (1 := uint8); return (2 := uint8); };
"ok";
A do * is a function boundary and a do is not, which is where the two forms part company. this, arguments, and new.target inside a do * are the enclosing function's, as they are in an arrow: the function* () { … }.call(this), and binding this any other way would reintroduce it. But the body is a generator body, so yield belongs to the do * rather than to the enclosing function, return sets the generator's return value rather than returning from the enclosing function, and an unlabelled break or continue cannot leave the body.
That return means one thing in do and another in do * is the sharpest edge in this clause and is worth stating rather than deriving. In a do it returns from the enclosing function, which is what the upstream getUserId depends on; in a do * it completes the generator. Nothing else in the language turns on a single token that way, and a reader who has just read the first form will assume the second.
The Promise.all takes an do * yielding the wrong thing is refused at the yield; through function* () { … }.call(this) the same relation holds but is read through a call whose this is supplied separately.
A match arm's
No program changes meaning. The arm rule accepted a strict subset of this one - a block ending in an if with an else, a try, or a switch, which the arm rule made match's value.
An arm's block is the first form and never do *: an arm produces the arm's value, and a do * there would produce a generator.
The Block family of DoBlock and DoGeneratorBlock, which are two contexts rather than one because what the block is differs, a block in one case and a generator body in the other, as ForBlock and ForOfBlock are two. They are the first block contexts to admit a return DoBlock decorator may return a value of the do expression's type, and a DoGeneratorBlock decorator a Generator.<Y, R, N>, replacing what the expression produced. The exclusion of blocks from
With the per-entry rule that family already carries - a block decorator runs on every entry rather than once where the declaration is evaluated - a do * decorator receives is an
A constant expression is a block whose value belongs to its site: it is evaluated at most once for a given ConstantExpression in a given
constant is a constant alone on a line followed by a block would be an ExpressionStatement and a Block under automatic semicolon insertion.
This is the rule ECMA-262's
The guarantee is IDENTITY, not speed. Two closures produced by two calls to one factory share the value, and a site inside a loop yields the same value on every iteration, because the key is the site and not any evaluation reaching it. A binding hoisted to enclosing scope cannot do that: it is re-created whenever the declaration containing it is evaluated again.
A
| Value | Meaning | |
|---|---|---|
| [[ConstantMap]] | a |
The value each ConstantExpression evaluated in this |
The Block must be CLOSED: it reads nothing from outside itself. Its value is then a property of the
Without this rule, Math.max is ambient but cannot observe how many times it ran. Being closed subsumes the non-determinism a
It refuses a Block that calls a helper declared outside it. Admitting a reference to an immutable outer binding would relax the rule soundly and is deliberately not specified here.
An
The value belongs to a
x |> f(%) evaluates its left operand, binds it to the topic, and evaluates its right operand with that binding in scope. This clause specifies the
|> is left-associative, binds looser than ?? and tighter than the conditional operator. The upstream |> binds looser than ... 0..<10 |> sum(%) pipes the range, which is what it reads as; the other order would make it 0..<(10 |> sum(%)), which nothing writing it means. Otherwise the placement is the a |> f(%) |> g(%) is (a |> f(%)) |> g(%), and c ? a |> f(%) : b needs no parentheses. The token % is the remainder punctuator, and the two are told apart by position — a
The upstream %, which is the
An expression language with operator overloading has fewer free tokens than one without, which is a fact about this # is a private name, ? is optional chaining and the optional marker of @ is a decorator in about twenty positions, _ is the pattern % and ^ are operators a class or a primitive block may declare. The _ and it cannot be: one glyph meaning "anything at all" in a pattern and "this exact value" in a pipeline is a worse collision than the remainder one.
% is the remainder punctuator and a % where an operand is expected is the ordinary Syntax Error it is today.The second error is what keeps the tacit form from arriving by accident. x |> f() is refused rather than read as f(x), so a program that means the call writes x |> f(%) and a program that meant something else is told.
"That the operand binds" excludes a a |> f(b |> g(%)) the only a |> f(%, b |> g(%)) or not at all.
PipelineExpression : PipelineExpression |> ShortCircuitExpression:
TopicReference : % returns the value bound in the nearest enclosing
The left operand is evaluated once however many times the expensive() |> [%, %] rests on. An immutable binding in its own environment also gives the shadowing rule for free: a nested pipeline creates a second
The
A tested pipeline propagates its body's result environments and, where proved, the
A
It is a ref (o.a |> f(ref %) would bind a reference to a temporary and a write through it would reach nothing the caller can observe. It is refused rather than admitted and useless, because the expression reads as though the pipe were transparent to the place it came from.
Three consequences follow from the x |> f.<uint8>(%). And the
A
The tacit form the x |> f, calling f with the value and no
What survives is narrower and is enough. A tacit form cannot express the steps that are not calls — % + 1, [%, %], await % — and await is not a function, so no signature rescues it. A pipeline whose steps are sometimes tacit and sometimes not would be two forms sharing an operator, and the second
is, typeof, instanceof, in, and a discriminant. shape |> (% is Circle ? %.radius : 0) is the case, and it is the rule a pipeline cannot do without — a substitution has no identity to narrow, which is a second reason the
|> may not be declared as an operator. Resolution reads the left operand's type and searches it for a signature, and a pipeline has no operation for a signature to describe — it binds one operand and evaluates the other rather than computing a value from both. The same reasoning excludes any future operator that binds rather than computes.
The threading extension gives a program more than one === answers on any
An ===, which is correct rather than a defect: identity is what interning within one heap establishes, and there is no third table above them in which to establish more. A program that needs the same type on both sides declares it on both sides, and every structural rule of this specification then agrees about it, because structure is what those rules read.
Two consequences follow for the types whose identity rests on something an
A Atomics operations, whose compareExchange compares with shared modifier marks the state a program expects to cross threads, which is what lets the boundary be reasoned about statically rather than discovered.
The shared marks what crosses and that mutable shared state is reached through an atomic or a lock. Retrofitting a Send/Sync proof onto a shared mutable heap without a borrow checker would make this a different language, and the memory safety the core guarantees is unconditional either way.
That tearing is possible is worth stating next to the shared SoA.<T> allocates each
The clause before this one settles what a shared heap means for the identity of a type. This one specifies the surface that puts a second shared modifier Atomics operations over
The whole of it rests on one decision the core has already made elsewhere and this clause only carries through: memory safety is unconditional and race freedom is not. A program that races produces a stale or surprising value, never a corrupted heap and never a value that is not of its declared type. Everything here that constrains an implementation constrains it in service of that line, and everything here that constrains a program is a discipline the program may keep or break without the engine's guarantees depending on which it does.
The clauses that follow are shared its meaning in the type system and its placement, which AbortSignal observed at points the operations that can observe it each name.
A thread is an
Each thread holds what an
A thread runs to completion and then ends.
Whether a thread may receive
A thread costs a native stack and the per-thread allocator state an
A promise is an
A
Settling a promise from another resolve visible to the code that awaits it.
The handle returned by callThread is not an exception to this rule. It settles on the callThread, because that is where its reactions were attached — the same reason every other promise behaves the way it does.
The alternative rule — run a reaction on whichever await. An await resumption IS a promise reaction, so under that rule the continuation of an async function runs wherever the awaited promise happened to settle. await lock.asyncHold() would resume the waiter on the await that caused it; a ThreadLocal read would not survive an await; and placement would become a race, since a handler attached before a settle would run on one
It would also separate await from .then, which this language specifies in terms of each other. A threading model in which rewriting one into the other changes which
A target is
A cleanup callback registered with a
callThread calls a function on a new
This method performs the following steps when called:
The declared type of this method is (options?: { signal?: AbortSignal }, ...args) => Promise.<Return, any>, where Return is the declared return type of the function it is called on. The rejection type is any because a function may throw anything.
The abstract operation ClassifyThreadArguments takes arguments func (a
The bag is recognized by the brand of the value it carries and not by the signal is an actual AbortSignal is an options bag in every program that is not deliberately constructed to look like one. An empty object is admitted so that a bag may be passed explicitly with nothing in it.
The declared signature overrides the brand, which is the step no untyped resolution can perform. A function that declares a first parameter admitting the object receives it as an argument, whatever it carries, so a program that wants to forward a signal-carrying object as data annotates the parameter and is answered exactly. Argument count plays no part: rest parameters and overloads make a declared length useless for this.
Admission is membership and not assignability. The operation holds a value rather than a type, so
A parameter type that is not resolvable at this point — the unbound type parameter of a generic function, which names a type the call has not yet bound — does not admit. Both answers are defensible and neither is more correct, but refusing keeps the bag reachable on a generic function, and a program that wants the other reading has the concrete annotation available to say so.
One case is left over, and it is the intersection of three opt-outs: an untyped variadic function whose one intended argument is an object carrying a real AbortSignal under the key signal sees it taken as the bag. Annotating the parameter resolves it, and so does wrapping the value.
This operation counts own callThread reads the option with callThread's validation step reachable: an object with no own keys is an explicit empty bag, so one created with an inherited signal is classified as a bag and the inherited value is then read. An inherited AbortSignal is used, and an inherited value that is not one throws rather than being silently ignored. Were both steps to consult only own properties, no object could reach that step with a bad signal and the check would be unreachable.
The abstract operation CreateThread takes arguments func (a
The capability is created on the calling
Adopting a await, so a await would never run and the handle would resolve with a promise rather than with a result. The reactions on inner are created on thread, so they are thread's reactions and run on it, and thread is therefore alive and running its own continuation until the adoption completes. That is the whole of what "the
There is no separate step draining the
The signal option is an AbortSignal, the mechanism the platform already has. A signal already aborted when callThread is called rejects the handle with the signal's abort reason and starts no
Otherwise the await, and in a wait — Atomics.wait and Atomics.waitAsync (
Observing it throws the signal's abort reason from whatever operation was pending. That is an ordinary finally blocks run, using declarations dispose, a lock held through hold is released, and a reason that reaches the top of the
Those two sentences have an order, and it decides what a finally blocks and catch clauses of that unwinding. So an abort abandons a
There is no checkpoint at a loop back-edge. A computation with no suspension point is cancelled cooperatively, by reading the signal's aborted state, which crosses threads because the signal is an object of the shared heap and because an abort on one
Cancellation interrupts; it does not destroy. A Thread.stop and Thread.Abort were both removed from the languages that had them, and why the surviving
shared before a type produces the PrimaryType : shared PrimaryType, given with the array and tuple types (
It is a SoA.<T> of the structure-of-arrays extension are what the modifier admits. It is a ref
An open operand retains this formation obligation. Substitution that establishes a closed operand discharges the same value-type, non-reference and non-nested-shared requirements, including through aliases and nested type positions. A resolved violation is a
An object needs no modifier and is not given one. There is one heap, so a shared would claim nothing of an object type that is not already true of every object type, and would falsely suggest that a marked collection is a concurrent one when what the
A shared T, which is how a value is
The unwrapping is one-sided on purpose. Two shared T and T for no T, which is what keeps the distinction load-bearing exactly where it has to be: a writable member is invariant ({ x: shared uint32 } is not viewable as { x: uint32 }, and the regime a
let a: shared uint32 = 5 while accepting let a: uint32 = 5, since a Number is not a member of uint32 but converts to one, and no rule of this clause asks for those two to differ.
Storage of a shared T and T have the same size, alignment, and
A shared SoA.<T> allocates each of its columns its own
Unmarked storage of a
Narrowing a binding or a
For storage that is not
For storage that is
Declared types hold in both regimes, so every elision
This is the position a C or C++ compiler takes on non-atomic storage, transplanted into a language where the failure it permits is bounded. There, a compiler that assumes no concurrent mutation and is wrong may produce anything at all. Here the memory safety the core guarantees is unconditional, so the same assumption, when wrong, costs a value that is out of date and nothing else. That bound is what makes the assumption affordable, and it is why the modifier can be a claim the program makes rather than one the implementation must verify.
This clause adds no
Let a shared typed slot be storage of a
Atomics operation on a shared typed slot (A boolean has [[NoTear]]
Tearing is bounded in a way it is not in an unmanaged language. A torn read of an aggregate composes bytes that were each written by some x is from one write and whose y is from another — and never the type of either. A program that needs the whole aggregate at once either takes a Lock (
Accesses to storage that is not shared storage and no Atomics has the
Two threads are
The Atomics operations of the uint32 may operate on it atomically without first arranging for it to
An Atomics operation whose first argument position is written ref binding receives a ref parameter does (
A built-in function is therefore able to declare a parameter position as taking a reference, and each of the operations of this clause declares its first. This is a change to the references extension, which until now needed no such thing: a built-in had no ref parameter, so a reference argument decayed to its referent at every built-in boundary without exception. The exception is unavoidable here, since operating on a copy of the binding is not the operation the program asked for. Nothing else changes, and in particular the reflective call paths remain decay channels: f.call(*null*, ref x)
The borrow is validated where the ref argument is evaluated, by ref argument reaches every callee. A reference that arrives at one of these operations has already passed it, so these operations do not apply it a second time.
The abstract operation ValidateAtomicTarget takes arguments args (a Atomics operation admits to one description of a place in memory.
An Atomic Target Record has fields [[Block]] (a
The shared modifier is not consulted. A binding carrying it and one not carrying it are equally valid targets, and the restrictions this operation enforces are restrictions of type: an any-
Requiring the modifier would buy no invariant. Unmarked storage is not unreachable from another ref uint32 would have to know whether its caller's storage carried a marker, and expressing that would mean a sharedness dimension through the reference types for no gain. What the modifier changes is cost, not legality (
Atomics.load, Atomics.store, Atomics.exchange, and Atomics.compareExchange accept any target Atomics.add and Atomics.sub accept the Atomics.and, Atomics.or, Atomics.xor, Atomics.wait, Atomics.waitAsync, and Atomics.notify are
A value stored through any of these operations passes the
Atomics.add and Atomics.sub on a target whose [[Type]] is a floating-point type perform the read-modify-write as a sequentially consistent compare-exchange loop: read the current value, compute the sum, and attempt to replace the value read with the sum, repeating until the
The result is therefore atomic but not deterministic across interleavings, floating-point addition not being associative. A program that needs a reproducible sum accumulates per-slice partials and combines them in a fixed order, which is what
Atomics.compareExchange converts the expected value to the target's [[Type]] and then compares it with the value read using
Strict equality would not terminate:
Atomics.wait, Atomics.waitAsync, and Atomics.notify on a
Atomics.wait throws a Atomics.waitAsync run on the
Atomics
Where the Lock.prototype.hold, Lock.prototype.acquire, and Condition.prototype.wait throw a Atomics.wait does. They throw rather than quietly becoming their async counterparts: an operation returning T on one Promise.<T> on another would have a return type that depends on which
A Lock has internal slots [[Owner]], an
A Lock is not reentrant. Reentrant monitors buy tolerance of self-nesting at the price of
It is therefore a hold or acquire. Those forms block, so the acquisition cannot make progress: the Condition.prototype.wait must check it anyway. A specification cannot leave the outcome unstated, and the platform's answer for an operation that can never proceed is to throw — the same choice made when Atomics.wait on a
asyncHold while holding is not a deadlock and is permitted. The acquisition queues, and the holder may release from a later job before anything awaits the pending promise.
It acquires the Lock, calls callback with no arguments, releases the Lock, and returns what callback returned. If callback returns an finally.
The closure does not escape the call, so an implementation may allocate nothing for it and may inline the inline, for the reason a ref callback may not (
It acquires the Lock and returns a lock guard, an object with a [Symbol.dispose] method that releases it. using guard = lock.acquire() therefore holds the Lock for the enclosing block, which reads better than hold where the return or spans a try.
Disposing a guard twice is a
A guard is where the type system can do what a callback cannot. A guard type the checker requires to be consumed by a using declaration, or disposed on every
It returns a promise for a release function, fulfilled when the Lock has been acquired for the calling await runs on the
The release function may be called once, by the
Releasing a Lock
Which waiter obtains a released Lock is
A Condition has an internal
It is a
An implementation may wake a waiter that was not notified. A program therefore tests its condition in a loop rather than assuming that returning from wait means the condition holds — which is the discipline every condition variable requires, and which this clause states rather than leaving to be discovered.
It returns a promise fulfilled when the waiter has been notified and lock reacquired, so the code after the await holds the Lock exactly as the code after wait does. Its reactions run on the
notify wakes one waiter, notifyAll wakes every waiter. Which one notify wakes is
A wait and an asyncWait are cancellation checkpoints (
ThreadLocal.<T> is a generic intrinsic, in the manner of SoA.<T> and Composite.<T>. An instance holds storage per value reads the storage of the
An
A ThreadLocal is coherent here because a reaction runs on the await and one after it could answer differently in the same function, and the primitive would be a trap rather than a tool. The two rules stand or fall together, which is why one specification carries both.
None of these is
The Thread namespace object carries two operations over a range of
Both operations cut begin through end into slices: disjoint contiguous sub-ranges covering the whole range, numbered from zero in ascending order of the elements they contain. How many, and where the cuts fall, is
Which
The constraint is what gives parallelReduce a reproducible answer over floating-point values, and it is chosen at the level where it costs least. Fixing the partition in this specification would deliver reproducibility across implementations too, but any constant this clause named would be arbitrary today and wrong on the hardware of a few years from now. Leaving the partition entirely to the implementation would deliver nothing: a result that changed with core count, or with whether a machine was busy, is not what a program means by deterministic. A partition that depends only on the range gives an implementation every tuning decision that matters and takes away only the one that would make the answer move.
It calls body once with each
The calling
Two properties follow, and both are normative. Executing every slice on the calling parallelFor cannot deadlock by exhausting the pool, since the inner call
A reference into an element is valid for the slice that borrows it (shared SoA.<T>, writes to distinct fields do not contend either, each
Each slice (parallelFor.
The result is therefore a function of the range, the body, and the callbacks — and not of the scheduling. Given one implementation, the same begin and end and the same callbacks produce the same value on a busy machine and an idle one, on four cores and on sixty-four. This holds although combine need not be associative, which matters because floating-point addition is not and is the case the operation exists for.
The guarantee is only as strong as the callbacks. A perElement or combine that consults a ThreadLocal, asks which
If a call to body or perElement completes abruptly in slice k,
That is the completion a sequential execution would have produced, since a sequential execution reaches the lowest failing index first and stops there. Failures are therefore as reproducible as results, and for the same reason.
Reporting whichever failure happened first in time would be simpler and is what an
The
Neither operation is
A string has no layout, its size being a property of the value rather than of the type (
String.fromUtf8(bytes) returns the String the given bytes encode. bytes is any array-like of [N].<uint8>, a [].<uint8> and a Span.<uint8> all reach it. An element that is not an
String.prototype.toUtf8(into) encodes the String as UTF-8 into into and returns the number of bytes written. The target is a PARAMETER rather than the receiver, and is so that an owned array reaches it: a [N].<uint8> coerces to a Span.<uint8> at a boundary and a parameter is a boundary, where a method's receiver is not.
String.prototype.utf8Length is the number of bytes the String's UTF-8 encoding occupies. It is not length, which counts UTF-16 code
A value whose encoding does not fit into is a
Only WELL-FORMED text encodes. A String may hold an unpaired surrogate, which has no UTF-8 encoding; toUtf8 and utf8Length both raise a String.prototype.isWellFormed is the question to ask first and String.prototype.toWellFormed is the program's explicit escape — the pair hasLayout and byteLength, where the property that ANSWERS guards the one that ASSERTS.
Decoding is strict, and the refusals are the four ways a decoder is usually lax: a TRUNCATED sequence whose continuation bytes run past the end; an OVERLONG encoding, C0 80 for U+0000 being the classic, which would let one code point have several spellings and is a security defect wherever a filter runs before a decoder; a surrogate encoded as three bytes, which is CESU-8 and would let a lone surrogate survive a round trip the encoder refuses to produce; and a code point above U+10FFFF, which is not a code point. A stray continuation byte and a lead byte no sequence begins with are refused for the same reason.
A zero byte decodes to U+0000 like any other, and fromUtf8 decodes every byte it is given. Trimming padding is a property of a FORMAT rather than of UTF-8, so it belongs to whatever overlays the bytes; keeping it out of the conversion is what lets one codec serve a zero-padded record, a length-prefixed wire format, and a tag with no padding convention at all.
JSON.parse gains a generic overload, written JSON.parse.<_T_>(_text_), that parses and validates in one pass. The JSON.parse.<_T_>(_text_) is T, since the value it produces has been converted against T; the two-argument reviver form is the untyped overload and has the any. It parses text as JSON, which is a JSON.parse is, and then converts the resulting value against T by catch (_e_: SyntaxError) and catch (_e_: TypeError) separate a malformed document from a well-formed one of the wrong
A JSON array coerces against an where clause runs once the target is filled, as the construction boundary check. Unlike the general boundary of
The abstract operation CoerceJSONValue takes arguments value (an
number type, return value.string type, thenboolean type, thennull type, thenThe physical form the extension describes, writing straight into contiguous
The conversions this operation does not perform are the deferred part of the extension: a class target that fills a sealed layout without running its decimal128, and bigint) whose digits must convert without first rounding through a Number, the complex types, for which JSON has no syntax at all — a number carries no imaginary part, so admitting one would be a decision about what the absent part means rather than a conversion, and the enforcement of the built-in refinement judgments, which parameterized validation consults but which are themselves specified with the primitive metadata they belong to. An
This annex fixes the compile-time-evaluable subset of the language as an implementation target in its own right, so that a tool that is not an ECMAScript engine can evaluate types and prove that it agrees with one. The determinism this specification already requires, one result or failure and never a different result (
The fragment is the ECMAScript grammar less the following, together with the reading discipline of
eval, the Function import(), which the library rule below also excludes.debugger statement.Everything else is within the fragment: functions and closures over evaluable bindings, local bindings and their mutation, the ordinary operators and control forms including try, subject to
A built-in is within the fragment when its result is determined by its arguments, it performs no observable mutation outside values the call created, and it depends on no toString, regular expression construction and matching, Map and Set, whose keys Type Objects serve as by interned identity, Symbol and its registry, and the pure methods and functions of String, Number, BigInt, Math, Array, Object, and JSON. Symbol is within the fragment although a fresh symbol is not determined by its argument: a fresh symbol is a value the call creates, as a fresh array or Map is, and the determinism the fragment requires is of the types a program computes and the order it observes them in, both of which the same source fixes by creating the same symbols in the same sequence. Symbol.for is within it by the ordinary rule, its result determined by its key. Excluded by the same rule: Math.random, Date, every locale-sensitive method, Atomics and shared memory, and weak references and finalization. The conformance suite fixes the exact enumeration.
The fragment's semantics are this specification's own, restricted to the fragment; no second evaluator is defined. A standalone implementation of the fragment conforms when, for every program of the fragment, it produces, position for position, the same interned types this specification produces, or fails where this specification fails, under the budget invariant of
The subset is deliberately small as a language: a conforming standalone evaluator is a small tree-walking interpreter, not an embedded engine, and because evaluation is memoized, an interactive tool evaluates each distinct application once. Regular expressions are the one sizeable obligation, and the pattern metadata of
A conformance suite accompanies this annex, and conformance to the fragment is conformance to it together with this annex's text. Its seed is the ===, which interning supplies and which the erased corpus can only imitate through unspecified checker behaviour.
The primitives are normative and never, keyof, and the evaluation semantics. The kit is the 313 non-comment lines of ordinary evaluable code written over them, and it ships as source under one standard module, spelled std:types here and reconciled with the standard library extension by observing that the two do not overlap: that extension gives partial(User) in two packages is one type, and makes the definitions themselves the reference documentation. A codebase that cannot assume the module can polyfill it verbatim.
The surface is suffixed and stringPattern are written in the module and NOT exported while the pattern meta key remains a provisional claim, and instanceType was withdrawn because a class is its own instance type, so the export was the identity for every input anyone would bring it.
| Export | Signature | Answers |
|---|---|---|
reflect | reflect(T: type) | The type node of T, as |
literal | literal(value: string | number | boolean | bigint): type | The literal type of value. |
union | union(armList: [].<type>): type | The union of armList. |
arms | arms(T: type): [].<type> | The arms of a union, or the one-element list of a non-union. |
intersection | intersection(members: [].<type>): type | The intersection of members. |
literalValues | literalValues(T: type) | The values of a union of literal types, in order. |
literalValue | literalValue(T: type): any | The value of a literal type. |
prop | prop(name: string | symbol, type: type, | A property record, for objectOf. |
objectOf | objectOf(properties: [].<any>, indexSignatures: [].<any> = []): type | An object type with the given properties. |
tupleOf | tupleOf(types: [].<type>): type | A tuple type with the given element types. |
arrayOf | arrayOf(element: type, extent: uint32 | undefined = undefined): type | A dynamic array type of element. |
tupleElements | tupleElements(T: type): [].<any> | The element records of a tuple. |
elementTypes | elementTypes(T: type): [].<type> | The element types of a tuple. |
mapProperties | mapProperties(T: type, f): type | T with each property record replaced by f of it. |
mapPropertyTypes | mapPropertyTypes(T: type, f): type | T with each property TYPE replaced by f of it. |
mapElements | mapElements(T: type, f): type | A tuple with each |
propertyType | propertyType(T: type, name: string | symbol) | The type of the named property of T. |
genericApplication | genericApplication(base: type | string, args: [].<any>): type | base applied to args, using an intrinsic declaration's String name or a nominal declaration |
partial | partial(T: type): type | T with every property optional. |
required | required(T: type): type | T with every property required. |
readonly | readonly(T: type): type | T with every property readonly. |
mutable | mutable(T: type): type | T with every property mutable. |
pick | pick(T: type, K): type | T restricted to the keys of K. |
omit | omit(T: type, K): type | T without the named keys. |
record | record(K: type, V: type): type | An object type with the keys of K, each of type V. |
pickByValue | pickByValue(T: type, V: type): type | T restricted to properties whose type is assignable to V. |
removeKind | removeKind(T: type): type | T without its kind property. |
merge | merge(A: type, B: type): type | A overwritten by B. |
renameProperties | renameProperties(T: type, f): type | T with each |
exclude | exclude(T: type, U: type): type | The arms of T not assignable to U. |
extract | extract(T: type, U: type): type | The arms of T assignable to U. |
nonNullable | nonNullable(T: type): type | T without |
mapUnion | mapUnion(T: type, f): type | The union of f applied to each arm. |
toArrayEach | toArrayEach(T: type): type | A union of arrays, one per arm. |
toArrayAll | toArrayAll(T: type): type | An array of the whole union. |
discriminants | discriminants(T: type, tag: string = 'kind') | The literal values of the kind property across a union. |
byKind | byKind(T: type, k: string, tag: string = 'kind'): type | The arm of T whose kind is k. |
handlers | handlers(T: type, R: type, tag: string = 'kind'): type | An object type of one handler per arm, keyed by discriminant. |
fn | fn(parameterTypes: [].<type>, returnType: type): type | A function type with the given parameter types and return type. |
flatten | flatten(T: type): type | The |
firstParameter | firstParameter(F: type): type | The first parameter type. |
returnType | returnType(F: type): type | The return type of a function type. |
parameters | parameters(F: type): type | The parameter types of a function type, as a tuple. |
mapLiterals | mapLiterals(T: type, f): type | A union of literals with each value replaced by f of it. |
uppercase | uppercase(T: type): type | The literal union uppercased. |
lowercase | lowercase(T: type): type | The literal union lowercased. |
capitalized | capitalized(T: type): type | The literal union with each first character uppercased. |
uncapitalized | uncapitalized(T: type): type | The literal union with each first character lowercased. |
getters | getters(T: type): type | An object type of get-prefixed readonly accessors over T. |
listeners | listeners(T: type): type | An object type of on-prefixed change listeners over T. |
head | head(T: type): type | The first |
tail | tail(T: type): type | The tuple without its first element. |
concat | concat(A: type, B: type): type | The two tuples joined. |
reverse | reverse(T: type): type | The tuple reversed. |
zip | zip(A: type, B: type): type | The two tuples zipped into a tuple of pairs. |
deepPartial | deepPartial(T: type): type | partial, applied recursively through object properties. |
paths | paths(T: type): type | The union of dotted paths through T. |
compose | compose(...fs) | The composition of the given type functions. |
traverse | traverse(T: type, | T rebuilt through the given per-kind visitors. |
deepMap | deepMap(T: type, leaf): type | T with f applied to every leaf type. |
keys | keys(T: type): type | The key union of T — the function form of keyof. |
indexed | indexed(T: type, K: type): type | The indexed access T[K]. |
awaited | awaited(T: type): type | The awaited type of a Promise, or T unchanged. |
routeParams | routeParams(path: string): type | An object type of the :name parameters of a route pattern. |
noInfer | noInfer(T: type): type | T, blocking inference at this position. |
withThisType | withThisType(F: type, Self: type): type | The signature with Self as its this type. |
thisParameterType | thisParameterType(F: type): type | The declared this type, or |
omitThisParameter | omitThisParameter(F: type): type | The signature without its this type. |
options | options(Data: type, Methods: type): type | An object type pairing state with its operations. |
brand | brand(T: type, tag: string | symbol): type | T refined by a brand metadata portion carrying tag. |
constructorParameters | constructorParameters(C: type): type | The |
The style is visible in the definitions. The four property mappers are one line each, the union filters two, and the brand three:
export function partial(T: type): type { return mapProperties(T, p => ({ ...p, optional: true })); }
export function required(T: type): type { return mapProperties(T, p => ({ ...p, optional: false })); }
export function readonly(T: type): type { return mapProperties(T, p => ({ ...p, readonly: true })); }
export function mutable(T: type): type { return mapProperties(T, p => ({ ...p, readonly: false })); }
export function exclude(T: type, U: type): type {
return union(arms(T).filter(arm => !Reflect.isAssignable(arm, U)));
}
export function extract(T: type, U: type): type {
return union(arms(T).filter(arm => Reflect.isAssignable(arm, U)));
}
export function brand(T: type, tag: string | symbol): type {
return Reflect.makeType({ kind: 'parameterized', base: T, metadata: { brand: tag } });
}
Where the kit and the core describe one operation, they must agree, and the corpus checks that they do: the corpus's library blocks assert each shipped helper against the exercise it solves. A helper that REBUILDS a type is held to the identity of === — a type rebuilt incorrectly answers with the same kind, the same base and the same fields — so a conformance suite that compares keyof is a primitive, and type keyof T reaches it from a value position, so the kit has no key helper to keep in step. The kit's normative status is this annex's status: informative, with the primitives it is written over carrying the normativity.
© 2026 Sirisian
All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.