Multipage preference

Proposal runtime-types

Stage 0 Draft / November 2, 2026

Runtime Types

Introduction

This proposal adds an optional static type system to ECMAScript. Its types are values at run time and are enforced there: a typed binding rejects a value outside its type, a conversion between numeric types is written explicitly rather than performed silently, and the type of a value is available through reflection. Annotations are optional and additive, so a program that does not use them keeps its current meaning.

The system is deliberately close to the value-type discipline of systems languages. It adds sized integers, floats, and decimals; value-type classes with a defined in-memory layout; operator overloading; generics specialized by monomorphization; and types as first-class, interned values that ordinary code can compute with and reflect on. None of this is visible to a program that does not opt in.

This document is the normative specification. It is written against the design, which, together with the rationale, the full set of extensions, and worked examples, is maintained in the design repository. Readers new to the proposal should start there; this document formalizes what that material describes.

Note

The specification is under active construction. The clauses below establish the shape of the document; normative text is added phase by phase, following the specification plan in the design repository. Until a section is filled in here, the corresponding behaviour is defined only by the design documents.

1 Runtime Types

This clause and its subclauses define the runtime type system as a set of additions to and revisions of the ECMAScript specification. The work is organized into the phases described in the design repository's specification plan: the static type-checking framework; the grammar of type expressions; the type universe; conversions; runtime enforcement; typed declarations, operators, functions, and classes; the type-object and reflection API; and the numeric library. Extensions (SIMD, generics, decorators, primitive metadata, and the rest) are specified as independent additions layered on this core.

Each addition is expressed as an insertion into or revision of the edition of ECMAScript that 1.1 pins, so that this document reads as a patch against that specification rather than a restatement of it. Clauses appear in the order of the underlying ECMAScript specification, cross-referenced to the sections they modify, and the extension clauses follow the core, each of them named in Table 39.

1.1 Conventions

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 proposal. A cross-reference whose target is not defined here is a reference to the pinned edition and resolves against it. Where a later clause extends a production an earlier clause has already patched, the later block restates the production in full, marking only its own additions as inserted; the blocks are one production, and the latest restatement is its complete form.

The design is the collection of documents in the ecmascript-types repository: a README specifying the core and a set of extension documents specifying SIMD, generics, decorators, primitive metadata, memory layout, type programming, and the rest. This specification is that design rendered as a patch against the pinned edition. A note that cites the design is pointing at the source of a requirement, at a case the design works through, or at a question this specification decided and the design should record; as the proposal advances, each such citation should become a link to an issue in that repository, so that a reader can follow a decision to the discussion that settled it.

Three conventions govern the semantics. The relations of the type system, SameType, IsSubtype, IsAssignable, and the operations over Type Records, are abstract operations rather than syntax-directed operations, because they relate types to types and not Parse Nodes to values; the syntax-directed part of the type system is the Static Type of an expression (1.2.6), which each clause that adds an expression form extends. The static semantics that attach to a production are Early Errors, and this document states one with the phrase “it is a type error if”, which 1.2.8 defines as an Early Error rather than a thrown error; they are stated at the rule they belong to rather than gathered into one block, because a type error is a condition on types and reads where the types are. A thrown TypeError or RangeError is reserved for a check that could not be resolved statically.

Note

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 design records is either specified here or named in Table 39 as belonging to an extension. What it has not had is the multi-editor review that turns a complete specification into an approved one, and the decisions it records, marked as such in the notes, are this specification's answers rather than the design's, offered so that a reader can disagree with a stated position instead of guessing at an unstated one.

1.2 Type System Foundations

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.

Note

ECMAScript defines runtime behaviour through abstract operations and a limited set of static semantics, chiefly Early Errors, but it has no framework for assigning a type to an expression or relating two types. This clause establishes that framework. Because a type in this proposal is also a value, a single notion, the Type Object, serves both as the entity the type checker reasons about and as the value a program observes.

1.2.1 Types and Type Objects

A Type Record is a Record used by this specification to describe an ECMAScript type. Each Type Record has a [[Kind]] field and the additional fields listed for that kind in Table 1.

Table 1: Type Record Kinds
[[Kind]]Additional FieldsDescription
any(none)The dynamic type. Any value is assignable to it, and a value of the any type is assignable to any type subject to a run-time check (1.7.7.1).
void(none)The type of the absence of a value.
primitive[[Name]] (a String), [[Arguments]] (a List of Type Records and other values)A built-in value type such as 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.
literal[[Value]] (a String, Number, Boolean, BigInt, or Symbol), [[Base]] (a Type Record)A literal type: the type whose one value is [[Value]], a subtype of its [[Base]] (1.5.19).
parameterized[[Base]] (a Type Record), [[Metadata]] (a metadata record)A metadata parameterization of any base type, such as float32.<{ m: 1 }> or a brand: a refinement of its [[Base]] (1.5.20).
nominal[[Declaration]] (a Parse Node), [[Arguments]] (a List of Type Records and other values), and, for a source interface or evaluated source class, [[DeclarationIdentity]] (an opaque specification identity)A class, interface, or enum type, identified by the declaration that introduced it, together with the arguments of a generic instantiation. A source interface also retains the binding instance that introduced it, as specified in 1.11.7.1. A source class retains its evaluated identity under 4.8. [[Arguments]] is empty where the declaration is not generic.
union[[Members]] (a List of Type Records)A union type. A value belongs to it if it belongs to any member.
intersection[[Members]] (a List of Type Records)An intersection type. A value belongs to it if it belongs to every member.
tuple[[Elements]] (a List of Tuple Element Records)A tuple type of position-wise element types, each element carrying its spread flag and its default.
array[[Element]] (a Type Record), [[Extent]] (a non-negative integer or dynamic)An array type. [[Extent]] is the fixed length of a [N].<T>, or dynamic for a [].<T>.
object[[Properties]] (a List of Property Type Records), [[IndexSignatures]] (a List of Index Signature Records)A structural object type or interface type.
function[[Signatures]] (a List of Signature Records)A function type. It has more than one signature when the function is overloaded.
reference[[Target]] (a Type Record)A reference to a storage location holding a value of the target type, as introduced by ref.
shared[[Target]] (a Type Record)A value type whose storage is shared between the threads of one heap, as introduced by shared (43.4). A value of it is a value of its [[Target]]; what the modifier decides is where the storage lives and what a checker may assume of it.
parameter[[Declaration]] (a TypeParameter Parse Node), [[Arity]] (a non-negative integer)A generic parameter standing for the type or value an application will bind, within the body and signatures of its declaration (4). An [[Arity]] of 0 is such a parameter; an [[Arity]] of n > 0 is a higher-kinded parameter, which stands for a generic declaration of n parameters rather than for a type, and is not itself a type (4.13). Frozen: outside its declaration a fully specialized type contains none.
deferred[[Operator]] (a core type operator, one of keyof and indexed, the built-in iterator-completion operation, or a compile-time-evaluable function), [[Operands]] (a List of Type Records and ECMAScript language values, at least one of which involves an unbound parameter)A deferred computation over a generic parameter, carried as a type until specialization closes its operands and applies the operator: keyof T (1.4.2.5), T[K] (1.4.2.6), a call of a compile-time-evaluable function (1.2.7), or IteratorCompletionValue (30). Two are the same when their [[Operator]]s are the same - a core or built-in operator by its name, a function by identity - and their [[Operands]] are pairwise the same. One kind rather than one per operator, because every deferred computation has the one identity rule, the one evaluation rule and the one opacity rule; a deferred operator is not a parameter, which is identified by a declaration a derived type has none of.
Note 1

The structured fields referenced above, a Property Type Record, an Index Signature Record, a Signature Record, and a Tuple Element Record, are defined by the clauses that introduce object types, function types, and arrays and tuples. This clause depends only on Type Records being comparable and canonicalizable, which the operations below provide for every kind.

A Type Object is an ECMAScript object that denotes a type. Every Type Object has a [[TypeRecord]] internal slot whose value is a canonical Type Record (1.2.2.8). A type name or type expression in expression position evaluates to a Type Object, and reflection returns one.

Type Objects are interned. 1.2.2.9 maps each canonical Type Record to a unique Type Object, so two Type Objects denote the same type if and only if they are the same value. Consequently comparing two Type Objects with IsStrictlyEqual returns true exactly when they denote the same type, and a Type Object may be used as a property key or a key in a Map or Set to key on a type.

Note 2

The observable behaviour of a Type Object, including how it exposes its own structure to reflection, is defined by 1.12. The remainder of the type system uses only its [[TypeRecord]] and its identity.

1.2.2 Structural Identity

Two types are the same when they have the same structure, with one exception: a nominal type is identified by its declaration, so two classes with identical members are still different types. This clause defines that notion and the canonical form that makes it decidable and internable. A pair of Type Records is carried through the recursive comparisons as a List so that comparison of a recursive type terminates; each such pair is represented as a Record with a [[First]] field and a [[Second]] field.

1.2.2.1 SameType ( s, t )

The abstract operation SameType takes arguments s (a Type Record) and t (a Type Record) and returns a Boolean. It determines whether s and t denote the same type. It performs the following steps when called:

  1. Return SameTypeWithAssumptions(s, t, « »).
  2. If IsSubtype(s, t, « ») is false, return false.
  3. Return IsSubtype(t, s, « »).

SameType is MUTUAL SUBTYPING. It is therefore reflexive, symmetric, and transitive wherever IsSubtype is reflexive and transitive, which is to say it is an equivalence relation - and callers that de-duplicate, intern, or otherwise treat it as equality may rely on that.

Note

It was previously defined as SameTypeWithAssumptions, whose algorithm carries three steps keyed on s alone - for an enum, a literal, and a parameterized type - with no mirror for t. That made a relation named for equality ASYMMETRIC: SameType("a", string) answered true where SameType(string, "a") answered false. Callers treating it as equality inherited an order dependence; de-duplicating a union by it discarded 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 IsSubtype computes, and a caller asking "does s refine to t" should call IsSubtype rather than a relation named for sameness.

1.2.2.2 SameTypeWithAssumptions ( s, t, assumptions )

The abstract operation SameTypeWithAssumptions takes arguments s (a Type Record), t (a Type Record), and assumptions (a List of Records that have [[First]] and [[Second]] fields) and returns a Boolean. It determines whether s and t denote the same type, treating each pair in assumptions as already established so that comparison of recursive types terminates. It performs the following steps when called:

  1. If s and t are the same Record, return true.
  2. If assumptions contains a Record p such that p.[[First]] is s and p.[[Second]] is t, return true.
  3. If s.[[Kind]] is not t.[[Kind]], return false.
  4. Set assumptions to the list-concatenation of assumptions and « the Record { [[First]]: s, [[Second]]: t } ».
  5. If s.[[Kind]] is any or s.[[Kind]] is void, return true.
  6. If s.[[Kind]] is primitive, then
    1. If s.[[Name]] is not t.[[Name]], return false.
    2. Return SameArgumentList(s.[[Arguments]], t.[[Arguments]], assumptions).
  7. If s.[[Kind]] is literal, then
    1. If SameValue(s.[[Value]], t.[[Value]]) is false, return false.
    2. Return SameTypeWithAssumptions(s.[[Base]], t.[[Base]], assumptions).
  8. If s.[[Kind]] is parameterized, then
    1. If s.[[Metadata]] and t.[[Metadata]] are not structurally equivalent (1.2.7.2), return false.
    2. Return SameTypeWithAssumptions(s.[[Base]], t.[[Base]], assumptions).
  9. If s.[[Kind]] is deferred, then
    1. If s.[[Operator]] and t.[[Operator]] are not the same - a core or built-in operator by its name, a function by identity - return false.
    2. Return SameArgumentList(s.[[Operands]], t.[[Operands]], assumptions).
  10. If s.[[Kind]] is nominal, then
    1. If s.[[Declaration]] and t.[[Declaration]] are not the same Parse Node, return false.
    2. If either s or t has a [[DeclarationIdentity]] field, then
      1. If either lacks that field, return false.
      2. If s.[[DeclarationIdentity]] and t.[[DeclarationIdentity]] are not the same identity, return false.
    3. Return SameArgumentList(s.[[Arguments]], t.[[Arguments]], assumptions).
  11. If s.[[Kind]] is union or s.[[Kind]] is intersection, return SameTypeList(s.[[Members]], t.[[Members]], assumptions).
  12. If s.[[Kind]] is tuple, then
    1. If s.[[Elements]] and t.[[Elements]] do not have the same number of elements, return false.
    2. For each integer i such that 0 ≤ i and i < the number of elements of s.[[Elements]], do
      1. Let p be s.[[Elements]][i].
      2. Let q be t.[[Elements]][i].
      3. If p.[[Rest]] is not q.[[Rest]], return false.
      4. If p.[[Initial]] is none or q.[[Initial]] is none, then
        1. If p.[[Initial]] is not none or q.[[Initial]] is not none, return false.
      5. Else if SameValue(p.[[Initial]], q.[[Initial]]) is false, then
        1. Return false.
      6. If SameTypeWithAssumptions(p.[[Type]], q.[[Type]], assumptions) is false, return false.
    3. Return true.
  13. If s.[[Kind]] is array, then
    1. If s.[[Extent]] is not t.[[Extent]], return false.
    2. Return SameTypeWithAssumptions(s.[[Element]], t.[[Element]], assumptions).
  14. If s.[[Kind]] is reference or s.[[Kind]] is shared, return SameTypeWithAssumptions(s.[[Target]], t.[[Target]], assumptions).
  15. If s.[[Kind]] is function, return SameFunctionType(s, t, assumptions).
  16. Assert: s.[[Kind]] is object.
  17. Return SameObjectType(s, t, assumptions).
Note

SameTypeList holds when two Lists of Type Records have the same length and are position-wise the same under assumptions. SameArgumentList is SameTypeList extended to compare non-type arguments, such as an array extent, with SameValue. SameObjectType (1.11.1.2) and SameFunctionType (1.10.2.1) compare the members of an object or function type. Because a canonical union or intersection has its members in a fixed order (1.2.2.8), position-wise comparison of members is sufficient.

1.2.2.3 SameTypeStructural ( s, t )

The abstract operation SameTypeStructural takes arguments s (a Type Record) and t (a Type Record) and returns a Boolean.

It determines whether s and t are the same type CONSTRUCTION, rather than whether they denote the same values.

It performs the following steps when called:

  1. Return SameTypeWithAssumptions(s, t, « »).

This is a STRICTER relation than SameType. Two Type Records may denote the same values and still differ in construction - most importantly in the metadata they claim - so an implementation that interns Type Objects must use this relation and not SameType. Interning by SameType merges records whose claims differ, and the claim of whichever record is discarded is lost with it.

Note

It is the relation SameType was defined as before that operation became mutual subtyping, kept here under a name that says what it computes. The two are separated because they answer different questions: SameType asks whether a value of one is acceptable where the other is required, and SameTypeStructural asks whether the two were built the same way.

1.2.2.4 SameTypeList ( a, b, assumptions )

The abstract operation SameTypeList takes arguments a (a List of Type Records), b (a List of Type Records), and assumptions (a List of Records that have [[First]] and [[Second]] fields) and returns a Boolean. It determines whether a and b have the same length and are position-wise the same type under assumptions. It performs the following steps when called:

  1. If a and b do not have the same number of elements, return false.
  2. For each integer i such that 0 ≤ i and i < the number of elements of a, do
    1. If SameTypeWithAssumptions(a[i], b[i], assumptions) is false, return false.
  3. Return true.

1.2.2.5 SameArgumentList ( a, b, assumptions )

The abstract operation SameArgumentList takes arguments a (a List), b (a List), and assumptions (a List of Records that have [[First]] and [[Second]] fields) and returns a Boolean. It determines whether a and b have the same length and equal elements, comparing Type Record elements as types under assumptions, Parse Node elements by being the same Parse Node, compound compile-time constants by structural equivalence (1.2.7.2), and other elements with SameValue. It performs the following steps when called:

  1. If a and b do not have the same number of elements, return false.
  2. For each integer i such that 0 ≤ i and i < the number of elements of a, do
    1. Let x be a[i].
    2. Let y be b[i].
    3. If x is a Type Record and y is a Type Record, then
      1. If SameTypeWithAssumptions(x, y, assumptions) is false, return false.
    4. Else if x is a Type Record or y is a Type Record, then
      1. Return false.
    5. Else if x is a Parse Node and y is a Parse Node, then
      1. If x and y are not the same Parse Node, return false.
    6. Else if x is a Parse Node or y is a Parse Node, then
      1. Return false.
    7. Else if x and y are compound compile-time constants, then
      1. If x and y are not structurally equivalent (1.2.7.2), return false.
    8. Else if SameValue(x, y) is false, then
      1. Return false.
  3. Return true.

1.2.2.6 AreDisjoint ( s, t [ , active ] )

The abstract operation AreDisjoint takes arguments s (a Type Record) and t (a Type Record) and optional argument active (a List of Records) and returns a Boolean. It determines whether no value is of both s and t. It performs the following steps when called:

  1. If active is not present, set active to a new empty List.
  2. If active contains a Record whose [[First]] is s and whose [[Second]] is t, return false.
  3. Set active to the list-concatenation of active and « the Record { [[First]]: s, [[Second]]: t } ».
  4. If s.[[Kind]] is any, parameter, or deferred, return false.
  5. If t.[[Kind]] is any, parameter, or deferred, return false.
  6. If s.[[Kind]] is shared, return AreDisjoint(s.[[Target]], t, active).
  7. If t.[[Kind]] is shared, return AreDisjoint(s, t.[[Target]], active).
  8. If s.[[Kind]] is intersection, then
    1. For each element m of s.[[Members]], do
      1. If AreDisjoint(m, t, active) is true, return true.
    2. Return false.
  9. If t.[[Kind]] is intersection, then
    1. For each element n of t.[[Members]], do
      1. If AreDisjoint(s, n, active) is true, return true.
    2. Return false.
  10. If s.[[Kind]] is union, then
    1. For each element p of s.[[Members]], do
      1. If AreDisjoint(p, t, active) is false, return false.
    2. Return true.
  11. If t.[[Kind]] is union, then
    1. For each element q of t.[[Members]], do
      1. If AreDisjoint(s, q, active) is false, return false.
    2. Return true.
  12. If s.[[Kind]] is void or t.[[Kind]] is void, return true if s.[[Kind]] is not t.[[Kind]], and false otherwise.
  13. Let sb be s; while sb.[[Kind]] is parameterized, set sb to sb.[[Base]].
  14. Let tb be t; while tb.[[Kind]] is parameterized, set tb to tb.[[Base]].
  15. If sb.[[Kind]] is literal and tb.[[Kind]] is literal, return false if SameType(sb, tb) is true, and true otherwise.
  16. If sb.[[Kind]] is literal, set sb to sb.[[Base]].
  17. If tb.[[Kind]] is literal, set tb to tb.[[Base]].
  18. If both sb and tb are composite types (25.2), then
    1. If either is the top composite type, return false.
    2. Return CompositeShapesAreDisjoint(the shape of sb, the shape of tb, active).
  19. If exactly one of sb and tb is a composite type, then
    1. Let other be the one that is not a composite type.
    2. If other.[[Kind]] is primitive, array, or function, return true.
    3. If other is an enum type, return true.
    4. Return false.
  20. If sb.[[Kind]] is primitive and tb.[[Kind]] is primitive, return false if SameType(sb, tb) is true, and true otherwise.
  21. If sb.[[Kind]] is primitive and tb.[[Kind]] is object, function, array, or tuple, return true.
  22. If tb.[[Kind]] is primitive and sb.[[Kind]] is object, function, array, or tuple, return true.
  23. Return false.
Note

This is the judgment 1.8.5.3 already made in prose, given a name so that canonicalization, the Early Error of 1.4.2.1, and the narrowing rows decide it once and the same way. Everything it answers true for that clause's note had already committed to: two distinct non-composite primitive types have no common values, since each value belongs to exactly one of them (1.5.1), and neither does a non-composite primitive type share values with an object or a function type.

An intersection of exactly two parameterized Type Records over one base reduces to their MEET, where a governing meta type gave one: the constraint its meet hook returned (Table 42), which is satisfied by exactly the values satisfying both. So uint8<{ bounds: 1..=10 }> & uint8<{ bounds: 5..=20 }> is uint8<{ bounds: 5..=10 }>, and the two spellings intern to one Type Object — the reason the reduction is here and not at each use, since a type reached by two spellings must answer ===.

The hook runs USER CODE and this operation does not, so the meet is computed by the checking pass and read here. A meta type declaring no meet, or returning undefined, leaves the intersection standing, which is what keeps two differently-branded parameterizations of one primitive distinct: 1.2.2.6 decides on the base and leaves them overlapping, and a meta type with nothing to prove says nothing here either.

The operation is CONSERVATIVE: where the values of a type are not yet known it answers false, never true. any, a parameter, and a deferred computation whose operands have not closed therefore overlap with everything, which is what keeps a generic body from being rejected for a disjointness that only some instantiation would have. It is likewise silent about two object types, two nominal types, and a nominal against an object: two object types may share a value, which is the case intersections exist for, and whether two unrelated nominal CLASSES are disjoint is a question this operation deliberately does not answer, there being no need of it and a subtyping cost to deciding it. That question is answered instead by an Early Error on the syntax (1.4.2.1) and by a step of NarrowTo (1.8.5.3), both run by the checking pass, which is already running IsSubtype, so the interning path this operation sits on is not disturbed. A written 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 parameterized type and never on its metadata, which is what keeps a layered brand a type rather than an empty one: 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 (1.5.20). A single step would have compared one nested brand against another and found two parameterized types it could not relate, and reading that as disjoint would have emptied every layered type in the language.

The base a walk arrives at need not be a primitive. A literal base is decided by the primitive beneath it, so a brand on 'a' and a brand on string may share a value while a brand on 'a' and a brand on 'b' may not. An object or array base carries a brand as a mark on the value, so two brands over one object shape are two refinements of it exactly as over a string, and a brand over an object is disjoint from a primitive for the ordinary reason rather than because it is branded.

1.2.2.7 CompositeShapesAreDisjoint ( s, t, active )

The abstract operation CompositeShapesAreDisjoint takes arguments s (a Type Record), t (a Type Record), and active (a List of Records) and returns a Boolean. It proves selected contradictions between two composite shapes without evaluating values or treating a membership contract as a construction schema. An unresolved case returns false. It performs the following steps when called:

  1. If one of s and t has [[Kind]] object and the other has [[Kind]] tuple or array, return true.
  2. If both s.[[Kind]] and t.[[Kind]] are object, then
    1. For each Property Type Record p of s.[[Properties]], do
      1. Let q be the Property Type Record of t.[[Properties]] with the same [[Key]] as p, or none if there is none.
      2. If q is not none, at least one of p.[[Optional]] and q.[[Optional]] is false, and AreDisjoint(p.[[Type]], q.[[Type]], active) is true, return true.
    2. Return false.
  3. Let sFixed be true if s.[[Kind]] is tuple and none of its elements is a rest element, and false otherwise.
  4. Let tFixed be true if t.[[Kind]] is tuple and none of its elements is a rest element, and false otherwise.
  5. If sFixed is true, let sMin be the minimum length admitted by the declared defaults of s.
  6. If tFixed is true, let tMin be the minimum length admitted by the declared defaults of t.
  7. If sFixed is true and tFixed is true, then
    1. Let required be max(sMin, tMin).
    2. If required is greater than the smaller of the numbers of elements of s and t, return true.
    3. For each integer i such that 0 ≤ i < required, do
      1. If AreDisjoint(s.[[Elements]][i].[[Type]], t.[[Elements]][i].[[Type]], active) is true, return true.
  8. Else if sFixed is true and t.[[Kind]] is array, then
    1. For each integer i such that 0 ≤ i < sMin, do
      1. If AreDisjoint(s.[[Elements]][i].[[Type]], t.[[Element]], active) is true, return true.
  9. Else if tFixed is true and s.[[Kind]] is array, then
    1. For each integer i such that 0 ≤ i < tMin, do
      1. If AreDisjoint(s.[[Element]], t.[[Elements]][i].[[Type]], active) is true, return true.
  10. Return false.

Record membership permits extra keys by width subtyping. Two conflicting optional properties can both be absent; if either is required, their common value must satisfy both property contracts. Likewise, only tuple positions required at every common admitted length can prove an element conflict. Two array composite shapes can share the empty tuple even when their element types are disjoint; an array shape denotes the variable-length tuple composite. A declared tuple default establishes possible omission without evaluating its initializer. Rest-containing tuple shapes, index-signature interactions and other unresolved shape constraints retain a conservative result unless the kind distinction already proves failure. Repeating an active Type Record pair supplies no proof of disjointness; other independently established conflicts are still checked.

1.2.2.8 CanonicalizeType ( t )

The abstract operation CanonicalizeType takes argument t (a Type Record) and returns a Type Record. It returns the canonical form of t, reading no [[Origin]] (23) in deciding it. Canonical Type Records that denote the same type are the same Record, which is what lets 1.2.2.9 intern them. It performs the following steps when called:

  1. If t.[[Kind]] is union or t.[[Kind]] is intersection, then
    1. Let members be a new empty List.
    2. For each element m of t.[[Members]], do
      1. Let c be CanonicalizeType(m).
      2. If c.[[Kind]] is t.[[Kind]], then
        1. For each element n of c.[[Members]], append n to members.
      3. Else,
        1. Append c to members.
    3. Remove from members each element that is the same type (SameType) as an element occurring earlier in members.
    4. Let kept be a new empty List.
    5. For each element m of members, do
      1. Let subsumed be false.
      2. For each element n of members other than m, do
        1. If t.[[Kind]] is union, let wider be n and let narrower be m; otherwise let wider be m and let narrower be n.
        2. If IsSubtype(narrower, wider, « ») is true, and either IsSubtype(wider, narrower, « ») is false or n occurs before m in members, set subsumed to true.
      3. If subsumed is false, append m to kept.
    6. Set members to kept.
    7. If t.[[Kind]] is intersection and members contains an element whose [[Kind]] is union and whose [[Members]] is empty, return the Type Record { [[Kind]]: union, [[Members]]: « » } (the never type; see 1.5.18).
    8. If t.[[Kind]] is intersection and members contains two elements a and b such that AreDisjoint(a, b) is true, return the Type Record { [[Kind]]: union, [[Members]]: « » } (the never type).
    9. If t.[[Kind]] is intersection, members has more than one element, and every element of members has [[Kind]] object, then
      1. Let properties be a new empty List.
      2. Let signatures be a new empty List.
      3. For each element m of members, do
        1. For each Property Record p of m.[[Properties]], do
          1. Let q be the Property Record in properties whose [[Key]] is p.[[Key]], or empty if properties contains no such Record.
          2. If q is empty, then
            1. Append a copy of p to properties.
          3. Else,
            1. Set q.[[Optional]] to true if q.[[Optional]] is true and p.[[Optional]] is true, and false otherwise.
            2. Set q.[[Readonly]] to true if either q.[[Readonly]] or p.[[Readonly]] is true.
            3. Set q.[[Type]] to CanonicalizeType(the Type Record { [[Kind]]: intersection, [[Members]]: « q.[[Type]], p.[[Type]] » }).
        2. For each Index Signature Record ix of m.[[IndexSignatures]], append ix to signatures.
      4. Return CanonicalizeType(the Type Record { [[Kind]]: object, [[Properties]]: properties, [[IndexSignatures]]: signatures }).
    10. If members has exactly one element, return that element.
    11. Order the elements of members according to the total order of 1.8.5.2.
    12. Return the Type Record { [[Kind]]: t.[[Kind]], [[Members]]: members }.
  2. If t.[[Kind]] is tuple, then
    1. Let elements be a new empty List.
    2. For each element e of t.[[Elements]], do
      1. Let c be CanonicalizeType(e.[[Type]]).
      2. If e.[[Rest]] is true and c.[[Kind]] is tuple, then
        1. NOTE: A rest element whose type is itself a tuple is that tuple's elements in place, [...[uint8, string], T] being [uint8, string, T], which is what lets a spread of a bound variadic parameter (4.4) intern as the tuple it spells.
        2. For each element n of c.[[Elements]], append n to elements.
      3. Else,
        1. Append the Tuple Element Record { [[Type]]: c, [[Rest]]: e.[[Rest]], [[Initial]]: e.[[Initial]] } to elements.
    3. For each Tuple Element Record e of elements, do
      1. If e.[[Rest]] is false and e.[[Type]] is never, return never.
    4. Return the Type Record { [[Kind]]: tuple, [[Elements]]: elements }.
  3. If t.[[Kind]] is array, then
    1. Let element be CanonicalizeType(t.[[Element]]).
    2. If t.[[Extent]] is a Number greater than 0 and element is never, return never.
    3. Return the Type Record { [[Kind]]: array, [[Element]]: element, [[Extent]]: t.[[Extent]] }.
  4. If t.[[Kind]] is reference, then
    1. Let target be CanonicalizeType(t.[[Target]]).
    2. If target is never, return never.
    3. Return the Type Record { [[Kind]]: reference, [[Target]]: target }.
  5. If t.[[Kind]] is shared, then
    1. Let target be CanonicalizeType(t.[[Target]]).
    2. If target is never, return never.
    3. Return the Type Record { [[Kind]]: shared, [[Target]]: target }.
  6. If t.[[Kind]] is literal, return the Type Record { [[Kind]]: literal, [[Value]]: t.[[Value]], [[Base]]: CanonicalizeType(t.[[Base]]) }.
  7. If t.[[Kind]] is parameterized, then
    1. Let base be CanonicalizeType(t.[[Base]]).
    2. If base is never, return never.
    3. Return the Type Record { [[Kind]]: parameterized, [[Base]]: base, [[Metadata]]: t.[[Metadata]] }.
  8. If t.[[Kind]] is deferred, then
    1. Let args be a new empty List.
    2. For each element a of t.[[Operands]], do
      1. If a is a Type Record, append CanonicalizeType(a) to args.
      2. Else, append a to args.
    3. Return the Type Record { [[Kind]]: deferred, [[Operator]]: t.[[Operator]], [[Operands]]: args }.
  9. If t.[[Kind]] is object, then
    1. Let c be the canonical form of t defined alongside that kind, with each contained Type Record canonicalized.
    2. For each Property Record p of c.[[Properties]], do
      1. If p.[[Optional]] is false and p.[[Type]] is never, return never.
    3. Return c.
  10. If t.[[Kind]] is function, return the canonical form of t defined alongside that kind, with each contained Type Record canonicalized.
  11. Return t.
Note 1

An earlier draft forbade absorption, on the ground that folding by IsSubtype would make the canonical form depend on a relation that READS canonical forms, and that the unabsorbed union cost nothing because membership and assignability answer identically either way. Neither holds.

The circularity does not arise, because the fold runs on members ALREADY canonicalized by the loop above it: IsSubtype is asked about canonical forms that exist, never about the form being built. The order-independence the clause needs survives it — "a" | string and string | "a" are one Type Object, as are "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 slot of it holds one kind of value. The saving is in shape and inline caches rather than in what the relation answers, which is precisely where a canonical form is supposed to pay.

Note 2

An intersection every member of which is an object Type Record is ONE object type, and is returned as one. An intersection requires every member, so a value of it has every member's properties, and a name more than one member declares must be satisfied for each of them at once — which is what an intersection of the types they give it denotes. The merged member is built as an intersection and handed back to this operation rather than decided here, so the three answers already defined are the three it gets: two identical types deduplicate, a subtype pair subsumes to the narrower, and a disjoint pair reduces to 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 object Type Record. A member of another kind has values the merge cannot describe — an array shares values with an object type, being a subtype of 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 object Type Record a REQUIRED property of which is 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 reference, a shared type, and a parameterized type reduce with their target or base for the same reason: a ref names a location and there is no location of the empty type, a shared marker is not observable in the value (1.2.2.6), and metadata narrows a base's values and cannot add one.

A tuple and a fixed-extent array are products too, and reduce for the reason an object does: a value would have to hold a value of a type that has none. The rule must be the object's rather than a weaker one, because a program can write the same shape either way — type Pair<A: type, B: type> = [A, B] and type Result<T: type, E: type> = { value: T, error: E } are one shape with two spellings, and an instantiation at the empty type that answered differently for them would make inhabitation a property of how a shape was written.

A REST position does not empty a tuple, and neither a variable-length array nor a zero-extent one is emptied by its element type. Each supplies no element, so the empty array or the empty tuple inhabits it; this is the boundary 1.8.3.1 also draws, where a zero extent is answered before the element is consulted.

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 field being equally 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 Record and, after interning, one Type Object. A nominal, primitive, any, or void Type Record is already canonical apart from canonicalizing the Type Records in its [[Arguments]], which the recursion reaches when they occur. Recursion terminates because a cycle in a type passes through a nominal position, which is canonical without descending into its declaration. Flattening is also what removes a 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 (1.5.18). The second is what makes 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 Type Object of its own - uninhabited by the reasoning of 1.8.5.3, with no default value and no assignable source, and yet neither never nor assignable to it, so IsAssignable could not equate two empty types. Reducing also removes a dead arm from a union that has one, flattening then dropping the never, so string | (number & bigint) is string: a slot of that type holds one kind of value and an implementation need not tag it as a union to discover so. Canonicalization ABSORBS by subtyping, and the two kinds keep opposite survivors: a union keeps the WIDER member, so "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.

1.2.2.9 GetTypeObject ( t )

The abstract operation GetTypeObject takes argument t (a Type Record) and returns a Type Object. It returns the unique Type Object that denotes the type described by t. It performs the following steps when called:

  1. If type evaluation is active, charge one type-construction request as specified in 1.2.7.5.
  2. Let canonical be CanonicalizeType(t).
  3. If the type-object intern table contains a Type Object existing such that SameTypeStructural(existing.[[TypeRecord]], canonical) is true, return existing.
  4. Let obj be a new Type Object whose [[TypeRecord]] internal slot is canonical.
  5. Add obj to the type-object intern table.
  6. Return obj.
Note

The intern table is a specification device; an implementation may realize it in any way that yields the same identities. It is a property of a heap: Type Objects, like other objects, are shared by the agents that share one and are not shared with an agent that does not (42).

1.2.3 Subtyping and Assignability

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 any type. Because this proposal performs no implicit numeric widening, a primitive type is a subtype of no other numeric type. It is a subtype of itself, of the any type, and of the same primitive bearing fewer metadata arguments, since a metadata parameterization refines the type it parameterizes rather than being a different type: Meter is a float32.<{ m: 1 }> and is a subtype of float32, which is what makes m instanceof float32 true.

1.2.3.1 IsSubtype ( s, t, assumptions )

The abstract operation IsSubtype takes arguments s (a Type Record), t (a Type Record), and assumptions (a List of Records that have [[First]] and [[Second]] fields) and returns a Boolean. It determines whether every value of the type s is a value of the type t, treating each pair in assumptions as already established so that comparison of recursive types terminates. The initial call passes an empty List for assumptions. It performs the following steps when called:

  1. If s.[[Kind]] is function and t.[[Kind]] is function, then
    1. If s is t, return true.
    2. If assumptions contains a Record p such that p.[[First]] is s and p.[[Second]] is t, return true.
    3. Let next be the list-concatenation of assumptions and « the Record { [[First]]: s, [[Second]]: t } ».
    4. For each Signature Record b of t.[[Signatures]], do
      1. Let matched be false.
      2. For each Signature Record a of s.[[Signatures]], do
        1. If IsSignatureSubtype(a, b, next) is true, set matched to true.
      3. If matched is false, return false.
    5. Return true.
  2. If SameTypeWithAssumptions(s, t, assumptions) is true, return true.
  3. If assumptions contains a Record p such that p.[[First]] is s and p.[[Second]] is t, return true.
  4. Set assumptions to the list-concatenation of assumptions and « the Record { [[First]]: s, [[Second]]: t } ».
  5. If t.[[Kind]] is any, return true.
  6. If s.[[Kind]] is nominal and s.[[Declaration]] is an enum declaration and IsSubtype(the underlying type of s.[[Declaration]], t, assumptions) is true, return true.
  7. If s.[[Kind]] is literal and IsSubtype(s.[[Base]], t, assumptions) is true, return true.
  8. If s.[[Kind]] is parameterized, then
    1. If IsSubtype(s.[[Base]], t, assumptions) is true, return true.
    2. If t.[[Kind]] is parameterized and SameTypeWithAssumptions(s.[[Base]], t.[[Base]], assumptions) is true and the metadata subtype judgment of 1.5.20 holds of s.[[Metadata]] and t.[[Metadata]], return true.
  9. If s.[[Kind]] is union, then
    1. For each element m of s.[[Members]], do
      1. If IsSubtype(m, t, assumptions) is false, return false.
    2. Return true.
  10. If t.[[Kind]] is union, then
    1. For each element m of t.[[Members]], do
      1. If IsSubtype(s, m, assumptions) is true, return true.
    2. Return false.
  11. If t.[[Kind]] is intersection, then
    1. For each element m of t.[[Members]], do
      1. If IsSubtype(s, m, assumptions) is false, return false.
    2. Return true.
  12. If s.[[Kind]] is intersection, then
    1. For each element m of s.[[Members]], do
      1. If IsSubtype(m, t, assumptions) is true, return true.
    2. Return false.
  13. If s is a composite type (25.2), then
    1. If t is the top composite type, return true.
    2. If s is not the top composite type, then
      1. Let sShape be the shape of s.
      2. If t is a composite type that is not the top composite type, then
        1. Let tShape be the shape of t.
        2. If sShape.[[Kind]] is tuple and tShape.[[Kind]] is array, then
          1. NOTE: Between two composite shapes both sides are frozen, so the reason a tuple is not a subtype of an array type (a mutable container) does not arise; the array shape is the variable-length tuple composite, and a tuple composite is of it when every element is of the element type.
          2. For each element e of sShape.[[Elements]], do
            1. If IsSubtype(e.[[Type]], tShape.[[Element]], assumptions) is false, return false.
          3. Return true.
        3. Return IsSubtype(sShape, tShape, assumptions).
      3. If t.[[Kind]] is object and sShape.[[Kind]] is object, return IsObjectSubtype(sShape, t, assumptions).
      4. If t.[[Kind]] is tuple and sShape.[[Kind]] is tuple, return IsSubtype(sShape, t, assumptions).
      5. If t is an interface type with a structural form, return IsSubtype(s, that form, assumptions).
    3. Return false.
  14. If t is an interface type and s does not refine t, then
    1. Return IsSubtype(s, the structural form of t, assumptions).
  15. If s.[[Kind]] is function and t.[[Kind]] is object, and both t.[[Properties]] and t.[[IndexSignatures]] are empty Lists, return true.
  16. If s.[[Kind]] is nominal and t.[[Kind]] is object, then
    1. If s is an interface type, return IsSubtype(the structural form of s, t, assumptions).
    2. Return false.
  17. If s.[[Kind]] is parameter or t.[[Kind]] is parameter, then
    1. If s.[[Kind]] is parameter and t.[[Kind]] is parameter, then
      1. If s.[[Name]] is t.[[Name]] and s.[[Arity]] is t.[[Arity]], return true.
      2. Return false.
    2. If s.[[Kind]] is parameter and s has a [[Constraint]] field, return IsSubtype(s.[[Constraint]], t, assumptions).
    3. Return false.
  18. If s.[[Kind]] is shared and t.[[Kind]] is not shared, return IsSubtype(s.[[Target]], t, assumptions).
  19. If t.[[Kind]] is shared and s.[[Kind]] is not shared, return IsSubtype(s, t.[[Target]], assumptions).
  20. If t.[[Kind]] is array, t.[[Extent]] is dynamic, t.[[Element]].[[Kind]] is any, and s.[[Kind]] is either array or tuple, return true.
  21. If s.[[Kind]] is not t.[[Kind]], return false.
  22. If s.[[Kind]] is nominal, then
    1. If t.[[Declaration]] is s.[[Declaration]], return SameArgumentList(s.[[Arguments]], t.[[Arguments]], assumptions).
    2. If t.[[Declaration]] is an ancestor of s.[[Declaration]] in the class and interface hierarchy, then
      1. Return the result of comparing t.[[Arguments]] against the arguments s supplies to t.[[Declaration]] through the hierarchy, with SameArgumentList.
    3. Return false.
  23. If s.[[Kind]] is array, then
    1. If t.[[Extent]] is not s.[[Extent]], return false.
    2. Return SameTypeWithAssumptions(s.[[Element]], t.[[Element]], assumptions).
  24. If s.[[Kind]] is tuple, then
    1. If s and t both have established positional domains as described in 1.14.1, return true if the source length range is included in the target length range and every occupied source position is a subtype of its corresponding target position at every admitted source length, and false otherwise.
    2. If one of s and t has an established domain involving finite or nested rest expansion and the other has no established positional domain, return false.
    3. If the minimum length of s is less than the minimum length of t, return false, where a tuple's minimum length counts the elements whose [[Rest]] is false and [[Initial]] is none.
    4. If the maximum length of s exceeds the maximum length of t, return false, where a tuple's maximum length is unbounded when any element's [[Rest]] is true and its element count otherwise.
    5. If t has more than one element whose [[Rest]] is true, then
      1. If the element counts of s.[[Elements]] and t.[[Elements]] differ, return false.
      2. For each integer i such that 0 ≤ i and i < the element count of t.[[Elements]], do
        1. If s.[[Elements]][i].[[Rest]] is not t.[[Elements]][i].[[Rest]], return false.
        2. If IsSubtype(s.[[Elements]][i].[[Type]], t.[[Elements]][i].[[Type]], assumptions) is false, return false.
      3. Return true.
    6. For each integer i such that 0 ≤ i and i < the greater of the element counts of s.[[Elements]] and t.[[Elements]], do
      1. If s cannot supply position i, continue, where a tuple can supply each position below its maximum length.
      2. If IsSubtype(the type at position i of s, the type at position i of t, assumptions) is false, return false, where the type at a position of t is the [[Type]] of the element that receives it and the type at a position of s is the union of the [[Type]] fields of every element of s that could receive it, an element receiving a position when some assignment of that many values to s.[[Elements]] gives it that one (1.10.4.2).
    7. Return true.
  25. If s.[[Kind]] is reference or s.[[Kind]] is shared, return SameTypeWithAssumptions(s.[[Target]], t.[[Target]], assumptions).
  26. If s.[[Kind]] is function, return IsFunctionSubtype(s, t, assumptions).
  27. If s.[[Kind]] is object, return IsObjectSubtype(s, t, assumptions).
  28. Return false.
Note 1

A refinement is a subtype of what it refines, and the three steps above are where that is decided. They were carried by 1.2.2.1, which this operation consults before any rule below, and that placement made them unreachable: a relation named for sameness answered true for two object types whose members merely refined each other, so this operation returned there and 1.11.1.3's writable-member invariance was never consulted. { 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 slot the program believes holds a Meter" - was admitted. SameType decides sameness; this operation decides refinement.

Note 2

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 is a String, so a 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 "s" is not a value of "abc". So function f<T: string>(x: T): string { return x; } is well typed and 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.

Note 3

A function type describes callable Objects, so every value of it satisfies an object type with no properties or index signatures. This is a subtype relation, not type identity: reflection retains the function's signatures. A signature alone establishes no named property or index signature, and an empty object contract does not establish callability.

Three of the steps above are one relation. A type refines another when it is a parameterized type whose [[Base]] is that type (1.5.20), an enum whose underlying type is that type (1.11.8), or a nominal type whose declaration extends or implements that type's declaration. A type is a subtype of every type it refines and of everything that type is a subtype of; the converse never holds without a crossing, a construction boundary for a parameterization, a validated call for an enum, and nothing at all for a class. The three steps are that rule's cases.

Note 4

A literal type is a subtype of its base and of everything its base is a subtype of, which is what makes '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 deferred computation is a subtype of itself, of the any type, and of its operator's BOUND where the operator has one: the key universe string | symbol | uint32 for keyof, T[keyof T] for an indexed access whose index is a subtype of 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 specialization nothing finer than identity is known about its result, so nothing finer is assumed: two mentions of one deferred call are one type by interning, and two different calls are unrelated until they evaluate. No step above names the kind because none needs to; a deferred source reaches the final step for every target but itself and its operator's bound and 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 typed uint8. A parameter may be declared covariant or contravariant, as it may in C# and Kotlin, and 4.12 states what such a declaration means; the step above is what it relaxes. Invariance is what a parameter carrying no such declaration has.

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 contextual type fix it, as Promise.reject does with its E.

Note 5

[ ] . < any > is the top of the array family: an array of some element type, which every array and every tuple type is a subtype 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 type within it.

Element invariance is untouched for every other element type, for the reason given above: a [ ] . < uint8 > used as a [ ] . < number > would accept a Number into storage typed 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 element type at run time (Table 23), so a write through the wider view is refused whatever the static type permitted. A language whose containers are invariant and whose elements are unchecked supplies this as a wildcard or a star projection and forbids writing through it; the check here is what takes the place of that prohibition.

Without this the bound a generic writes over the array family, T extends [ ], is satisfied by an array whose element type is written 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 set of some element type and Map.<any, any> a map of some key and value types, and every instantiation is a subtype of its family's top. What makes 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 (1.14.10), so a write through the wider view is refused whatever the static type permitted.

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 1.15.7 - and a 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.

Note 6

This resolves an inconsistency rather than adding a rule. A specialization of a LIBRARY generic was formerly a subtype of its bare name while a specialization of a USER generic was not, so 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 proposal: a nominal type is a subtype along its declared inheritance; an array is invariant in its element and in its extent, a fixed-length array being neither a subtype nor a supertype of the dynamic-length array of the same element (11.1 gives Span.<T>, the one type that accepts either, and 1.7.11 states the same rule for instanceof), with the array whose element is any a supertype of every array and tuple type (Note 5); a tuple is covariant position-wise, which is sound only because a store to a position is checked against the type the value carries for it (1.14.10), as the same rule makes [].<any> a supertype of every array, and makes Set.<any> and Map.<any, any> supertypes of every instantiation of theirs; a reference is invariant in its target, as is a shared (43.4); a composite type is covariant in its shape, which the frozenness of every composite makes sound (25.2). IsObjectSubtype (1.11.1.3) applies width subtyping to properties and depth subtyping through a readonly one; IsFunctionSubtype (1.10.2.4) applies contravariant-parameter, covariant-return subtyping to signatures. An enum type is additionally a subtype of its underlying type (1.11.8). The justification for each choice is given by the clause that introduces the corresponding feature.

1.2.3.2 IsAssignable ( s, t )

The abstract operation IsAssignable takes arguments s (a Type Record) and t (a Type Record) and returns a Boolean. It determines whether a value of the type s may be assigned where the type t is expected. This is the relation checked at bindings, arguments, and returns. It performs the following steps when called:

  1. If s.[[Kind]] is any or t.[[Kind]] is any, return true.
  2. Return IsSubtype(s, t, « »).
Note

When s is the any type and t is not, assignability holds but the value's membership in t is not known statically; a check is inserted at the boundary (1.7.7.1) that throws a TypeError if the value is not of the type t. When t is the any type, no check is required. This two-part treatment, static where the types are known and a run-time check at the any boundary, is the gradual character of the system: it is why an untyped program keeps its behaviour while a typed one is checked.

1.2.4 Strict Mode for Annotated Code

A Script whose own body contains a TypeAnnotation is strict mode code. A function-like unit whose parameters, return position or own body contains a TypeAnnotation is strict mode code, including its parameters and all code that inherits its strictness. This classification applies to the entire unit independently of source order. Annotation discovery for an enclosing unit excludes nested function-like units, including their parameters and return annotations, and nested class bodies. A method's computed name and decorator expressions belong to their enclosing evaluation scope. Classes and Modules retain their existing strictness.

The trigger is syntactic and includes an annotation denoting any; it does not resolve or evaluate the annotated type. Conversion expressions, inferred types and other type syntax do not independently activate this rule. Nested functions inherit strictness downward, but a nested function's annotation does not activate strictness in its parent or siblings. The strict-code Early Errors are Syntax Errors, and the usual strict runtime semantics apply, including the unbound this value, unmapped arguments and direct eval's scope. An implicit strict unit does not contain a synthetic use-strict directive, so it does not acquire the separate prohibition on such a directive with non-simple parameters. A dynamically constructed function classifies its own combined parameter/body source; it does not inherit the caller's mode.

Checked code (1.2.5) is classified the same way from a wider set of syntax, and adds type errors only.

Example (Informative)

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();

1.2.5 Checked Code

A unit is a Script, a Module, a function-like unit or a class. Proposal syntax is any production this specification adds to the grammar of the pinned edition, a decorator included. A unit is checked code when its own syntax contains proposal syntax, or when it is nested in checked code. Discovery for a unit excludes nested units, including their parameters and return positions; a method's computed name and the decorators on a unit belong to the enclosing unit. The classification applies to the entire unit independently of source order, and it does not resolve or evaluate any type.

Direct eval code is checked code when the code containing the direct eval is checked code, as it inherits that code's strictness; otherwise it classifies its own source. Indirect eval code classifies its own source. A dynamically constructed function classifies its own combined parameter and body source and does not inherit its caller's classification.

Outside checked code, the judgments of 1.8.5.4 and 1.8.5.1 produce no type error, though the facts they establish still hold. A rule this specification attaches to a built-in, operator or declaration form of the pinned edition likewise produces no type error outside checked code, and keeps the behaviour of the pinned edition, including any error it throws when evaluated: the typed signatures of existing built-in methods, which also give their arguments no contextual type there; a store to a built-in accessor; the object operand of 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 set only where at least one of them is checked code, and an unannotated declaration then joins the set as a fallback.

A rule about a declared type, or about a binding or global this specification introduces, applies everywhere. A program written for the pinned edition cannot reach one: it contains no proposal syntax, and its own definitions of those names shadow the new globals.

Note

Checked code and strict mode code are classified alike but from different triggers. Strictness changes run-time semantics and follows annotations only (1.2.4); checked code adds type errors only and follows all proposal syntax. A unit using match without an annotation is checked code but not strict mode code.

1.2.6 The Static Type of an Expression

A resolved typed super property read participates in immutable-binding inference just as the corresponding typed 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, any and ordinary unannotated mutable bindings retain their existing treatment.

The Static Type of an expression is the Type Record the type checker assigns to it. It is a syntax-directed operation defined by cases over the expression grammar. Each later clause that adds or modifies an expression form gives the Static Type for that form; the cases established by this clause are those that do not depend on a feature introduced later, listed in Table 2.

Established evidence for an unannotated immutable String constant is preserved through an unannotated immutable copy, including parentheses and finite alias chains. Such evidence may determine a built-in concatenation's normal String result without creating an annotation-derived binding or return contract. Keep the value evidence distinct from the source-form judgment names a literal type: copying a literal name does not itself make the copy one. An explicit any view, mutable or reference alias, or unknown initializer supplies no such immutable-copy proof.

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 abrupt completion, while an unreached or abruptly failed initialization supplies no completed binding evidence.

An initialized immutable binding whose value is a Symbol supplies that Symbol's identity as computed-property-key evidence, independently of whether its readable contract is symbol or any. An immutable copy and an import retain the identity of the actual target value. An uninitialized binding, mutable target, reference binding or opaque object-environment lookup supplies no such evidence. This key evidence does not manufacture a binding contract or annotation-derived participation, evaluate an initializer, or read a reference's current referent. At a Module's required checking boundary, use its declaration environment and the exporting bindings that have actually initialized; linking alone supplies no value identity.

Where source analysis establishes that an immutable local declaration will receive a fresh Symbol, repeated visits to that declaration within the same checking unit retain one private key identity. Immutable aliases of that declaration retain that identity. Distinct declarations, including those with equal description text, remain distinct. A private checking identity is not an evaluated Symbol or a published binding value; it neither initializes the declaration nor substitutes for the actual Symbol when evaluation supplies it. Unknown initializers and shadowed intrinsic names supply no fresh-Symbol proof. Reconsider dependent computed-key judgments when the actual binding identity becomes available.

Apply those retained facts in the environment the source text uses: an ordinary Script and a dynamically constructed function use their target realm's global environment, direct eval uses the caller's lexical environment, and indirect eval uses its eval realm's global environment. A binding in a different realm or activation, a same-spelled property, or an opaque object-environment lookup supplies no substitute. Retained type or constant evidence does not change the temporal dead zone, ordinary evaluation order, or reference permissions and liveness.

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 typed function literal. A same-named parameter of another activation, an equal Type Object, or a previous check of the same source supplies no substitute. Retain only arguments available at their ordinary binding point; a parameter not yet bound does not borrow an argument from an older invocation. The source's own declarations and the literal's effective own parameters retain their ordinary shadowing precedence. A nearer ordinary binding blocks fallback even when uninitialized or unable to supply evidence, and an opaque object environment supplies no such proof. Obtaining this checking evidence performs no environment-method call or value read, and neither initializes a binding nor grants reference permissions or liveness. Direct eval uses the selected lexical binding; indirect eval and dynamically constructed functions do not inherit a caller's generic-parameter bindings through this rule. Ordinary reads and type denotation remain subject to the applicable lexical environment and availability rules.

Table 2: Static Type of Base Expression Forms
Expression formStatic Type
A numeric literalThe primitive type named "number".
A string literalThe primitive type named "string".
A typeof expressionThe union of the literal String types of the tags its operand's Static Type can produce, typeof being unchanged (1.12.1); every tag where the operand's type is any or a member's tag is not known.
true or falseThe primitive type named "boolean".
nullThe type named "null".
A reference to a bindingThe declared or inferred type of that binding, or the any type if the binding has none.
A type name or type expression in expression positionThe primitive type named "type".
new.targetThe any type.
new C Arguments where C names a classThe class's type (1.11.2); for a generic class, the nominal type of the specialization 4.9 binds, at the bindings that clause's ladder yields from the construction's contextual type, its arguments, and the declaration's defaults.

An OptionalExpression is typed along its live chain. At each optional boundary, remove 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 live result type with undefined. An entirely nullish base contributes only undefined; a known non-nullish base introduces no additional absent result; a result whose Static Type is unknown remains any. Parentheses end the chain: (o?.a).b is an ordinary access to the optional result, whereas o?.a.b keeps the latter access on the live path. Redundant optional access on a non-nullish base is permitted. Keys, arguments and nested expressions are still checked, and the ordinary argument-binding rules apply to optional calls.

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 1.8.5.4; computing its result type does not waive that error. The ordinary checks on keys, arguments, receiver requirements and nested expressions remain applicable.

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 TemplateStrings, or undefined for an invalid escape; it is not a lexical lookup of the name undefined. The position count and cooked literal facts participate in overload selection. The raw positions remain Strings. This judgment changes neither GetTemplateObject, the frozen arrays and site identity it provides, nor the syntax errors for untagged templates. A constructor call uses its resolved constructor signature, including explicit type arguments, before applying the same argument-binding judgments.

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 PropertySetParameterList bound in the setter's parameter scope, including binding patterns and the enclosing class's type parameters. Its parameter annotations, assignments and shadowing obey the ordinary parameter-binding judgments. The computed property name is checked before entering that scope.

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 type error if a selected member is readonly in any such alternative and the operation lacks that member's constructor or initializer permission (1.11.2). This applies to simple, compound and update assignments and to assignment targets in destructuring and iteration. Write permission is separate from assignability of the stored value. This rule does not freeze an object, deepen readonly, or change intersection member judgments.

A store through a union receiver or a statically established finite set of property keys must satisfy each reachable known destination's write type separately. This includes array and tuple elements, simple and compound assignments, destructuring and iteration targets, and stores through reference aliases whose origins are known. Fresh literals receive each destination's contextual type independently; a union of read types is not a replacement for these write obligations. A single location declared with a union type remains one destination. The same alternatives determine write permission and whether a member belongs to closed typed-class storage (1.7.10); open structural objects are not thereby closed. Unknown alternatives retain their runtime checks and do not erase separately known obligations. Key expressions are not evaluated by this judgment, and correlations are used only where independently 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 type error if a known alternative refuses the call; the signatures of distinct union alternatives do not form one overload set. When each selected return is known, the call's Static Type is their union. A contextual result requirement applies independently to each alternative. Unknown arguments and spreads retain the existing gradual checks; uncertainty about a spread's suffix does not erase a known incompatible prefix. Per-alternative checking does not select a runtime callee or transfer one alternative's inferred bindings to every callee. Function intersections are not governed by this union rule.

new.target is any DELIBERATELY, and the reason is worth stating so that a later reading does not take it for an omission. Its value is a constructor or undefined, and the constructor is not bounded by the enclosing function: 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 undefined - and a union with undefined cannot have its members read without narrowing, which 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.

Note 1

A union type here would refuse a program that ECMAScript admits, which is worse than saying nothing: any claims nothing and so cannot license an elision (1.7.9) that a value contradicts. Should narrowing ever extend to meta-properties - import.meta has the same shape - the union becomes both sound and usable, and this row should be revisited then. Widening any to a type is a compatible change; a wrong type is not.

Note 2

Reading a member of a UNION gives the union of that member's type across the union's members, and is a type error where 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 (1.8.5.3). A key EVERY member declares still reads without narrowing, which is what makes a discriminated union usable: the discriminant is readable, and reading it is what narrows.

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 1.7.10 - it yields undefined on every value the union admits, exactly as it does on a single object type, and if (u.maybe), typeof u.absent and _u_.absent === undefined 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.

Note 3

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 design writes this form throughout, and a pattern is where the annotation has nowhere else to go — there is no second place to say what 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 contribution of the declared property it selects, for String and Symbol keys alike. If every alternative of a union source declares that key, the contribution is the union of those read types, including undefined for optional members. An element annotation is checked against that contribution, and typed parameter patterns and participating 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 contribution from an unknown key or a possibly overwriting unknown spread.

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 iterator value, evaluate its default if selected, then perform the required admission before initializing the binding or proceeding to the next extraction. A failure preserves earlier effects and already initialized bindings, leaves that binding uninitialized, and follows ordinary iterator-closing rules where applicable. Substitute generic parameters in the same runtime context as the enclosing binding. Successful admission establishes the binding's contract for later uses, captured reads and retained binding evidence. This does not infer a contract for an otherwise gradual binding, deepen readonly, promote an already conforming plain aggregate into typed storage, or grant a reference borrow permission. Explicit annotations and reference bindings retain their existing separate rules.

Note 4

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 Static Type of a numeric literal given here is its type in the absence of a contextual type. Literal propagation, by which a literal takes the type its context requires, so that 0 in a uint8 position is a uint8, refines this and is defined by the conversions clause. The Static Types of the remaining expression forms, including operators, calls, array and object literals, and this, are given by the clauses that specify those forms; until a form is specified, its Static Type is the any type. An unannotated let or var binding has the any Static Type unless it uses a typed initializer. An unannotated const preserves a type supplied by a typed feature in its initializer: a typed construction, call result, conversion, binding, member or element read, or an expression composed from such contributions. Parentheses are transparent. An explicit annotation takes precedence, including any; an initializer whose Static Type is any leaves the binding dynamic. Literal-only untyped initializers do not acquire binding types under this rule. The preserved type describes the value held by the binding and does not make its fields or elements readonly. Initialization is a boundary at the inferred type, subject to the same stability conditions as an explicit annotation (1.7.9.1); immutability of the destination alone does not establish that its initializer produces a value of that type. Literal propagation nonetheless reaches a USE of an unannotated 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 21.3.1 participate as such a const does, since none of them can be written as a literal that denotes it.

1.2.7 Compile-Time Evaluation

Because a type is a value, a type annotation may be an expression that computes one (1.4.2.4); an expression whose computation can be carried out during type checking is compile-time evaluable. This clause defines when such an expression may be evaluated, when it is, what its evaluation may do, and what carries its result before it can run: the evaluability discipline, the deferred application that stands for a call whose generic arguments are not yet bound, the evaluation of a call with memoization and a fixpoint for recursion, and the budget that bounds the whole.

Evaluation is confined to the admitted compile-time-evaluable fragment. A top-level type-position expression required by pre-evaluation checking is evaluated at the source-checking boundary of 1.2.8. For a Module, that boundary follows the module evaluation order and dependency availability specified in 1.3.8; it is not a graph-wide requirement to finish computed type checking before any module body runs. A type-position expression inside a generic declaration evaluates at each specialization, once every generic parameter it reads is bound. A call may resolve through imports to a compile-time-evaluable function declaration. Resolving that call does not execute an imported module body on demand or admit reads of ambient mutable state; the evaluability restrictions below continue to apply.

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]] (8) and the hooks of a meta type (6), which the checker reads and calls at named seams; a forward-verified proposal is a trial candidate or a declared inverse (17), which binds nothing until forward evaluation and assignability confirm it; and a contract is checked at every evaluation before it is believed at any (16). The computed constraints of 1.4.3.4 are the measure of the boundary rather than an exception to it, since a constraint over T runs after T is bound and before the parameter it constrains is, which is after everything it reads.

Note

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 proposal admits only the first. It is stated so that later ergonomic pressure has something to push against, and the sentence to push against is the one this note follows, not the slogan.

1.2.7.1 IsCompileTimeEvaluable ( expr )

The abstract operation IsCompileTimeEvaluable takes argument expr (a Parse Node) and returns a Boolean. It determines whether expr can be evaluated during type checking, reading only values known at that time. It performs the following steps when called:

  1. If expr is a literal, return true.
  2. If expr is a reference to a binding, then
    1. Let binding be the binding to which expr resolves under the ordinary lexical and module binding rules.
    2. If binding is an indirect import binding, follow its target bindings to the binding in the exporting module and set binding to that binding. Use its declaration and initializer in the following steps.
    3. If binding is a parameter of the function under evaluation, or a local binding of that function, return true.
    4. If binding is a function declaration binding whose source text contains no assignment to that binding and no direct eval, and the declared function is compile-time evaluable, return true.
    5. If binding is immutable and its initializer is compile-time evaluable, return true.
    6. If binding is a value generic parameter or a type generic parameter, return true.
    7. If binding is an enumerator of the enum under evaluation that is declared before the enumerator whose initializer is being evaluated, return true.
    8. Return false.
  3. If expr is a function call, then
    1. Let callee be the expression that expr calls.
    2. Let args be the List of argument expressions of expr.
    3. If IsCompileTimeEvaluable(callee) is false, return false.
    4. If callee does not denote a compile-time-evaluable function, return false.
    5. For each element arg of args, do
      1. If IsCompileTimeEvaluable(arg) is false, return false.
    6. Return true.
  4. If every operand of expr is compile-time evaluable and the result of expr's operator is determined by its operands, return true.
  5. Return false.

A compile-time-evaluable function is one whose body reads only its parameters, its own local bindings, immutable bindings whose initializers are compile-time evaluable, and other compile-time-evaluable functions. It may create local state and mutate it. Its body contains no await, no yield, no direct or indirect eval, no use of the Function constructor, and no construction of a Proxy; regular expressions are within the fragment. It performs no input or output and reads no ambient mutable state, none of which it can name under the reading rule above, which is why the discipline is a property of what the code can name rather than a wall around what it does.

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 Static Type is not the value of a binding: a function signature must not stand in for the function, nor may a namespace's structural type stand in for the namespace value. These rules neither initialize an unavailable binding nor execute a dependency body; availability and failure timing remain those of 1.3.8.

Note 1

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 compile-time evaluable remains an ordinary function in expression position; only its use in type position is restricted. Local mutation is admitted because an accumulator array or a visited 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 1.2.7.3 is the supported mechanism and is observably equivalent. Fixing the fragment's grammar and semantics as a normative implementation target in its own right, with a conformance suite, is A; the discipline above is the boundary it pins.

Note 2

A preprocessor module and every export of it used as a replacement decorator are required to be evaluable, for the reason a type builder is: both are user code an implementation runs while compiling, and the discipline is what makes the run deterministic. There is one notion here, not two.

1.2.7.2 Deferred Applications

A call whose arguments involve an unbound generic parameter cannot evaluate yet, and is carried as a type: the deferred Type Record of Table 1. The deferred application Type Record of a ComputedType is the deferred Type Record whose [[Operator]] is the function its callee resolves to and whose [[Operands]] holds, for each argument in order, an element that is: the TypeParameter Parse Node where the argument is a reference to an unbound generic parameter; the deferred Type Record where the argument is itself a ComputedType reading one; and otherwise the argument's compile-time value, a Type Object contributing its [[TypeRecord]].

Identity is by that key. SameTypeWithAssumptions compares two deferred Records by operator identity and SameArgumentList, and Type Objects are interned, so two mentions of partial(T) in one generic body are one Type Object before anything is known about T. At specialization, each TypeParameter Parse Node among the [[Operands]] is replaced by the parameter's binding, and the application evaluates by 1.2.7.3.

Two compound compile-time constants, arrays or plain objects of compile-time constants, are structurally equivalent when they have the same shape and their leaves are pairwise SameValue, a Type Object comparing by identity and a RegExp by its 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.

Note

An unbound parameter is represented by its declaring Parse Node rather than by a Type Record kind of its own, because a parameter is never a type by itself anywhere this specification currently reaches; what a bare parameter annotation denotes inside a generic body belongs to the generics work, and the hook for it is recorded in Table 39. Structural equivalence for compound constants is the value semantics the Composites proposal gives tuples and records; it is stated directly here so that the rule does not depend on that proposal, and 25, which specifies composites for this design, coincides with it by construction.

1.2.7.3 EvaluateBuilderCall ( f, args )

The abstract operation EvaluateBuilderCall takes arguments f (a compile-time-evaluable function) and args (a List) and returns either a normal completion containing a Type Object or a throw completion. It evaluates one type-level call, with memoization and a fixpoint for recursion, and checks the contracts of 16 against its result. It performs the following steps when called:

  1. Let key be CanonicalizeType(the Type Record { [[Kind]]: deferred, [[Operator]]: f, [[Operands]]: args }).
  2. If the evaluation memo contains an entry for key, return a normal completion containing that entry's Type Object and retaining its type-result context.
  3. If the in-flight List contains key, then
    1. If no placeholder exists for key, let the placeholder for key be a fresh Type Object, created without interning and distinguished from every other value.
    2. Return the placeholder for key.
  4. Append key to the in-flight List.
  5. Let result be Completion(the evaluation of the body of f with the elements of args as its arguments under the semantics of this clause).
  6. Remove key from the in-flight List.
  7. Let value be ? result.
  8. If value is not a Type Object, throw a TypeError exception.
  9. Let context be the type-result context retained by result, or none if it retains none.
  10. If a placeholder exists for key, then
    1. If value is the placeholder for key, throw a TypeError exception.
    2. Let final be the Type Record obtained from value.[[TypeRecord]] by replacing each position that holds the placeholder with a reference back to the type being defined, forming a cyclic Type Record.
    3. If no replaced position is a reference position, throw a TypeError exception.
    4. If context is not none, set context to the Type Record obtained from context by the corresponding placeholder replacement, preserving its binder declarations and captured inputs.
    5. Set value to GetTypeObject(final).
  11. Record value and context in the evaluation memo for key.
  12. Return a normal completion containing value and retaining context.

The evaluation memo is a specification-level map from canonical deferred Type Records to successful results, retaining both their Type Objects and their type-result contexts (1.6.5), and the in-flight List tracks the keys under evaluation. Each distinct key evaluates once per program; every later occurrence, in any module, yields the same interned Type Object and selected context. Reuse requires the same determining declaration and captured inputs; equal result Type Objects alone do not identify a reusable context. An implementation may realize both devices in any way that yields these results. An in-flight placeholder supplies no completed context. When fixpoint completion resolves placeholder edges in the result, resolve corresponding edges in its retained context consistently, preserving its binder declarations and captured inputs; no incomplete context is published on failure.

Evaluation of the body of f uses f's ordinary function environment and the applicable parameter binding, generic specialization, receiver and return contracts. Permission to evaluate a function during checking does not remove any of these contracts. An imported name that denotes f denotes that same function for this operation; importing it does not instantiate a new function in the importer's environment. A failed contract or a result that is not a Type Object fails the containing type computation under 1.2.8. Reuse of a memoized result must preserve these requirements and the accounting rules of 1.2.7.5.

Note

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 (1.8.1). The two throws distinguish the two failures: a call whose entire result is its own placeholder learnt nothing and is unproductive recursion, and a cycle resolved at a position that embeds the layout inline would demand an infinite layout. A recursion that never repeats a key, a genuinely infinite family, is not caught here; it runs into the budget of 1.2.7.4. This mechanism is the computational twin of the coinductive assumption Lists that SameTypeWithAssumptions and IsSubtype already carry.

1.2.7.4 The Evaluation Budget

Evaluation under this clause is metered by two host-defined limits: a count of evaluation steps and a count of type-construction requests, each per top-level type-position evaluation. Every operation that evaluates a type is metered, the instantiation of a generic type alias among them, since an unmetered one is a way to recurse without spending the budget; so is BindTypeArguments (4.3), and with it the specialization of a declaration over a variadic parameter, since a declaration applying itself to a pack one element longer is the recursion a type alias performs with one more field. For a given source text, two hosts either produce, position for position, the same interned types, or at least one reports a type error: the limits decide whether compilation fails, never what it produces.

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 (4.13), because truncating a search under a budget yields a wrong answer where truncating a computation yields none.

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.

Note 1

This bears on any floor a host is asked to meet. A step count in the millions bounds a computation that widens as it runs, and bounds nothing about one that RECURSES: a self-referential type alias reaches an implementation's native stack in thousands of steps, not millions, so an implementation meeting a high floor and reporting nothing has satisfied the number and failed the requirement. The requirement is that exhaustion be reported, and a host chooses limits low enough that it is.

Lowering the STEP limit is the wrong remedy, and a host is not asked to. A step count low enough to stop a recursion would stop a wide computation that was going to finish, and the two failures are unrelated: one exhausts a stack, the other a clock. A host that meters NESTING separately — a third count, of type-position evaluations open at once — bounds the resource that is actually scarce and leaves the step floor where it is. The value is not a floor this specification fixes, because the depth a native stack affords differs between implementations and between the constructs a recursion passes through: a recursion through an array or a function type walks further per level than one through an object member, so a host measures its own worst case rather than trusting a shared number.

Exhaustion is not an abrupt completion the evaluated code can observe. No try statement within the evaluation handles it; the evaluation is abandoned, and EvaluateToTypeObject of the containing type-position expression returns a normal completion containing empty, with a diagnostic naming the outermost call and the deepest frames. The containing source check reports a StaticTypeError for evaluation-limit exhaustion. An incidental hook exception or lack of a result after exhaustion does not instead establish an ordinary type mismatch, select an overload fallback, or supply an unknown type.

Note 2

"Not an abrupt completion the evaluated code can observe" is a requirement on how exhaustion is REPRESENTED, not only on what it means. An implementation that reports exhaustion by throwing gives a 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.

Note 3

Making exhaustion catchable inside evaluation would let a program's types depend on the host's limits: the same source would elaborate differently under a large budget and a small one, and the memo would cache whichever answer came first, which is nondeterminism through the error channel. Graceful degradation therefore has to be deterministic and structural: a builder takes an explicit cap as an argument and computes the identical type on every host, rather than falling back when fuel runs out. Bounding a deliberately Turing-complete type language by limits has precedent; the magnitudes are fixed here as floors a host meets or exceeds: ten million evaluation steps and one million type-construction requests per top-level evaluation, ten thousand automaton states for the pattern decision procedure, and sixty-four specializations for a trial, calibrated against the erased-system limits the design cites. A host exceeding a floor can only turn a failure into a result, never a result into a different one, so the invariant above is undisturbed.

1.2.7.5 Type Evaluation Accounting Boundaries

The type computation includes propagating retained specialization obligations through declarations: substituting the determining bindings, admitting arguments, and retaining the constraints, predicates, defaults and consumer contracts that an expanded result may erase. These operations are metered when performed during static checking as well as during pre-evaluation; the absence of a builder invocation or a Type Object allocation does not exempt them. Nested propagation shares the enclosing computation's resource state, and an implementation that meters nesting accounts for these logical dependencies even when it processes them with a worklist. This accounting does not require evaluating a default or other expression whose evaluation is deferred by an existing generic rule.

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 1.2.7.4, that limit includes ordinary ECMAScript function-body invocations made during the computation, including mutual recursion and calls through callbacks; tail-call optimization does not remove a logical invocation from this accounting. Suspension retains that state; evaluation by another agent has a distinct state. Evaluating ordinary program code outside such a computation does not consume its allowance.

A type-construction request is an invocation of GetTypeObject while type evaluation is active. Charge one request on entry, before canonicalization, lookup, or allocation, whether the result is already interned or requires a new Type Object. Reading an existing Type Object without invoking this operation is not a construction request. Physical allocation, garbage collection, interning order, and a previously populated intern table do not determine this charge. A tuple candidate enumerated by trial specialization consumes the additional request specified in 17.2, even if that candidate has no materialized Type Object.

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 published as a successful specialization, inverse proposal, resolved type, or successful evaluation-memo entry.

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 1.2.7.4. An implementation failure of its own stack or heap is not evidence that a language type is invalid and must not be translated into an ordinary type-mismatch proof.

Note

These rules distinguish logical requests from physical allocation and define containment of exhaustion. The host-defined limits and the remaining evaluation-step accounting in 1.2.7.4 still permit acceptance differences. This clause does not by itself establish a portable total fuel model or authorize a host to report native failure as semantic exhaustion.

1.2.7.6 EvaluateToTypeObject ( expr )

The abstract operation EvaluateToTypeObject takes argument expr (a Parse Node) and returns a normal completion containing either a Type Object or empty. It evaluates expr, which appears in type position, to the Type Object it denotes, or defers it, or returns empty if it does not evaluate to one. It performs the following steps when called:

  1. If IsCompileTimeEvaluable(expr) is false, return empty.
  2. If expr is a ComputedType and expr reads a generic parameter that is not bound, return GetTypeObject(the deferred application Type Record of expr (1.2.7.2)).
  3. Let result be Completion(the evaluation of expr during type checking in which each call of a compile-time-evaluable function is performed by EvaluateBuilderCall).
  4. If result is an abrupt completion, return empty.
  5. Let value be result.[[Value]].
  6. If value is not a Type Object, return empty.
  7. Return result, retaining its type-result context for the containing type judgment.

A successful result retains its type-result context as auxiliary checking evidence for this evaluation occurrence, not as a property of its Type Object or shared source node. An unavailable generic input supplies only the deferred type until its ordinary binding point; failure supplies no context. These requirements do not change the Type Object denoted by the expression.

It is a type error if an expression appears in type position and the value in the normal completion returned by EvaluateToTypeObject is empty (1.2.8). When the empty arises from an abrupt completion whose value is an Error, the type error's diagnostic carries that Error's message, prefixed by the source text of the type-position expression; when it arises from the budget, the diagnostic is the one 1.2.7.4 describes. Diagnostic content is not observable to the evaluated program.

Note

Evaluation during type checking uses the ordinary evaluation semantics of the operators and calls involved, restricted to the compile-time-evaluable fragment, so no separate evaluator is defined. Because enum and sealed-class exhaustiveness is checked, a switch used to map a value to a type covers every case its argument can take and therefore yields a Type Object rather than undefined. A thrown exception becoming the diagnostic is the design's authored-error story: a builder states its own misuse message, which an erased type language cannot.

1.2.8 Type Errors

Type errors fall into three families (1.2.5). A judgment that reads a type inferred from code, and a rule attached to a built-in, operator or declaration form of the pinned edition, apply only in checked code. A rule about a declared type, or about a binding or global this specification introduces, applies everywhere.

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 iterable for an iterator entry hook, the iterator for next or return, the original object for primitive conversion and the thenable for promise assimilation. The ordinary method receiver marker denotes that receiver and is not an unrelated nominal requirement. A failure proof combines receiver, argument and result failures per signature and per reached alternative; an unknown or viable alternative prevents that proof. Preserve generic substitution, nullish fallback, skipped stages, iterator closing and completion precedence. The checking pass invokes none of these methods.

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; any, named object spreads and unknown iteration counts retain the ordinary call rules.

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 compile-time evaluable, and the like. The clauses that follow state such a violation with the phrase “it is a type error if”.

A constructor reached through a stable lexical value origin retains its argument contracts, including named, optional, reference and generic parameters. A conditional constructor target is checked against each established alternative independently; it is not an overload set. Shadowing, explicit any, reassignment, decorators or an unresolved origin prevent transporting a contract without further evidence. No constructor or getter is executed to recover an origin.

A type error that is determinable before a source text runs is an Early Error: a source text that contains one is rejected rather than evaluated. Detection is the work of the checking pass, the logical source check specified here and in 1.3. An implementation may divide this work into several physical traversals or reuse validated results when it preserves the required judgments, applicability, failure timing and effects. The purely syntactic type rules are applied together with the other Early Errors at parse; the rules that evaluate — a computed type, a builder call, a hook of the metadata protocol (6) — are applied by the checking pass, under the discipline and budget of 1.2.7. For a Script the pass runs when evaluation of the script would begin, before the first step of its body; for a Module it runs at the per-module boundary of 1.3.8, before that module's own body and after the dependency prerequisites required by the ordinary module evaluation algorithms. A cycle does not require every dependency body to have completed. The source text of an eval is a Script and is checked as one. The source text CreateDynamicFunction builds is checked as one unit, in the global scope where that function is created, at the point where CreateDynamicFunction enforces its other Early Errors, and a type error is thrown there as they are. Before applying any judgment that consults them, the pass processes the source text's own type declarations — its type aliases, its interfaces, its meta declarations, the implicit cast operators of its primitive blocks (6.5), and the inverse decorations of its compile-time-evaluable functions (17.3) — in source order, which is what makes a meta type declared in a source text govern a judgment in the same source text; a declaration the pass has processed is not processed again when evaluation reaches its position. Type alias and interface declarations inside export wrappers participate under 1.3.7.

A check that cannot be resolved statically, because a value reaches a typed position only as the any type or because the nominal ancestry evidence required by 1.2.8.2 is unavailable, is deferred to run time. 1.7 specifies where these deferred checks are inserted and what they throw: a TypeError when a value is not of the required type, and a RangeError when a numeric value is outside the range of its type.

Note

The design documents sometimes describe a statically-determinable violation, such as assigning a uint32 to a uint8 binding, as a “TypeError”. This specification realizes such a violation as an Early Error, and reserves a thrown TypeError for the any boundary and other genuinely dynamic checks. The distinction is observable: an Early Error rejects the source text before any of it runs, whereas a thrown TypeError occurs during evaluation. Treating a statically-determinable type violation as a whole-source rejection matches a strict, check-before-run type system; whether some violations should instead be deferred to a thrown error at the offending operation was a decision left open when this clause was first written and is now settled, in this direction, for the reason that follows: the compile-time evaluation phase (1.2.7) already commits every implementation to the per-module checking pass this clause now names, so the cost that once made deferral attractive is sunk, and a diagnostic at the declaration is strictly better than the same diagnostic at first execution. For a Module judgment requiring type evaluation, rejection is observable at its pre-body checking boundary after the ordinary dependency prerequisites. Parsing and linking retain their earlier judgments, and cycles retain the availability rules of 1.3.8.

1.2.8.1 Source-Position Class Constructor Evidence

A source class declaration's type denotation and the current constructor value of an ordinary expression are distinct, as specified in 4.8.1. When a checking judgment uses a constructor value's origin, determine the evidence at that expression's evaluation position. A write after a heritage read does not retarget the superclass already selected, and a write during argument evaluation does not replace the constructor or callee already obtained. These rules describe checking evidence; ordinary Evaluation still obtains and uses the actual values once, in its specified order.

The prescribed transparent cases are class creation, initialized unannotated lexical constructor aliases, parentheses, direct identifier assignment, comma evaluation, and conditional or short-circuit selection. Track lexical binding identity, including ordinary shadows and the class's separate inner self binding. An established initialization or assignment transfers its constructor origin to subsequent reads of that binding; a later assignment withdraws or replaces that evidence for subsequent reads only. Preserve any established generic application and its original argument/declaration context. A mutable alias can therefore supply an origin at a particular read without becoming immutable or becoming valid in a type position. An explicit erased or reference view does not acquire a constructor-origin proof by this rule.

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 captured by an earlier component independently of subsequent state. Check required source forms on skipped or non-completing paths under their existing rules; this is not permission to execute them or to transfer their effects onto another path.

A pattern or iteration write withdraws the origin of each destination binding unless its replacement origin is independently established. A write to a different shadowing binding does not withdraw an outer binding's fact. Account for a loop's possible previous iterations before using a value-origin fact in its body; widen the bindings and dependencies its repeated operations may affect. A catch can observe effects performed before an abrupt completion, and a finally clause retains its ordinary completion ordering. Where a construct has no prescribed transfer establishing an origin, leave that origin unresolved rather than using a syntactic declaration as a substitute.

Potential calls, getters, setters, conversions, iterators, decorator applications and suspension withdraw mutable evidence they may affect. This includes reachable Script-global bindings, imported producer dependencies and bindings writable through captured code or exposed references. Preserve independent immutable captures and local cells that the operation cannot reach. A potentially direct eval additionally affects its visible mutable bindings; in non-strict code, account for declaration injection that can change lookup of an outer name. A bound name spelled eval may still invoke the intrinsic direct eval. Indirect eval and dynamically constructed functions have their ordinary global environment; their spelling alone neither proves purity nor gives them access to an unexposed local cell. Do not parse or execute eval text during this judgment.

Defining a callable does not invoke its body or parameter defaults. Analyze its possible invocation with only the captured facts that remain valid at that entry; perform its default-initializer effects before its body facts. A class evaluates heritage before computed names and static initialization, and its instance initializers and methods run in their separate invocation contexts. A generic application can initialize a specialization, so its permitted effects precede later ordinary reads. Ordinary class decoration retains the exact or bounded replacement-origin rules of 1.11.2, including on exports.

A mutable Script-global constructor read in a deferred body does not capture the constructor present when that body was defined. For established ordinary invocation sites in the checked source, retain a constructor candidate only when the prescribed effect analysis establishes that binding's stability at the actual read, including argument evaluation, parameter defaults and possible repeated activations. This evidence applies to those invocation contexts; it is not a guarantee about future callers. With no established invocation context, the absence of a write in the current source is insufficient: a later Script can replace the global constructor before the body's first invocation. An independent immutable value capture and a class's inner self binding retain their separate origin rules. A property of the global object is not the same binding as a same-spelled Script-global lexical declaration.

Calling a generator creates an iterator and does not establish when its body reads a mutable global constructor. Do not project such a read, or a helper invocation reached from that body, to iterator creation as evidence of binding stability. A suspension retains its effect boundary even when the initial invocation site is known. Unknown invocation or resumption contexts and exhausted evidence withdraw the conditional origin; they do not freeze a global binding or change the constructor denoted by an ordinary expression.

An established violation follows the existing type-error boundary, including a receiving, inherited-member or constructor-signature violation now established through a source-position alias. Unknown effects or ancestry retain runtime admission under 1.2.8.2. This analysis supplies no independent permission to elide checking, to replay an operand after guard failure, or to alter prototype obligations, conversion ordering, storage identity or reference liveness. Optional deeper analysis must not introduce additional mandatory Early Errors or move an inner dynamic-source error before its evaluation.

Example (Informative)
class Original {}
class Other {}
let Selected = Original;
Selected = Other;
const value: Original = new Selected(); // established mismatch: Early Error

Conversely, an immutable alias captured before the assignment continues to denote the constructor it received. A later unknown operation that can affect a mutable binding withdraws that binding's current-value evidence without changing the declaration contract.

1.2.8.2 Incomplete Nominal Ancestry at Receiving Boundaries

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 null heritage, ends that chain. An unresolved heritage expression does not. Failure to find the target along a chain whose relevant ancestry is unavailable does not establish a type error.

This is a rule about the evidence supporting a receiving judgment, not a change to SameType, IsSubtype or IsAssignable. Retain the source and target contracts, known own member contracts and established declaration identities. Do not replace them with any. A known incompatible argument or identity of an already established application of the target declaration remains incompatible; unavailable ancestry cannot erase that fact. Nor does it erase an independent primitive-category, storage or type-formation error. The existing rules for conversions still apply.

For this judgment, consider source union members independently: a member established to be incompatible still requires the existing type error, while unavailable ancestry in another member supplies no positive proof. A target union admits an established compatible alternative; where none is established compatible but one remains possible because of unavailable ancestry, retain the runtime boundary. A target intersection requires every conjunct; an established incompatible conjunct is not repaired by an unresolved one.

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 capture, argument and constructor effects, their order, and abrupt completions. Do not execute a constructor, getter, initializer or eval source during checking to recover this evidence. Type computations explicitly permitted by 1.2.7 retain their own rules.

Unavailable ancestry never licenses check elision, overload selection or a positive subtype proof. Successful runtime admission retains the ordinary nominal prototype obligations of 1.7.7.2 and all existing storage and reference-liveness checks. A failed admission occurs at the receiving boundary, after the effects that ordinary evaluation has already performed. This clause does not change which declaration a type name denotes.

Example (Informative)

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");

1.2.8.3 Proved Library Operations

A proved Map.groupBy result retains the established callback key contract and the source iteration contribution as the element contract of each fresh group array. A proved Object.groupBy result retains the same group contract and the callback result after ToPropertyKey: Symbol keys remain Symbols, while established non-Symbol primitive keys become Strings. These are facts about fresh results, not invariant type stamps placed on ordinary collections. Check a receiving annotation using its normal adoption and conversion rules. No non-empty group is inferred from an empty or unknown source, and unknown callback, key-conversion or iteration effects retain the runtime boundary.

At proved intrinsic String.prototype.replace and replaceAll operations, a callable replacement is checked against each established invocation's actual argument list and undefined receiver. A string search supplies the matched String, its numeric offset and the entire input String; a RegExp or another replacement protocol may supply a different list and requires its own proof. Use ordinary parameter conversion, omission, defaults and rest binding. A proved absence of matches establishes no callback invocation. A custom @@replace method, a replacement method or unknown lookup effects do not inherit the intrinsic contract.

At an established implicit ToString stage of a proved library operation, a participating operand necessarily containing only Symbols is a type error. At an established implicit ToNumber stage of a proved library operation, a participating operand necessarily containing only Symbols, or only BigInts, is likewise a type error: the Math functions and Number convert their arguments with ToNumber, which throws for both, so a Symbol or BigInt given to 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 ToString has the stage, among them the search operands of 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 ToString with a String-only parameter requirement: numeric and other ordinarily convertible primitives remain admitted. The explicit String function's Symbol conversion is unchanged. A proved Object.is is a test whose answer the types may already know: it is a type error where a participating operand's type cannot hold the other operand's value, as a search with a needle its element type cannot hold is (1.14.10, 1.15.4.1). A proved Array.isArray declares a narrowing of its argument (8.2) to the ~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 false for any other value as its existing signature does. Classify the established argument-domain members independently by each proved intrinsic predicate's answer: a true member survives only on the true edge, a false member only on the false edge, and an unknown member on both. In particular, a Number-valued floating domain may still answer false to Number.isInteger. Publish an argument fact only to its preserved captured origin; lexical shadowing or replacement of the intrinsic defeats its origin proof. A test of either is therefore judged by 1.8.5.4. A proved Object.is supplies only SameValue-compatible branch evidence under 1.8.5.1. It is not rewritten as strict equality: zero signs and decimal cohort identity retain their distinct semantics. Unknown earlier protocol effects retain runtime checks.

At a proved typed array or window set operation, read the source as an array-like object, not as an iterable. Establish its length, the converted offset, the destination bounds and the reached indexed reads before checking a copied value against the corresponding destination storage type. An incompatible established store is a type error under the same rules as a direct typed store. An empty copy creates no element obligation; an earlier range failure does not establish a store. Overlap, conversion order, source getters, range errors and reference liveness retain their runtime semantics.

At proved Object.create or Object.setPrototypeOf, a participating prototype argument necessarily outside Object or null is a type error. Preserve Object.setPrototypeOf's preceding RequireObjectCoercible stage and its ordinary handling of non-nullish primitive targets. This is a prototype-argument judgment, not structural adoption or a requirement that the target itself be an Object.

A proved synchronous 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 undefined mapper, use the source's established iteration contribution. Check a receiving type against that result at the ordinary conversion boundary. Do not infer elements from mutable indexed storage, infer an incompatible element from an empty source, or turn initial result length into fixed storage extent. An unknown callback result or lost intrinsic proof retains the runtime boundary. This rule introduces no synchronous result contract for 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 type error. Generic statics such as 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 type error when a participating input's established type cannot satisfy the synchronous iteration protocol the operation requires. Ordinary iterable values are admitted; elements need not themselves be Promises. Preserve every viable or unknown alternative. Where no early proof applies, the original rejection and completion behaviour remains in force.

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 undefined Array mapper selects identity mapping; a grouping callback is required. A known empty source does not establish an invocation with an incompatible element, although the operation still checks callability. Use the selected operation's own await stages: 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 ordered positions; unknown iteration or callback alternatives retain runtime checks.

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 constructor, whose instance fields take scalar initializers or primitive defaults, constructed with scalar arguments. A runtime identity guard cannot justify an Early Error. Unknown dependencies retain runtime checks; checking invokes no ordinary getter, setter, conversion, callback or thread body. Permitted compile-time type evaluation remains governed by its existing rules.

It is a type error to perform an established write to an accessor with no setter, such as the original typed-array 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 replacement setter. Compound assignments and updates use the same judgment. A logical assignment whose store is skipped performs no write and is not rejected by this rule. Capacity reservation, storage relocation and reference liveness are unchanged.

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 2.3; forwarding does not recreate references. Overload and generic argument binding retain their ordinary rules.

At a proved typed Atomics operation, it is a type error if the known target category cannot satisfy 43.6's operation-specific restriction, or an established operand boundary necessarily fails its prescribed conversion. Integer-only operations remain distinct from operations admitting floating-point targets; shared is not an eligibility requirement. The first reference argument retains the Atomics exception to built-in decay. A conditional replacement conversion, such as the replacement of 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 43.3.2 to established facts, then check the forwarded inputs at the target's callable contract. A non-Object first argument is not an options bag. A first parameter admitting an Object takes precedence over bag classification. If several classifications remain possible, reject only when every relevant mapping fails; otherwise retain the runtime classification. Built-in reference decay remains in force. This judgment launches no work and changes neither Promise settlement nor cancellation.

At a proved Object.assign, Reflect.set, Object.defineProperty, Reflect.defineProperty or Object.defineProperties, an established write to typed own data obeys the ordinary storage boundary. It is a type error when that write necessarily fails. The proof must establish ownness, enumerability, keys, descriptors and the actual receiver as needed by the selected helper. For example, the original 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 assumed to be the target. Unknown getters, proxies, descriptors and explicit any remain dynamic. Reflection gains no ability to replace protected typed storage or extend a reference's lifetime.

Descriptor adapters preserve the selected operation's staging. Reflect.defineProperty does not bypass a typed storage conversion because it returns a Boolean. 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.

1.3 Static Type Checking

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.

1.3.1 Static Semantics: Checked Unit Ownership

The checked unit of a Parse Node is its innermost owning Script, Module, function-like unit, or class, with the ownership exceptions in the following table. Ownership includes parameter and return positions. A function-like unit is a function declaration or expression, generator declaration or expression, async function declaration or expression, async generator declaration or expression, arrow function, async arrow function, method (including a getter, setter, generator method, async method, or async generator method), or operator definition (including a signature without a body). A class declaration or expression owns its heritage, modifiers, fields, static initialization and other class syntax, excluding the own syntax of its methods and other nested units.

Table 3: Checked unit ownership
SyntaxOwner
A unit's parameters, return position, and own bodyThat unit.
A method's computed nameThe unit enclosing the method.
Decorators on a unitThe unit enclosing the decorated unit.
A nested unit's parameters, return position, and own bodyThe nested unit; they do not activate its enclosing unit.
Other syntaxThe innermost enclosing unit, following the preceding exceptions.

To classify a parsed source, first determine every node's owner and whether each unit owns any proposal syntax. Proposal syntax is determined by the added grammar alternatives, including alternatives added to existing productions; it is not determined by a list of implementation AST class names. Next, in outer-to-inner order, mark a unit checked if it owns proposal syntax or its enclosing unit is checked. The Script of a direct eval whose caller is checked is marked checked before this second operation. Indirect eval and dynamically constructed functions receive no such inherited mark. The combined parameter and body source of a dynamically constructed function has one owning function unit even if an implementation parses its fragments separately.

The classification does not evaluate annotations, decorators, computed keys, or any other expression. Moving proposal syntax within its owning unit cannot change that unit's classification. Strictness is a separate classification; neither strictness nor a typed import activates checking by itself. Classification and ownership may be cached. A cache must include inherited direct-eval classification and must not reuse a result for a different source or ownership graph.

1.3.2 Type Rule Applicability

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 unit. A deferred obligation retains that ownership when type evaluation resolves its inputs; the unit containing the resolution machinery is irrelevant. A synthesized node retains the source obligation it represents.

The following precedence resolves the three families in 1.2.8. A rule enforcing a declared type or a contract on a proposal-introduced binding applies everywhere. This remains so when its source value was inferred, when an existing built-in forwards the value to typed storage, or when the contract is reached through an established import or alias. Otherwise, an added rule on an existing built-in, operator or declaration form applies only in checked code. An inference-based impossibility or flow diagnostic also applies only in checked code. Ordinary ECMAScript Syntax Errors and runtime errors are unaffected.

A diagnostic family can describe several obligations with different applicability. For example, an inadmissible constructor can be an existing JavaScript value used with new, or a Type Object introduced by this proposal that supplies conversion but no construction. The first added static rejection is checked-only; the latter contract applies everywhere. The rule and contract origin determine applicability, never a diagnostic's English wording or its family code.

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 checked code, a union alternative that fails only the added callability rule cannot contribute to that latter proof. No hook or getter is executed to classify the obligation.

Applicability does not erase analysis. Outside checked code, the transfers of 1.8.5.1 still establish their specified facts and distinguish impossible predecessors. They withhold only an inapplicable diagnosis. An empty meet is still empty, including when established by metadata evaluation. An unresolved or failed optional knowledge-producing hook follows its own specified fallback; it must not be confused with a successful empty result.

Existing built-in contextual signatures and overload formation require earlier applicability decisions. Outside checked code the added existing built-in signature supplies no contextual argument type. A declared callable or typed storage obligation still supplies its declared context. Declarations form an overload set under exactly the checked-declaration condition of 1.2.5. Suppressing a final message cannot undo a contextual conversion or an overload set that should never have been formed.

1.3.3 Checking State and Evidence

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 path includes the captured receiver origin and the established keys; the same spelling on another receiver is a different path.

Table 4: State components
ComponentMeaning and invariant
Declaration environmentThe 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 factsThe domains, exclusions, numeric categories, and value relations established by the specified transfers. Missing evidence is distinct from the declared type any, the type never, and an impossible control-flow predecessor.
Origin factsThe captured values, established data paths and intrinsic or declaration identities admitted by the origin rules. A saved result describes the value that was captured, not a subsequently replaced binding.
Reference factsThe location, permissions and liveness facts admitted by 2.10. A value relation supplies no reference and extends no lifetime.
Completion alternativesNormal, 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 captured value that the existing capture rules preserve.

The checker must use the defined finite domains and transfers. It does not solve arbitrary predicates, assume purity from a return annotation, or enumerate possible runtime objects. A recursive type judgment uses its specified assumptions or cycle rule. A transfer for which this specification supplies no value proof preserves the applicable declared contracts and treats the missing value evidence as unknown; that absence cannot justify an impossibility rejection.

1.3.4 Recursive Declaration Validation

The cycle rules of 1.8.1 and 9.4 apply to a closed declaration independently of whether execution reaches a use of it. A bare alias cycle denotes no type. A cycle requiring infinite inline layout is invalid. A cycle passing through the reference positions admitted by those rules remains eligible; the existence of a cycle alone is not a rejection predicate.

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 specialization rules; a resource limit is not a substitute for this structural judgment.

1.3.5 Declaration Preparation State

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, realm, agent, or earlier source-checking invocation is not a member of the group.

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 1.3.4, default-value requirements, and compile-time evaluability. A temporary unresolved reference supplies no fallback language type, successful member contract, or narrowing fact. A runtime-dependent type expression retains its specified runtime behaviour; failure to prepare it does not by itself turn its eventual runtime exception into an Early Error.

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, abrupt completion, resource exhaustion, and host interruption. Reusing a parsed source or a type cache must not reuse an earlier invocation's temporary membership evidence.

This state creates no program reference, extends no reference lifetime, and adds no observable property to an exception or Type Object. Equivalent internal representations are permitted. Discarding temporary preparation evidence does not itself undo declaration instantiation or observable effects; those remain governed by their respective evaluation algorithms.

1.3.6 Recursive Interface Resolution

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.

  1. Resolve a type reference under the ordinary lexical binding rules. If it refers to an already initialized binding, use that binding's type value under the existing rules. Otherwise, only a binding identified with an actual interface declaration is eligible for declaration-based interface formation. An unrelated uninitialized value, a shadowing parameter, an unresolvable name or a thrown lookalike supplies no such declaration.
  2. Identify the declaration, its binding in the current lexical activation, and the generic arguments established by the ordinary binding rules. Retain the interface declaration identity specified in 1.11.7.1 in every node and application for that binding. If formation of that declaration is already in progress for this activation, retain a reference to the corresponding node of the type graph. Otherwise, begin its formation in the declaration's environment. Resolving an outer declaration from an inner scope must not substitute the inner scope's same-spelled captures.
  3. Form the interface's complete structural contract, retaining recursive edges in property types, method and call signatures, index signatures and compound types. Generic applications retain their bound arguments; a reference to one specialization does not become a reference to the unspecialized declaration or another specialization. Complete the recursive graph before using it as a successful type result. An unfinished edge is not the type any, an unconstrained object, an absent property, or a function with no signatures.
  4. Apply the existing declaration, recursive-validity, evaluability and specialization judgments to the resulting graph. A recursive reference is evidence of graph identity, not permission to ignore a member obligation. Subsequent membership, assignability, reflection and typed boundaries consult the completed contract.
  5. On success, expose the completed type through the existing declaration and Type Object rules. On failure, propagate the applicable completion and discard temporary formation evidence. Restore the prior lexical and generic environments on every exit, including abrupt completion, resource exhaustion and host interruption. Evidence from a failed formation, another activation, realm or agent cannot complete a later graph.

Resolving a forward declaration in type position does not itself initialize that declaration's ordinary value binding or mark its ordinary Evaluation as completed. It does not execute intervening statements or an enclosing body. Expression-position reads retain their ordinary initialization and temporal-dead-zone rules. Source preparation may still initialize declarations at its existing checking boundary; this rule does not undo that preparation or process an already completed declaration twice.

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 1.3.8. Closed invalid declarations remain subject to the pre-body judgment of 1.3.4; an unrelated native runtime error retains its existing phase and class.

1.3.7 Exported Declaration Preparation

For the declaration preparation required by 1.2.8, an ExportDeclaration of the form ExportDeclaration : export Declaration whose Declaration is a TypeAliasDeclaration or InterfaceDeclaration contributes that declaration in the same lexical declaration scope and at the same source-order position as the corresponding declaration without the 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 shape aliases available before their governing 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 specialization rules still apply. A declaration successfully processed during preparation is not processed again when ordinary Evaluation reaches it through the export wrapper. Failure to prepare a genuinely runtime-dependent declaration does not mark it as successfully processed. A required unavailable imported input fails as specified in 1.3.8; an eligible recursive reference still requires the validity judgment in 1.3.4.

This selection does not perform Evaluation of the export wrapper or of other export forms. It does not execute an ordinary exported value initializer, class initializer, default-export expression or decorator application. Those operations retain their specified evaluation order and effects. The restricted computations that an existing type-preparation rule requires remain subject to 1.2.7 and its budget. No export grammar, reference lifetime or ordinary module dependency order is changed by this rule.

1.3.8 Module Type Checking Order

Parsing and linking apply the rules assigned to those phases before module evaluation. For the remaining judgments that require type evaluation, each Source Text Module Record has a pre-body checking boundary at the point where its ordinary module evaluation would enter its own code. Checking does not introduce a separate traversal that executes dependency bodies. InnerModuleEvaluation, ExecuteAsyncModule and AsyncModuleExecutionFulfilled continue to determine when that boundary is reached, including cycles and asynchronous dependencies.

In Source Text Module Record ExecuteModule, replace the Evaluation of the module's [[ECMAScriptCode]] in the branch without top-level await with EvaluateModuleBodyWithTypeChecking of that code. Retain the surrounding context suspension, restoration and abrupt-completion propagation. In AsyncBlockStart, when asyncBody is a Module Parse Node, replace its Evaluation with EvaluateModuleBodyWithTypeChecking of asyncBody inside the async evaluation closure, after its context becomes current. The existing rejection path handles an abrupt result. This change does not apply to an ordinary async function body.

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 StaticTypeError for type-evaluation failure before the candidate module body starts. Binding identity follows the resolved import target, including re-exports, as in 1.3.5. Do not treat this failure as permission to retry after body entry, wait for every member of the cycle, or substitute a fallback type. This does not require evaluating an expression whose evaluation is deferred by a generic or other explicit deferral rule, and it does not reclassify an unrelated runtime exception.

The required-input rule also covers a producer's uninitialized module binding read through the original lexical environment of a helper, including a transitive helper or namespace import. Record that failure when the read is attempted. Handling its ordinary exception, returning a fallback type or Boolean, or replacing its completion in a finally clause cannot make the required type judgment succeed. Conditional and short-circuit evaluation still reads only selected operands; do not reject an otherwise evaluable unselected immutable dependency merely for being uninitialized. A helper's own local temporal dead zone and an exception constructed by user code retain their ordinary completion semantics. This rule does not make the current source's pending declarations available or remove their explicit preparation and deferral rules.

Before accepting a computed result or publishing successful checking or specialization evidence, check whether the containing required evaluation recorded an unavailable external input. Such a failure publishes no success and is reported as the type-evaluation failure above; budget exhaustion retains precedence. Retain the failure for that required evaluation, including nested type computations, and discard its internal bookkeeping when checking exits. A subsequent independent check has its own input state. Ordinary runtime evaluation outside this checking boundary is unchanged, and neither error properties nor diagnostic text establish an input's identity. No additional reference-liveness permission follows from a successful check.

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 published does not postpone the required imported interface head read. Perform that read at this module boundary; if it fails, report the type-evaluation failure above. Do not initialize the binding from a structural description or mark the application complete merely because the read succeeds. A generic default or constraint that is only being described, or an application whose evaluation is deferred by unbound generic inputs, does not become a required closed reference by this rule. Its existing demand and specialization rules continue to apply.

The existing Module Record evaluation state governs repeated evaluation requests and shared dependencies. Checking must not re-enter an already evaluated module body or repeat its type computations in response to another request to evaluate the same record. A still-pending evaluation is neither successful nor rejected. Cached checking results may be reused only when their semantic inputs and required observable phase are preserved.

1.3.8.1 EvaluateModuleBodyWithTypeChecking ( code )

The abstract operation EvaluateModuleBodyWithTypeChecking takes argument code (a Parse Node) and returns a Completion Record. It applies the module's required pre-evaluation type judgments before entering its body, in the module execution context selected by the ordinary module evaluation algorithms. It performs the following steps when called:

  1. Assert: code is a Module Parse Node.
  2. Let checkResult be the Completion Record obtained by applying the pre-evaluation checking of 1.2.8 to code in the running execution context, including its required declaration preparation and type judgments.
  3. If checkResult is an abrupt completion, return ? checkResult.
  4. Return Completion(Evaluation of code).

1.3.9 Effects and Evaluation Order

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 path prefix invalidates dependent suffixes. Declared store contracts survive these invalidations.

An unknown call, accessor, conversion, proxy operation, callback, iterator operation or direct eval cannot supply an effect-free proof. Invalidate the mutable facts that the operation can affect under the existing origin and escape rules; retain facts about immutable captured primitive values and other identities those rules establish as independent. A known intrinsic is used only while its identity and the relevant receiver/descriptor facts remain established. No ordinary operation is executed merely to discover these facts. Compile-time type evaluation is the separate, restricted operation of 1.2.7.

Suspension, iterator closing, resource disposal and a finalizer apply their specified invalidations and completion replacement before a successor receives facts. A runtime identity guard or an inline-cache observation is not a static proof. The origin and effect preconditions in 1.2.8.3 remain requirements for every transferred library fact and contract.

1.3.10 Transfer Composition

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 1.8.5.1; an empty alternative does not by itself authorize a new diagnostic in every expression position.

Table 5: Composition of transfers
FormRequired composition
Parentheses and negationParentheses preserve the result. Negation exchanges the true and false alternatives without re-evaluating the operand.
Logical AND and ORAnalyze 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 chainsUse 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 expressionsAnalyze the condition, then each arm under its own incoming alternative. Join reached normal results and preserve abrupt alternatives separately.
Assignments and updatesEvaluate and capture the target in the specified order. Check a reached store against its declared destination, perform the specified conversion/effects transfer, and retain the captured expression result independently of later destination changes. Logical assignments also retain their skipped-store alternative.
Comparisons, predicates, patterns and assertionsUse the applicable row of Table 24 and its membership-stability conditions. Preserve distinctions among strict equality, SameValue, NaN and signed zero. Unknown or effectful cases supply no additional subtraction proof.
Calls, construction and other operatorsResolve 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 resultsSequence operand effects and transport only each construct's specified completion value. Match guards and patterns use their defined scopes and transfers; a pipeline topic identifies the captured input value. Generator-producing forms retain their generator result rather than a yielded scalar value.

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 captured discriminant, and includes labels on both sides of a default when deciding whether that default is selectable. A label's evaluation effects precede subsequent label tests.

Loops use the bounded write-discovery and widening rules of 1.8.5.1. The entry alternative is not a repeated-iteration alternative. A while or ordinary for loop includes its possible zero-iteration exit; a do-while first executes its body. A continue reaches only its designated loop's update/test, and a break reaches only its designated exit. For-in, for-of and for-await-of apply their specified enumeration, iterator, assignment, closing and suspension effects. An established empty iteration has no body predecessor, whether or not its diagnostic applies.

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 UpdateEmpty where the ordinary semantics requires it. Unreachable source still undergoes the ordinary syntactic and declared-contract checks specified for it; it supplies no outgoing execution facts to a reachable join.

1.3.11 StaticTypeError Objects

The initial value of the global object's StaticTypeError property is the intrinsic object %StaticTypeError%. The property has the attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }. Each realm has its own %StaticTypeError% and %StaticTypeError.prototype% objects. They are created with the realm's other intrinsic objects.

The %StaticTypeError% intrinsic is a built-in function object with [[Call]] and [[Construct]] internal methods. Its [[Prototype]] is %Error%. Its name is "StaticTypeError" and its length is 1𝔽, with the usual built-in function property attributes. Its prototype property has value %StaticTypeError.prototype% and attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }.

1.3.11.1 StaticTypeError ( message, options )

  1. If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget.
  2. Let O be ? OrdinaryCreateFromConstructor(newTarget, "%StaticTypeError.prototype%", « [[ErrorData]] »).
  3. If message is not undefined, then
    1. Let msg be ? ToString(message).
    2. Perform CreateNonEnumerableDataPropertyOrThrow(O, "message", msg).
  4. Perform ? InstallErrorCause(O, options).
  5. Return O.

1.3.11.2 Properties of the StaticTypeError Prototype Object

The %StaticTypeError.prototype% intrinsic is an ordinary object. Its [[Prototype]] is %Error.prototype%. It has no [[ErrorData]] internal slot. Its own constructor, name, and message properties have values %StaticTypeError%, "StaticTypeError", and the empty String, respectively. Each has attributes { [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }.

A StaticTypeError instance has an [[ErrorData]] internal slot. No diagnostic identifier, source location, or other host diagnostic record is an additional required own property of the instance. A required type rejection creates the error using the intrinsic of the checking realm, independently of changes to the global StaticTypeError binding.

1.3.12 Diagnostic Identity

Normative rejection predicates are the rules of this specification, with applicability determined by 1.3.2. The accompanying diagnostic registry supplies stable references to those rules and developer-facing diagnostic families. It does not replace the predicates, enlarge the required rejection set, or authorize suppressing an applicable error.

Diagnostic text, presentation order, number of reported consequences, and host transport are implementation-defined. No diagnostic code property is added to ECMAScript Error objects by this clause. A host may expose structured diagnostics containing a family identifier, responsible rule, phase, source location, structured arguments, and related declaration/origin locations. The identifiers are independent of section numbering, message wording, and source filenames. A stable identifier is never reassigned to a different meaning; a retired identifier remains reserved. A provisional identifier must be identified as such in the registry.

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.

1.3.13 Diagnostic Rule Index

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; stable and retired identifiers are never repurposed.

Table 6: Diagnostic rule identities
RuleDiagnostic familyApplicabilityMeaning and normative source
rt-default-evaluationRT_DEFAULT_EVALUATION (provisional)EverywhereA required default cannot be evaluated. Supply an evaluable default or an explicit initializer. 1.8.3
rt-default-conversionRT_DEFAULT_CONVERSION (provisional)EverywhereThe default cannot undergo the declared conversion. 1.8.3
rt-where-unsatisfiedRT_WHERE_UNSATISFIED (provisional)EverywhereThe application fails a declared constraint. 4.10
rt-unclaimed-metadataRT_UNCLAIMED_METADATA (provisional)EverywhereA metadata key has no governing meta declaration. 6
rt-enum-initializerRT_ENUM_INITIALIZER (provisional)EverywhereAn enum initializer violates its binding, evaluability, or value contract. 1.11.8
rt-specializationRT_SPECIALIZATION (provisional)EverywhereThe call cannot be specialized under the declared constraints. 4.7
rt-type-evaluationRT_TYPE_EVALUATION (provisional)EverywhereA required type computation cannot complete normally, or a closed type expression does not produce a Type Object. 1.2.7, 1.3.8, 4.10.1
rt-impossible-testRT_IMPOSSIBLE_TEST (provisional)Checked codeThe deciding test necessarily succeeds or necessarily fails under the specified stable facts. 1.8.5.4
rt-empty-intersectionRT_EMPTY_INTERSECTION (provisional)EverywhereThe declared intersection has no inhabitant under the specified stable judgment. 1.4.2.1
rt-assignabilityRT_ASSIGNABILITY (provisional)EverywhereThe established source cannot satisfy the declared destination contract. 1.2.3.2
rt-uninhabited-declarationRT_UNINHABITED_DECLARATION (provisional)EverywhereThe declared type cannot have an initialized value. 1.5.18
rt-missing-defaultRT_MISSING_DEFAULT (provisional)EverywhereThe declaration needs an initializer because its type has no default. 1.8.3
rt-inherited-contractRT_INHERITED_CONTRACT (provisional)EverywhereAn implementation or override fails the inherited callable or member contract. 1.11.2
rt-generic-binding-orderRT_GENERIC_BINDING_ORDER (provisional)EverywhereA parameter constraint or default reads a binding before it is available. 1.4.3
rt-variadic-contractRT_VARIADIC_CONTRACT (provisional)EverywhereThe variadic declaration or rest boundary does not meet its required type or separation rule. 4.4
rt-generic-constraintRT_GENERIC_CONSTRAINT (provisional)EverywhereThe argument does not satisfy the type parameter constraint. 1.4.3
rt-return-contractRT_RETURN_CONTRACT (provisional)EverywhereAn established return value cannot satisfy the function return contract. 1.10.7
rt-void-useRT_VOID_USE (provisional)EverywhereA void result is used where a value is required. 1.5.16
rt-ambiguous-conversionRT_AMBIGUOUS_CONVERSION (provisional)EverywhereMore than one applicable conversion is maximal; provide a uniquely applicable conversion. 1.6
rt-undeclared-memberRT_UNDECLARED_MEMBER (provisional)EverywhereThe required member is not declared for the established receiver alternatives. 1.7.10
rt-immutable-pattern-bindingRT_IMMUTABLE_PATTERN_BINDING (provisional)EverywhereThe pattern binding is immutable. 26.3
rt-readonly-storeRT_READONLY_STORE (provisional)EverywhereThe operation writes a declared readonly member. 1.7.10
rt-readonly-layoutRT_READONLY_LAYOUT (provisional)EverywhereThe operation writes an immutable type-layout property. 9.1
rt-missing-setterRT_MISSING_SETTER (provisional)Checked codeThe established existing built-in accessor has no setter for the reached store. 1.2.8
rt-index-storeRT_INDEX_STORE (provisional)EverywhereThe index operation supplies neither a setter nor a reference location. 1.9.9
rt-string-storeRT_STRING_STORE (provisional)EverywhereThe operation writes a fixed string position. 29
rt-vector-lane-storeRT_VECTOR_LANE_STORE (provisional)EverywhereA vector store names a component more than once. 1.5.10
rt-duplicate-memberRT_DUPLICATE_MEMBER (provisional)EverywhereA repeated member conflicts with the declared storage contract. 1.11.2
rt-index-signatureRT_INDEX_SIGNATURE (provisional)EverywhereAn index signature, indexed type, or index operand does not meet its declared key contract. 1.2.8
rt-partial-classRT_PARTIAL_CLASS (provisional)EverywhereThe partial class contribution violates its target or contribution restrictions. 1.11.11
rt-meta-declarationRT_META_DECLARATION (provisional)EverywhereThe meta declaration cannot be completed: its owner, key claims, parameter count, default or required hooks are invalid. 6.1
rt-computed-member-nameRT_COMPUTED_MEMBER_NAME (provisional)EverywhereThe computed name is not an admitted constant member key. 1.2.8
rt-narrowing-declarationRT_NARROWING_DECLARATION (provisional)EverywhereThe declared narrowing target does not name a parameter of this callable. 8.2
rt-layoutRT_LAYOUT (provisional)EverywhereThe declared storage cannot have the required finite layout. 9, 1.3.4
rt-overload-declarationRT_OVERLOAD_DECLARATION (provisional)EverywhereThe declared overloads overlap in a prohibited or ambiguous way. 1.4.3.2.2
rt-implements-contractRT_IMPLEMENTS_CONTRACT (provisional)EverywhereThe implements target is invalid, or an implemented member is missing or does not satisfy the target's member contract. 1.11.2
rt-generic-argumentRT_GENERIC_ARGUMENT (provisional)EverywhereThe application does not supply the required kind, count, or inferable generic arguments. 1.4.3
rt-protected-memberRT_PROTECTED_MEMBER (provisional)EverywhereThe member is protected at this access. 1.11.2
rt-promise-protocolRT_PROMISE_PROTOCOL (provisional)EverywhereThe established then contract cannot accept the assimilation callbacks. 1.2.8
rt-iterable-requiredRT_ITERABLE_REQUIRED (provisional)EverywhereThe required iterable operation is impossible for the established source. 1.14.8
rt-compile-time-evaluabilityRT_COMPILE_TIME_EVALUABILITY (provisional)EverywhereThe required type computation is outside the compile-time-evaluable fragment. 1.2.7
rt-parameter-orderRT_PARAMETER_ORDER (provisional)EverywhereThe required positional parameter cannot follow this default without an unambiguous binding. 1.2.8
rt-member-defaultRT_MEMBER_DEFAULT (provisional)EverywhereA member with a default must be optional. 1.8.3
rt-type-requiredRT_TYPE_REQUIRED (provisional)EverywhereThe type position or annotation does not denote the required type category. 1.2.1
rt-integer-widthRT_INTEGER_WIDTH (provisional)EverywhereThe integer width is outside the specified integer-width domain. 1.5.23
rt-array-extentRT_ARRAY_EXTENT (provisional)EverywhereThe array extent or type argument list is invalid. 1.4.2.7
rt-composite-typeRT_COMPOSITE_TYPE (provisional)EverywhereThe requested composite has an inadmissible component type or key. 25.5.2
rt-weak-targetRT_WEAK_TARGET (provisional)EverywhereThe declared weak collection cannot hold this value category. 1.12.9
rt-explicit-conversionRT_EXPLICIT_CONVERSION (provisional)EverywhereThe explicit conversion is not defined for these types. 1.6
rt-constructor-requiredRT_CONSTRUCTOR_REQUIRED (provisional)Checked codeThe established target cannot be constructed. 1.2.8
rt-range-valueRT_RANGE_VALUE (provisional)EverywhereThe range has an inadmissible endpoint or produces a value outside its declared domain. 21
rt-numeric-rangeRT_NUMERIC_RANGE (provisional)EverywhereThe established value is outside the required numeric or literal domain. 1.6.7.3
rt-numeric-operatorRT_NUMERIC_OPERATOR (provisional)EverywhereThe numeric categories do not admit this operator together. 1.9
rt-disjoint-comparisonRT_DISJOINT_COMPARISON (provisional)Checked codeThe established operands share no value for this comparison. 1.8.5.4
rt-zero-divisorRT_ZERO_DIVISOR (provisional)EverywhereThe literal divisor is zero where the typed operation forbids it. 1.9
rt-shift-distanceRT_SHIFT_DISTANCE (provisional)EverywhereThe typed shift distance is negative or removes every bit. 1.9
rt-required-memberRT_REQUIRED_MEMBER (provisional)EverywhereThe value omits a required declared member. 1.7.10
rt-constant-conditionRT_CONSTANT_CONDITION (provisional)Checked codeThe deciding value has only truthy or only falsy alternatives. 1.9.5
rt-impossible-assertionRT_IMPOSSIBLE_ASSERTION (provisional)Checked codeThe asserted stable domain cannot admit the established value. 8.2
rt-unreachable-operandRT_UNREACHABLE_OPERAND (provisional)Checked codeThe deciding left operand necessarily skips the right operand. 1.8.5.1
rt-pattern-domainRT_PATTERN_DOMAIN (provisional)EverywhereThe pattern cannot match or unambiguously describe the required position. 26.3
rt-matcher-contractRT_MATCHER_CONTRACT (provisional)EverywhereThe custom matcher is absent or has an incompatible callable/result contract. 26.3
rt-unreachable-patternRT_UNREACHABLE_PATTERN (provisional)Checked codeNo remaining value can reach this pattern clause. 26.3
rt-nonexhaustive-matchRT_NONEXHAUSTIVE_MATCH (provisional)EverywhereThe match omits a required case or catch-all. 26.3
rt-unreachable-defaultRT_UNREACHABLE_DEFAULT (provisional)Checked codeEarlier cases cover every admitted value, so the default cannot be selected. 1.8.5.1
rt-pattern-scopeRT_PATTERN_SCOPE (provisional)EverywhereThe binding-carrying pattern is outside its governing position or scope. 26.3
rt-return-inferenceRT_RETURN_INFERENCE (provisional)EverywhereReturn inference is proved not to converge to a finite result; write an explicit return type. 1.6.2.3
rt-inference-limitRT_INFERENCE_LIMIT (provisional)EverywhereReturn inference exhausted a host resource limit; this does not prove non-convergence and supplies no fallback result. 1.6.2.8
rt-unproductive-typeRT_UNPRODUCTIVE_TYPE (provisional)EverywhereThe alias only defines itself and produces no type. 1.8.1, 1.3.4
rt-reference-locationRT_REFERENCE_LOCATION (provisional)EverywhereThe operation requires a referenceable location, not merely a value. 2.3
rt-fixed-extent-storeRT_FIXED_EXTENT_STORE (provisional)EverywhereThe operation changes a fixed sequence extent. 1.7.10
rt-property-receiverRT_PROPERTY_RECEIVER (provisional)Checked codeThe established receiver cannot support the required property operation. 1.2.8
rt-conversion-protocolRT_CONVERSION_PROTOCOL (provisional)EverywhereThe selected typed conversion protocol cannot satisfy the reached operation. 1.2.8
rt-iterator-closeRT_ITERATOR_CLOSE (provisional)EverywhereThe reached iterator close cannot satisfy its declared protocol contract. 1.2.8
rt-named-argumentRT_NAMED_ARGUMENT (provisional)EverywhereNamed and positional arguments cannot bind the signature as written. 1.2.8
rt-builtin-argumentRT_BUILTIN_ARGUMENT (provisional)Checked codeThe established arguments cannot satisfy the added contract of the existing built-in operation. 1.2.8
rt-atomic-argumentRT_ATOMIC_ARGUMENT (provisional)EverywhereThe typed atomic operation requires a supported location and operand category. 43.6.3
rt-typed-array-storeRT_TYPED_ARRAY_STORE (provisional)EverywhereThe established intrinsic array store cannot convert to the declared element type. 1.14.10
rt-call-argumentRT_CALL_ARGUMENT (provisional)EverywhereNo argument binding can satisfy the required callable contract. 1.2.8
rt-ambiguous-callRT_AMBIGUOUS_CALL (provisional)EverywhereMultiple declared signatures remain applicable without a unique selection. 1.10.3
rt-not-callableRT_NOT_CALLABLE (provisional)Checked codeThe established callee is not callable. 1.2.8
rt-decorator-argumentRT_DECORATOR_ARGUMENT (provisional)EverywhereThe decorator cannot accept its written arguments and object context. 1.2.8
rt-reference-argumentRT_REFERENCE_ARGUMENT (provisional)EverywhereThe argument does not supply the required reference or reference type. 2.4
rt-proxy-storageRT_PROXY_STORAGE (provisional)EverywhereA Proxy cannot wrap the specified protected typed storage. 1.7.10
rt-collection-seedRT_COLLECTION_SEED (provisional)EverywhereA surviving seed entry cannot satisfy its declared collection element contract. 1.15.2
rt-this-requiredRT_THIS_REQUIRED (provisional)EverywhereThe call lacks the receiver required by the declared method contract. 1.2.8
rt-duplicate-operatorRT_DUPLICATE_OPERATOR (provisional)EverywhereThe operator overlaps an already declared primitive operation. 6.5
rt-empty-iterationRT_EMPTY_ITERATION (provisional)Checked codeThe established source has no element, so the loop body cannot execute. 1.8.5.1
rt-reference-iterationRT_REFERENCE_ITERATION (provisional)EverywhereThe iteration cannot supply references to its elements. 2.7
rt-disposal-contractRT_DISPOSAL_CONTRACT (provisional)EverywhereThe selected disposal hook cannot satisfy the required call contract. 38
rt-instanceof-protocolRT_INSTANCEOF_PROTOCOL (provisional)Checked codeAn added instanceof protocol requirement cannot be satisfied in checked code. 1.9.4, 1.3.2
rt-instanceof-declared-callRT_INSTANCEOF_PROTOCOL (provisional)EverywhereEvery selected callable instanceof hook violates its declared receiver or parameter contract. 1.9.4, 1.3.2
rt-object-operandRT_OBJECT_OPERAND (provisional)Checked codeThe operator requires an Object right operand. 1.2.8
rt-catch-orderRT_CATCH_ORDER (provisional)EverywhereAn untyped catch must be the final handler. 1.7.12
rt-unreachable-catchRT_UNREACHABLE_CATCH (provisional)Checked codePrevious catch filters admit every value this handler could accept. 1.7.12
rt-enum-caseRT_ENUM_CASE (provisional)EverywhereThe enum switch names an invalid case or omits a required case. 1.11.8
rt-unreachable-range-caseRT_UNREACHABLE_RANGE_CASE (provisional)Checked codeEarlier range labels cover every value this label could select. 1.8.5.1
rt-declaration-inferenceRT_DECLARATION_INFERENCE (provisional)EverywhereThe declaration requires a static type that its initializer does not establish. 1.8.2
rt-reference-rebindingRT_REFERENCE_REBINDING (provisional)EverywhereThe destination is not a rebindable reference binding. 2.5
rt-typed-deleteRT_TYPED_DELETE (provisional)EverywhereThe operation deletes protected typed storage. 1.7.10
rt-abstract-constructionRT_ABSTRACT_CONSTRUCTION (provisional)EverywhereAn abstract class cannot be constructed. 1.11.2
rt-declared-zeroRT_DECLARED_ZERO (provisional)EverywhereThe declared zero does not satisfy the type. 1.11.5
rt-superclass-contractRT_SUPERCLASS_CONTRACT (provisional)EverywhereThe typed superclass does not satisfy the constructor/prototype contract. 1.11.2
rt-ambiguous-resultRT_AMBIGUOUS_RESULT (provisional)EverywhereSeveral comparison result forms remain possible; supply a result context. 1.10.5
rt-operator-argumentRT_OPERATOR_ARGUMENT (provisional)EverywhereNo declared operator signature accepts these operands. 1.10.3
rt-ambiguous-operatorRT_AMBIGUOUS_OPERATOR (provisional)EverywhereMore than one declared operator signature remains applicable. 1.10.3
rt-parameter-kindRT_PARAMETER_KIND (provisional)EverywhereThe written parameter domain does not determine an admitted parameter kind. 1.4.3.3
rt-specialization-contractRT_SPECIALIZATION_CONTRACT (provisional)EverywhereThe specialized declaration does not preserve its owner contract or has invalid selection patterns. 1.4.3.2.2
rt-type-default-identityRT_TYPE_DEFAULT_IDENTITY (provisional)EverywhereA shared type default must have value semantics. 1.8.3
rt-reference-permissionRT_REFERENCE_PERMISSION (provisional)EverywhereThe reference operation violates its access or lifetime permission. 2.6
rt-vector-argumentRT_VECTOR_ARGUMENT (provisional)EverywhereThe vector application does not meet its element, extent, or lane contract. 1.5.7
rt-type-evaluation-limitRT_TYPE_EVALUATION_LIMIT (provisional)EverywhereThe specified semantic work limit is exhausted; the computation supplies no fallback type. 1.2.7.4, 1.2.7.5
rt-proposal-constructionRT_CONSTRUCTOR_REQUIRED (provisional)EverywhereThe established target cannot be constructed. 1.2.8
rt-declared-callRT_NOT_CALLABLE (provisional)EverywhereThe established callee is not callable. 1.2.8

1.4 Grammar

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 additive, and every construct it introduces is a syntax error in the current language, so no source text changes meaning.

Note

The type grammar is a separate grammar from the expression grammar, reached from expression contexts only through the annotation and operator productions of 1.4.4 and 1.4.5. Keeping the two apart is what lets a type use |, &, {, and ( with meanings unrelated to their expression counterparts.

1.4.1 Lexical Grammar

1.4.1.1 Punctuators

Two punctuators are added. Neither token sequence occurs in any currently valid source text, which is what allows them to be added without changing the meaning of an existing program.

Punctuator :: OptionalChainingPunctuator TypePunctuator OtherPunctuator TypePunctuator :: one of .< := Note

.< introduces a type argument list (1.4.2.2). Today a . 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 typed initializer or a conversion (1.4.5). A : immediately followed by = does not occur in valid source text.

1.4.1.2 The InputElementType Goal Symbol

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 goal symbol is therefore added in which > is always a single punctuator.

InputElementType :: WhiteSpace LineTerminator Comment CommonToken RightBracePunctuator

The InputElementType goal symbol is used in all syntactic grammar contexts where a TypeArguments or a TypeParameters is being parsed, between its opening .< or < and its closing >. When this goal symbol is used, the punctuators >>, >>>, >=, >>=, and >>>= are not recognized; a > is lexed as the single-character punctuator >.

Note

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.

1.4.1.3 Imaginary Literals

An imaginary literal is a numeric literal with the suffix i, denoting a value on the imaginary axis. The suffix occupies the slot a numeric literal followed immediately by an identifier start occupies today, which is a syntax error, and is the slot the n suffix of a BigInt literal already uses.

NumericLiteral :: DecimalLiteral DecimalBigIntegerLiteral DecimalImaginaryLiteral NonDecimalIntegerLiteral[+Sep] NonDecimalIntegerLiteral[+Sep] BigIntLiteralSuffix LegacyOctalIntegerLiteral DecimalImaginaryLiteral :: DecimalLiteral ImaginaryLiteralSuffix ImaginaryLiteralSuffix :: i Note

Unlike the BigInt suffix, which attaches only to an integer literal, the imaginary suffix attaches to any DecimalLiteral, so 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.

1.4.2 Type Expressions

A Type is the syntax of a type. Union and intersection compose types, and the remaining forms are primary.

Type[Yield, Await] : UnionType[?Yield, ?Await] FunctionType[?Yield, ?Await] UnionType[Yield, Await] : |opt IntersectionType[?Yield, ?Await] UnionType[?Yield, ?Await] | IntersectionType[?Yield, ?Await] IntersectionType[Yield, Await] : &opt PostfixType[?Yield, ?Await] IntersectionType[?Yield, ?Await] & PostfixType[?Yield, ?Await] PostfixType[Yield, Await] : PrimaryType[?Yield, ?Await] IndexedAccessType[?Yield, ?Await] ParameterizedType[?Yield, ?Await] IndexedAccessType[Yield, Await] : PostfixType[?Yield, ?Await] [ Type[?Yield, ?Await] ] ParameterizedType[Yield, Await] : PostfixType[?Yield, ?Await] TypeArguments[?Yield, ?Await] PrimaryType[Yield, Await] : shared PrimaryType[?Yield, ?Await] PredefinedType LiteralType TypeReference[?Yield, ?Await] ComputedType[?Yield, ?Await] KeyOfType[?Yield, ?Await] ArrayOrTupleType[?Yield, ?Await] ObjectType[?Yield, ?Await] ReferenceType[?Yield, ?Await] CoverParenthesizedTypeAndFunctionTypeParameters[?Yield, ?Await] PredefinedType : one of void null undefined LiteralType : NumericLiteral - NumericLiteral Infinity - Infinity NaN - NaN StringLiteral true false ReferenceType[Yield, Await] : ref PrimaryType[?Yield, ?Await] KeyOfType[Yield, Await] : keyof PrimaryType[?Yield, ?Await] Note

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 design writes 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.

FunctionType is an alternative of Type rather than of UnionType, because a function type's return extends as far to the right as it can: (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 reserved words and so cannot be reached through TypeReference, which is why they are given their own production. undefined is not a reserved word but a global binding, an ordinary identifier; it is given its own production here as well, so that 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 TypeReference; they are shadowable, so a program that declares its own binding named uint8 is unaffected.

ReferenceType takes a PrimaryType operand, so ref uint8 | null is a union of ref uint8 and null. ref is a contextual keyword: it is a valid identifier today, so ref followed by a token that cannot begin a type is an identifier.

keyof is likewise a contextual keyword, and a KeyOfType binds tighter than union and intersection, so 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 1.4.2.5, producing a union of the literal types of 1.5.19.

1.4.2.1 Static Semantics: Early Errors for Intersection Types

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.

IntersectionType : IntersectionType & PostfixType Note

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-object intersection and gives a shared member the intersection of the types its arms declare (1.2.2.8), so a disjoint pair there empties the object exactly as a disjoint pair of arms empties the intersection. Without it the case would be reported nowhere useful: 1.2.2.6 is silent about two object types, so the whole-arm rule answers false and the emptiness reaches the program as a refusal at every use of the annotation.

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 1.5.18 at a property rather than a mistake. An OPTIONAL declaration on either side is exempt because it does not empty anything: a value omitting the property satisfies both arms, so { 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 1.2.2.6 declines. No value is an instance of two classes neither of which extends the other: this language has single inheritance, and 1.7.1.1 gives a value the nominal Type Record of ONE class. 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. AreDisjoint runs from 1.2.2.8, which runs whenever a type is interned, while IsSubtype's nominal arm recurses into type arguments and re-enters canonicalization; putting the judgment there would make interning depend on subtyping for a diagnostic. This pass is already running IsSubtype, so the same judgment costs nothing, and the type is left as written rather than reduced — which is also why an implementation is free to keep AreDisjoint conservative.

The rule is restricted to CLASS declarations, and must be: nominal covers an interface and an enum as well, and both overlap freely. A value satisfies any number of interfaces, so 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 object type is inhabited by an instance of a subclass carrying the member. A base and its subclass are exempt by the subtype test, sharing every value of the subclass.

Canonicalization already reduces such a type to never (1.2.2.8), so this rule changes no type; it changes where a program hears about one. Reduction alone would report the mistake at every USE of the annotation, as an assignment refused by a type no value inhabits, rather than at the & 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 1.8.5.3 already makes with its empty sentinel, which reports a test that cannot succeed as a mistake rather than narrowing to the type with no values, and it is made here for the same reason and with less doubt: a narrowing row at least has a live value flowing through it, while a written empty intersection has nothing that could ever reach it. & 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 (1.12.5) constructing a disjoint intersection returns 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 parameter, over which AreDisjoint is conservative, so the declaration stands and an instantiation at 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 shape of its members; this rule is decided over what the checking pass has resolved, and where a member is not resolved far enough there to be judged, AreDisjoint answers false and no error is reported. The type is 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 site, as it did before this rule existed.

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 (1.5.20), and its object-shaped argument is a type.

A member that denotes never is exempt because writing it says what the rule is for saying. uint8 & never is the annihilation identity of 1.5.18 spelled out rather than a mistake, and an author who wants to write the empty type has 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.

1.4.2.2 Type References and Type Arguments

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 4.8.1, including when the declaration was evaluated by an earlier Script or is reached through an import. This does not change ordinary expression evaluation of its mutable binding.

TypeReference[Yield, Await] : TypeName[?Yield, ?Await] TypeArguments[?Yield, ?Await]opt TypeName[Yield, Await] : IdentifierReference[?Yield, ?Await] TypeName[?Yield, ?Await] . IdentifierName TypeArguments[Yield, Await] : .< TypeArgumentList[?Yield, ?Await] ,opt > .< > TypeArgumentList[Yield, Await] : TypeArgument[?Yield, ?Await] TypeArgumentList[?Yield, ?Await] , TypeArgument[?Yield, ?Await] TypeArgument[Yield, Await] : Type[?Yield, ?Await] BindingIdentifier[?Yield, ?Await] : Type[?Yield, ?Await] ... Type[?Yield, ?Await] Note 1

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 TypeName cannot extend across it.

A type argument is written as a Type even when it is a value, as the width in int.<8> and the lane count in vector.<float32, 4> are. A numeric or string literal argument is a LiteralType, and a name that resolves to a value generic parameter is a TypeReference, so the syntax need not distinguish the two; the clause on generics decides which a given parameter expects. A value argument that is a computed expression, such as an arithmetic combination of value generic parameters, is written through a ComputedType (1.4.2.4): the builder call is the spelling for a computed argument, evaluated once its parameters are bound, and the argument position takes no further computation syntax of its own. A where clause (4.10) constrains the values a variadic or value parameter admits.

A TypeArgument may NAME the parameter it supplies, 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 BindTypeArguments (4.3), the one operation every application site performs, whichever spelling the list uses: a parameter takes its positional argument where one reaches it, otherwise the named argument bearing its name, otherwise its TypeParameterDefault, and a parameter left by all three is an error, as it is for a positional application. A TypeArgument of the third form spreads a tuple: its Type must resolve to a tuple type or to an array type whose extent is stated, and it is a type error otherwise, since a spread whose length is not known cannot say how many parameters it fills; the elements are spliced into the list, in order, before anything binds, so swizzle.<...Pair> for type Pair = [0, 1] is swizzle.<0, 1> and names the same specialization.

It is a type error for a TypeArgumentList to contain a TypeArgument of the second form whose BindingIdentifier does not name a TypeParameter of the type or function being applied. It is a Syntax Error for a TypeArgument of the second form to appear in the TypeArguments of an ArrayOrTupleType, a form that takes TypeArguments WITHOUT declaring parameters, such as [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 type error rather than a Syntax Error because it turns on the declaration applied, which may be imported, an alias, or computed, and is known only when the checking pass resolves it. A name that matched nothing would otherwise be discarded silently and the parameter would take its default, which makes a misspelling change what a program means.

It is a Syntax Error for two TypeArguments of the second form in one TypeArgumentList to have the same BindingIdentifier. It is a type error for one to name a TypeParameter already supplied positionally, and a type error for a TypeArgument of the first form to follow one of the second, so the positional arguments are exactly the leading ones and a positional argument's meaning does not depend on the names used after it — except where the second-form argument names a variadic parameter, in which case the first-form arguments after it, up to the next second-form argument, join its run: 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 Table 7, so Map.<V: uint8, K: string> means what it says and Map.<Z: uint8> is the error a user declaration's unknown name is.

Table 7: Type Parameter Names of Library Generics
GenericParametersDeclared in
Map, WeakMapK, V1.15
Set, WeakSetT1.15
IterableT28
vectorT, N1.5.7
int, uintN1.5.2
Note 2

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 ArrayOrTupleType, is in it by omission and refuses every name. A standard library declared in the language's own syntax would carry these names on its declarations and retire the table.

1.4.2.3 Library Type Argument Binding

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 shape restriction on 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 constructor or changing conversion rules.

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 TypeArguments retain their existing untyped, adoption or inference behaviour.

Table 8: Library Type Argument Contracts
DeclarationParameters in orderDefaults
Map, WeakMapK, VNone
Set, WeakSet, WeakRef, FinalizationRegistry, ProxyTNone
PromiseR, ER = any, E = any
Generator, AsyncGeneratorY, R, NR = 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 type error. Duplicate written labels remain the Syntax Error of 1.4.2.2. Existing constraints, including weak holdability, still apply. Annotation and expression applications use the same declaration metadata. Expression judgments require the actual generic declaration's origin, and a shadowing declaration supplies its own parameter list. None of these declarations is variadic. Other library families retain their own declarations, including Iterator's wrapper parameter.

1.4.2.4 Computed Types

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.

ComputedType[Yield, Await] : TypeReference[?Yield, ?Await] Arguments[?Yield, ?Await] ComputedType[?Yield, ?Await] Arguments[?Yield, ?Await]

A ComputedType is a compile-time type expression and is governed by 1.2.7: the call must be compile-time evaluable, and the type the ComputedType denotes is the Type Object contained in the normal completion that EvaluateToTypeObject returns for it. Retain that completion's type-result context for contextual typing (1.6.5). A call that is not evaluable, that completes abruptly, or that does not produce a Type Object is a type error by the rule stated there.

Note

The form is additive twice over. Every position in which a Type may appear is new syntax, so no existing program contains one; and within the grammar of this specification a TypeName followed by ( 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 design's rule is that an expression evaluating to a Type Object during checking is a valid type, and the call is the one expression form the type grammar needs to reach all of it: a conditional over IsAssignable, a map over a union's members, or any richer computation is a function away, written inside the builder rather than inline in the annotation. Keeping the surface to one form also keeps the evaluability requirement attached to one production.

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 Type Object the type position denotes. This production makes the call writable where a type is required; it does not change what the call is.

1.4.2.5 Static Semantics of keyof

A KeyOfType denotes GetTypeObject of the Type Record that KeyTypesOf returns for the Type Record its operand denotes.

1.4.2.5.1 KeyTypesOf ( t )

The abstract operation KeyTypesOf takes argument t (a Type Record) and returns a Type Record. It returns the type of the keys of t, which is never where t has no keys. It performs the following steps when called:

  1. If t involves an unbound parameter, return the Type Record { [[Kind]]: deferred, [[Operator]]: keyof, [[Operands]]: « t » }.
  2. If t.[[Kind]] is object, then
    1. Let keys be a new empty List.
    2. For each element p of t.[[Properties]], do
      1. If p.[[Key]] is a String, let base be the Type Record of the String type.
      2. Else, let base be the Type Record of the Symbol type.
      3. Append the Type Record { [[Kind]]: literal, [[Value]]: p.[[Key]], [[Base]]: base } to keys.
    3. For each element x of t.[[IndexSignatures]], append x.[[KeyType]] to keys.
    4. Return CanonicalizeType(the Type Record { [[Kind]]: union, [[Members]]: keys }).
  3. If t.[[Kind]] is intersection, then
    1. Let keys be a new empty List.
    2. For each element m of t.[[Members]], do
      1. Let k be KeyTypesOf(m).
      2. Append k to keys.
    3. Return CanonicalizeType(the Type Record { [[Kind]]: union, [[Members]]: keys }).
  4. If t.[[Kind]] is union, then
    1. If t.[[Members]] is empty, return t.
    2. Let first be KeyTypesOf(t.[[Members]][0]).
    3. Let kept be a List of the union members of first: the elements of first.[[Members]] where first.[[Kind]] is union, and « first » otherwise.
    4. For each integer i such that 1 ≤ i and i < the number of elements of t.[[Members]], do
      1. Let k be KeyTypesOf(t.[[Members]][i]).
      2. Remove from kept each element that is not assignable (IsAssignable) to k.
    5. Return CanonicalizeType(the Type Record { [[Kind]]: union, [[Members]]: kept }).
  5. If t.[[Kind]] is parameterized, return KeyTypesOf(t.[[Base]]).
  6. If t.[[Kind]] is nominal and t has a [[Structure]] field, return KeyTypesOf(t.[[Structure]]).
  7. Return the Type Record { [[Kind]]: union, [[Members]]: « » }.
Note

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 deferred record until a specialization closes its operand - the treatment 1.4.2.6 gives T[K] and 1.2.7 gives a call, stated once for all three as the deferred kind. Without it a parameter reached the final step 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 (B), not a spelling of a kit function, so there is no second definition to keep in step. A union's keys are the common keys, computed by keeping the first arm's keys that remain assignable to each further arm's, which is what lets the literal '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 (1.5.19), so the operation is total over the keys an object type can have. A refinement has its base's keys, so keyof sees through a brand and through the where of a dependent record type (5) to the properties underneath, which is the only reading on which keyof and the property mappers agree about a refined object. An interface has the keys of its structural form, for the same reason and by the same unwrapping IndexedTypeOf performs. A kind with no keys is never, the empty union, rather than an error: the set of keys of a 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 union, so the union step above returns it unchanged. A 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 type error and the operator stays total. IndexedTypeOf is unaffected and still returns empty for its three failures, since indexing a type by a key it does not have is a different question. An implementation folding over the arms must include the empty-union base case, since an arm-intersection fold does not otherwise reach it.

1.4.2.6 Static Semantics of Indexed Access

A deferred indexed access retains its formation obligation through aliases, nested types and generic substitution. When substitution closes its operands, apply IndexedTypeOf again. It is a type error if the result is empty, even when the containing declaration is unused. A failed closed projection does not become any, never or an unknown annotation. Unresolved operands remain deferred; this judgment executes no ordinary property lookup.

An IndexedAccessType denotes GetTypeObject of the Type Record that IndexedTypeOf returns for the Type Records its PostfixType and Type denote. It is a type error if IndexedTypeOf returns empty.

Note

It is stated as an abstract operation, as 1.4.2.5 is stated as KeyTypesOf, so that one operation has one normative statement and the two spellings cannot drift. The kit's indexed (B) computes the same function, and is a convenience rather than a necessity: the syntax reaches every position, including a generic return — function pluck<T: type, K: keyof T>(o: T, key: K): T[K] — a Type Object held in a variable, and an expression position through the 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 IntersectionType and PrimaryType rather than among the primaries.

Only a String literal key is admitted in this edition. A numeric key, an index signature, and a tuple index are each a type error rather than an unspecified corner, so a program that writes T[number] is told that this edition does not cover it rather than discovering an implementation's guess. The restriction is a floor and not a ceiling: a later edition may admit them, and could not have taken them away.

That restriction is also what keeps this form clear of the INDEX ACCESSORS (1.9.9), and the two do not overlap today. An index accessor applies where the base is an instance of a class that declares one and every index is a value of a NUMERIC type, and an index that is not numeric makes the access an ordinary property read; this syntax is a TYPE position, takes a String literal key, and reads a declared property. Different position, different key type, and this form does not reach a class instance type at all.

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] denotes for a 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.

1.4.2.6.1 IndexedTypeOf ( t, k )

The abstract operation IndexedTypeOf takes arguments t (a Type Record) and k (a Type Record) and returns either a Type Record or empty. It returns the type of the members of t named by the keys k, or empty where there is no such type. It performs the following steps when called:

  1. If t mentions a parameter type or k mentions a parameter type, then
    1. Let tName be t.[[Name]] if t.[[Kind]] is parameter, and the display form of t otherwise.
    2. Let kName be k.[[Name]] if k.[[Kind]] is parameter, and the display form of k otherwise.
    3. Let name be the string-concatenation of tName, "[", kName, and "]".
    4. Return the Type Record { [[Kind]]: parameter, [[Name]]: name }.
  2. Let arms be t.[[Members]] if t.[[Kind]] is union, and « t » otherwise.
  3. Let keys be k.[[Members]] if k.[[Kind]] is union, and « k » otherwise.
  4. Let results be a new empty List.
  5. For each element arm of arms, do
    1. Let shape be arm.
    2. Repeat, while shape.[[Kind]] is parameterized, or shape.[[Kind]] is nominal and shape has a [[Structure]] field,
      1. Set shape to shape.[[Base]] if shape.[[Kind]] is parameterized, and to shape.[[Structure]] otherwise.
    3. If shape.[[Kind]] is not object, return empty.
    4. For each element key of keys, do
      1. If key.[[Kind]] is not literal, or key.[[Value]] is not a String, return empty.
      2. Let p be the element of shape.[[Properties]] whose [[Key]] is key.[[Value]], or empty if there is none.
      3. If p is empty, return empty.
      4. If p.[[Optional]] is true, append CanonicalizeType(the Type Record { [[Kind]]: union, [[Members]]: « p.[[Type]], the Type Record of the undefined type » }) to results.
      5. Else, append p.[[Type]] to results.
  6. Return CanonicalizeType(the Type Record { [[Kind]]: union, [[Members]]: results }).
Note

Where a type parameter is involved the access is DEFERRED rather than computed or refused - IndexedTypeOf returns the Type Record { [[Kind]]: deferred, [[Operator]]: indexed, [[Operands]]: « t, k » } - : within the declaration that binds T, nothing is known about it, so T[K] names an access that will be performed at each specialization. It is a type of its own, and its opacity is the deferred kind's - it relates to itself, to 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 typed while 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 empty rather than raising is what lets the three failures - a key that is not a String literal, an operand with no properties, and a property that does not exist - be reported as one type error by the clause above, while the kit's indexed reports which of them it was. A class instance type is not reached: it is nominal without a [[Structure]] the loop unwraps to, so C["n"] is empty where I["n"] for an interface is not.

1.4.2.7 Array and Tuple Types

A declared tuple default is evaluated and converted to its position type when that type is formed, whether the initializer is a literal, a folded expression, or another compile-time-evaluable expression. An incompatible closed default is a type error even if no value ever demands it. A default depending on a type parameter is checked when specialization closes the obligation. Evaluation uses the existing evaluable fragment and budget; the converted value is the default used for construction, identity and reflection.

For a tuple access with a proven singleton numeric key, a required position before any rest has that position's declared Static Type. The proof may be a numeric literal, a numeric literal type, or the established value of an unannotated compile-time numeric const (1.2.6). It respects lexical shadowing and explicit annotations, including any, and preserves exact integer values rather than rounding a key to select a different position. It is a type error if the proven index is negative, fractional, or beyond the positions of a fixed tuple. The selected type participates in the ordinary call, assignment and borrow judgments. This rule establishes no type for a general dynamic index or for a newly recognized key whose position depends on a default or rest; it does not remove a runtime storage, bounds or reference-liveness check.

An array type is a fixed or dynamic count of elements of one type, and a tuple type is a fixed count of individually typed positions. Both are written in brackets, and are told apart by whether a type argument list follows the closing bracket.

ArrayOrTupleType[Yield, Await] : [ ] [ ] TypeArguments[?Yield, ?Await] [ ArrayExtent[?Yield, ?Await] ] TypeArguments[?Yield, ?Await] [ TupleElementList[?Yield, ?Await] ,opt ] ArrayExtent[Yield, Await] : AssignmentExpression[+In, ?Yield, ?Await] TupleElementList[Yield, Await] : TupleElement[?Yield, ?Await] TupleElementList[?Yield, ?Await] , TupleElement[?Yield, ?Await] TupleElement[Yield, Await] : Type[?Yield, ?Await] Initializer[+In, ?Yield, ?Await]opt ... Type[?Yield, ?Await] Note 1

[] 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 proposal's design documents and its 190-program challenge corpus, [] 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> (11) is the type of a value that does not, and every form above is assignable to it. A tuple's trailing position may carry a default through Initializer, and a tuple may spread another tuple or an array type at either end.

The first three alternatives and the fourth are distinguished only after the closing ], by whether a TypeArguments follows, and their bracketed contents belong to different grammars: an extent is an expression and a tuple element list is a list of types. A single-element case such as [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 TypeArguments follows and a tuple otherwise. The committee may still prefer to distinguish the two forms by their opening bracket, which would remove the cover grammar rather than change the meaning of any program it already accepts; this is a decision worth taking before the syntax is final.

Determine default placement in the effective ordered sequence after expanding tuple rest operands. A finite array rest contributes exactly its extent in required positions; a zero extent contributes none. It is a type error if a required position follows a defaulted position, or if a defaulted position follows an unbounded rest. A trailing unbounded rest may follow a defaulted fixed prefix. An unresolved operand defers the affected formation obligation until specialization; a closed specialization must satisfy it. A tuple is positional: an unsupplied defaulted position is selected only when the input stops before that position, never because a supplied value fits a later element better. In particular, [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 shape wanting to omit a member in the middle names its members, which is an object type.

A tuple element's Initializer is its position's default, and [[Initial]] is that default's value. The Initializer must be compile-time evaluable (1.2.7) and it is a type error otherwise: a tuple type is interned, so one Type Object serves every use of the type and the value in it is shared by all of them. A compile-time evaluable default is a value type, a string, or an enumerator, each of which is copied where it is used, so the sharing is unobservable; a default that allocated would hand every value of the type the same object. Evaluability does not by itself ensure this, since a compile-time-evaluable function may create local state and return it (1.2.7.1), so the rule is stated on the value as well: it is a type error for a default's value to be an Object unless the position's type is a value type (1.5.1), whose values copy. [[2].<uint8> = [1, 2]] is therefore a default and [any = []] is not.

For a ... TupleElement, canonicalize the resolved operand type. It is a type error unless that record is an array or tuple. An operand dependent on an unbound type parameter retains this requirement, which is checked when substitution determines the operand, including at a generic application. A closed any, scalar, or union that remains a union is not a sequence record; an unbounded homogeneous rest is written ...[].<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 Type Record identity fields.

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 element type of the rest that collects it. A position the array does not supply takes its default, converted to that position's type as a supplied value would be, so the length may fall anywhere from the number of positions carrying neither a rest nor a default up to the number of positions, and beyond it only where a rest collects the surplus. Conversion rather than a bare check is what every boundary of this specification performs, and a tuple literal builds a new Array, so no storage another binding holds is rewritten by it.

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 TypeError exception, whether it is a store to an index at or past the extent, an assignment to 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 TypeError exception, 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 (2.10). The extent is a compile-time constant: the layout computes a 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 (2.10), an index used to read or write an element, the length of a view over a buffer, and the size of a keyed collection (1.15). That type is the index type, and it is 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 (14); typed owned arrays and views use the same full count range. %TypedArray% is unaffected: its counts are Numbers and remain so (1.14.10.4). It is a property of the language and not of an array: an array type takes ONE type argument, its element, and there is no per-array spelling that varies the counts' type. Naming it once rather than repeating uint32 at each site is what lets the width be stated in one place, and 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.

A store to the length property of an array carrying an array or tuple type is a typed store whose target is the index type. A numeric literal takes that contextual type; it is a type error if a statically known source is not assignable to the index type or a literal cannot be represented by it. It is also a type error if a statically known literal length violates the array's fixed extent or the tuple's permitted lengths. Where the source is unknown, the store performs the ordinary checked conversion to the index type: a value of the wrong type throws a TypeError exception and a numeric value not exactly representable throws a RangeError exception. An untyped Array retains the base language's length conversion.

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 index type's arithmetic, including its integer wrapping, without making 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 typed array, ArraySetLength converts a supplied [[Value]] to the index type before changing the array. It uses that count as the requested length, checks the fixed-extent or tuple constraint, and then performs the applicable length and element-removal operations. Checking the count does not discharge a reference-liveness obligation: resizing during reference iteration and removal of a borrowed element remain subject to 2.10, including when the count check is statically elided.

Note 2

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 integer type across their containers.

An array type may be written where an expression is expected, and there denotes the type's constructor: 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 element type (1.8.3); a construction of a dynamic one has none and grows. An instance is an Array carrying the element type, so a store to an element is checked against it and the methods of 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 (2.11), so every method of Array.prototype reaches an instance unchanged while capacity, reserve, and shrinkToFit reach only an array that has an element type. Placing them on 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 true for an ordinary array and reading it would throw. Two mentions of one array type denote the same constructor, since the types are interned and the constructor is the type's.

Note 3

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 type error if the operand of shared is itself shared. Which types admit the modifier, what it places where, and what a checker may assume of a slot that carries it are given by 43.4; the grammar is stated here because it is the type grammar, and the grammar is the core's.

1.4.2.8 Intrinsic Array Method Contracts

For a proved intrinsic tuple mutation, check the values stored at each established destination slot against that slot's declared type. The union of tuple element types is not a write contract for each position. Apply the selected operation's index coercion, clamping, overlap order and reads before stores: 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 type error when established arguments and length prove that the operation necessarily changes the extent or leaves the tuple's permitted lengths. Apply the operation's index coercion, clamping and deletion rules before computing that effect. A zero-item 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 1.4.2.10: a let binding qualifies while no assignment, assignment pattern, captured write or direct eval can replace it, and a declaration without an Initializer holds its type's default, which for a stated-extent array is a new array. A guard executed by an inline cache cannot justify an irreversible Early Error. These judgments never prove reference liveness.

For with, the replacement value is checked at the receiver's element type, or at the selected tuple position. A known negative index is relative to the current length. When several tuple positions remain possible, a statically known replacement must satisfy each possible destination; unknown values and unresolved indices retain their runtime checks. The base language's index coercion and out-of-range behaviour are unchanged.

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 element type. These rules do not require an unused callback parameter to be written.

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 shape is invented. Receiving boundaries enforce the created container's actual type even when its current length happens to equal a required fixed extent.

1.4.2.9 Object Types

A computed member read whose key is a statically established finite set retains the read contract of each selected key. For each key, a named property takes precedence; only a key not declared by name uses an applicable index signature. Join the established read types, preserving optionality and any missing outcome established by the ordinary exact-key rule. If an alternative remains unknown, the read remains unknown. This does not establish that an index-signature key exists. General non-finite computed reads retain the dynamic treatment below. Neither key expressions nor getters are evaluated by this judgment.

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 type error even for an unused type. A dependent default is checked after specialization supplies its dependencies. The readonly modifier is admitted only on named properties; readonly before an IndexSignature is a Syntax Error. An ordinary property whose name is 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 type error if no such value can be produced. Named properties retain precedence. Compatible overlaps and an empty object remain valid, and an unknown contribution retains runtime checking. Apply the same obligation to established shorthand, method and final spread contributions under the existing property-replacement rules.

An object type describes a value by its members. It is the inline form of an interface.

It is an early error for a TypeMemberList to declare the same PropertyName twice, whatever the two members say. { n: int32, n: string } and { n: int32, n: int32 } are both refused, the second although its halves agree.

Note 1

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 (1.11.7): members arriving from different declarations of one interface are merged, and it is only within a single list that a repeat is an error.

The relationship runs both ways, and the reverse direction needs a name. The structural form of an interface type is the object Type Record whose [[Members]] are the members the interface declares, taken together with those it inherits, and with the interface's type arguments substituted. Every interface has one. A class has none: a class states a construction and an identity as well as a shape, and it is the identity that its type is for.

Note 2

This is what lets a value satisfy an interface by having its members, which 1.2.3.1 reads. Without it the rules would refuse f({ a: 'a' }) for interface IExample { a: string; }, since an interface is a nominal type (Table 1) and an object literal's type is object, and the step separating the kinds would answer before any member was inspected. The composite types have relied on the notion since they were specified, naming it in their own subtype rule; this clause is where it is defined rather than assumed.

ObjectType[Yield, Await] : { } { TypeMemberList[?Yield, ?Await] TypeMemberSeparatoropt } TypeMemberList[Yield, Await] : TypeMember[?Yield, ?Await] TypeMemberList[?Yield, ?Await] TypeMemberSeparator TypeMember[?Yield, ?Await] TypeMemberSeparator : one of , ; TypeMember[Yield, Await] : PropertyName[?Yield, ?Await] ?opt TypeAnnotation[?Yield, ?Await] Initializer[+In, ?Yield, ?Await]opt PropertyName[?Yield, ?Await] ?opt MethodSignature[?Yield, ?Await] MethodSignature[?Yield, ?Await] IndexSignature[?Yield, ?Await] MethodSignature[Yield, Await] : TypeParameters[?Yield, ?Await]opt ( FunctionTypeParameterList[?Yield, ?Await]opt ,opt ) TypeAnnotation[?Yield, ?Await]opt WhereClauses[?Yield, ?Await]opt IndexSignature[Yield, Await] : [ BindingIdentifier[?Yield, ?Await] TypeAnnotation[?Yield, ?Await] ] TypeAnnotation[?Yield, ?Await] Note 3

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 MethodSignature with no PropertyName is a call signature. An object type or interface whose members are all call signatures denotes the function Type Record whose [[Signatures]] are those signatures in declaration order, so { (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 (1.4.2.10). A call signature's TypeParameters are subject to the same early errors as a function type's. It is a type error for an object type to mix call signatures with named members or an index signature: this specification gives a callable value a function type and a keyed value an object type, and the design writes no shape that is both.

A member may be declared readonly, which sets the [[Readonly]] field of the Property Type Record it contributes. A write to a readonly member is a type error, at compile time where the type of the base is known and at run time otherwise. A form that stores back to the member, including a compound assignment's binary fallback and an increment or decrement, is a write. A declared compound operator instead invokes a method without replacing the member (1.9.9), so that invocation is permitted through a readonly member. readonly is shallow here as it is for a field (1.11.2): it fixes the member, not the object the member refers to.

Note 4

This is what makes the depth covariance of 1.11.1.3 sound. That clause admits a subtype through a 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 field's, and the difference is where each belongs. A class's 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 IndexSignature constrains the properties an object type does not declare by name: { [key: string]: uint32 } is the type whose remaining string-keyed properties, beyond those declared, are uint32. It is a type error if a TypeMember's Initializer appears without ?, since a default belongs to an optional member, the design's c?: uint8 = 0. A member default's value copies, under the rule and for the reason 1.4.2.7 gives a tuple element's, so c?: any = [] is a type error: an array the type shared would be one array in every value built at it. The Initializer must be compile-time evaluable (1.2.7) and it is a type error otherwise, for the reason 1.4.2.7 gives of a tuple element's: an object type is interned, so a member's default is one value shared by every use of the type rather than a computation performed per construction. A language that evaluates a default at each construction may admit any expression there; this one evaluates it once, which is what makes “the default is part of the contents that intern” a coherent statement and what lets a typed composite creation write it before freezing. A default is a construction feature: it is written where a value of the type is being built, an object literal taking the type, a typed parse (45.1), and a typed composite creation (25.5.2), and never by a check of a value that already exists. A check reads; an object lacking an optional member satisfies the type with the member absent, whether or not the member declares a default, and no check writes the default onto its subject. The line between the two is the one 17.1 already draws. An object literal written AT the position is fresh and is being built there, so the type supplies what the literal omits; a value that reached the position through a binding is not fresh and is only read, so nothing is written onto it. 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 type error if the key type of an IndexSignature is not 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 ObjectType declares by name is NOT constrained by a signature whose key type admits its key: the signature governs the remaining properties, as the first sentence of this paragraph says, and the declared member governs its own. Each signature contributes an Index Signature Record to the [[IndexSignatures]] field of the object Type Record the type constructs (1.11.1).

Note 5

An earlier draft of this clause required a declared member to be assignable to the signature's value type, the rule TypeScript applies, in the same sentence that gave the signature the properties "beyond those declared". The two cannot both hold, and the second is the one this specification keeps, for a reason stronger than which sentence came first: IsOfType (1.7.6.1) decides membership by walking the members, checking a declared member against its own type and every other key against the signature, so { 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 1.11.7 points at IsOfType for exactly this question. The two would also disagree about a fresh literal, which 1.4.2.9 fills member by member by the same division.

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-finite read is not given the signature's value type statically, and reaches the boundary check of 1.7.7 like any other read whose type is not established. The cost TypeScript is avoiding is one this proposal does not pay, so the constraint it pays for is one this proposal does not need.

1.4.2.10 Function Types

Calling a Type Object for conversion does not by itself grant [[Construct]]. It is a type error to use a proved conversion-only Type Object as the target of ordinary 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 constructor remains constructible when held in a binding annotated type; that annotation alone proves neither constructibility nor non-constructibility. Explicit array construction and placement construction retain their own rules. Unknown identities and explicit any remain dynamic.

A function type is a parameter list and a return type. Its parameters may be named or unnamed, and may carry the ref and rest modifiers a declaration's parameters carry.

The collected-container well-formedness rule of 1.4.4 applies to every annotated rest in a function type, object or interface call signature, method signature, and abstract method. Resolve the annotation before publishing the signature. It is a type error unless it denotes an array or tuple; a type parameter must have an array-or-tuple constraint. Aliases and unnamed parameters obey the same rule. A genuinely deferred type-resolution obligation is retained until resolution. This check is independent of whether an implementation or call is present and does not change reference-rest permissions.

A signature does not establish whether its function value can be constructed. It is a type error to construct a statically identified typed arrow, generator, async function, or async generator, and it is a type error to invoke a statically identified typed class constructor without new, including through an optional call or a tagged template. Ordinary function construction remains admitted. A function participates in this judgment through its parameter or return annotations, reference parameters, type parameters, or a non-any annotation on the binding holding it. A class participates through its typed members, declared operators, type parameters, class modifiers, or implemented interfaces. An annotation elsewhere in the enclosing body does not make an unrelated invocation participate.

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-constructor origin merely because the Static Type used for another judgment is unknown. A skipped edge performs no invocation. Subsequent member access or a call result needs its own origin proof; the original callee's identity does not transfer to that different value. A diagnostic belongs to the original optional expression and its checking unit, not to a synthetic expression used by an implementation.

A conditional selection is a ConditionalExpression or an expression of the ||, && 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 falsy part of its Static Type (1.9.5) rather than itself: a falsy part made only of primitive types is known to be neither a constructor nor callable and counts as such without participating, while a falsy part that may hold an Object is an unknown alternative. A conditional selection and an immutable alias of it preserve a conservative summary of these invocation capabilities. It is a type error to construct such a selection when every possible selected origin participates and is known not to be a constructor; apply the corresponding rule to calling only known typed class constructors. A possible supported or unknown alternative prevents the negative proof. Capability facts are independent of signature identity, so canonicalizing equal signatures does not discard them. This judgment evaluates neither the condition nor a selected function.

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 replacement is excluded; assignments, including assignment patterns and captured writes, and direct 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 constructor has [[Call]], whose class-constructor guard rejects ordinary invocation; it is not classified as an object lacking [[Call]].

These are typed-source judgments under 1.2.8. Pure JavaScript such as new (() => 1)() retains its runtime exception, as do unknown values and values deliberately exposed through any. The checking pass does not execute a function, decorator or property getter to determine an invocation capability.

FunctionType[Yield, Await] : TypeParameters[?Yield, ?Await]opt FunctionTypeParameters[?Yield, ?Await] => Type[?Yield, ?Await] FunctionTypeParameters[Yield, Await] : CoverParenthesizedTypeAndFunctionTypeParameters[?Yield, ?Await] CoverParenthesizedTypeAndFunctionTypeParameters[Yield, Await] : ( ) ( FunctionTypeParameterList[?Yield, ?Await] ,opt ) FunctionTypeParameterList[Yield, Await] : FunctionTypeParameter[?Yield, ?Await] FunctionTypeParameterList[?Yield, ?Await] , FunctionTypeParameter[?Yield, ?Await] FunctionTypeParameter[Yield, Await] : refopt Type[?Yield, ?Await] Initializer[+In, ?Yield, ?Await]opt refopt BindingIdentifier[?Yield, ?Await] ?opt TypeAnnotation[?Yield, ?Await] Initializer[+In, ?Yield, ?Await]opt refopt ... BindingIdentifier[?Yield, ?Await] TypeAnnotation[?Yield, ?Await] refopt ... Type[?Yield, ?Await]

A FunctionTypeParameter may carry an Initializer, a default for the parameter, as a TupleElement may (1.4.2.7): (a: uint8 = 1) => void, or in an interface of call signatures (string = '5', named: uint32). The Initializer is evaluated once, when the function type is resolved, and its value is converted to the parameter's type; it is compile-time evaluable and its value copies, under the rule 1.4.2.7 gives a tuple element's default, since the function type is interned with it; the Parameter Record's [[Initial]] holds the result and the parameter is optional. A parameter with an Initializer need not be named: the default fills its position, and a name is needed only to be addressed by a named argument. It is a type error if a parameter without an Initializer, without a BindingIdentifier, and not marked ? follows one with an Initializer, save a rest parameter. A parameter with a name may follow one: a named argument (1.10.4) fills it and leaves the default to be taken, which is what the example (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 1.4.2.7 gives for a tuple's defaults. The default is data the type supplies for a position; it is read by the call site (1.10.4) and never by the callee, whose own parameter list is unaffected.

When the production PrimaryType : CoverParenthesizedTypeAndFunctionTypeParameters is recognized, the interpretation of CoverParenthesizedTypeAndFunctionTypeParameters is refined using ParenthesizedType : ( Type ) ; it is a Syntax Error if the cover does not match that refinement, the judgment reading the cover alone: a FunctionTypeParameter that carries a BindingIdentifier, an Initializer, a ?, or a ... is not a Type, while a lone ref PrimaryType is the ReferenceType it spells and refines to a parenthesized one.

ParenthesizedType[Yield, Await] : ( Type[?Yield, ?Await] ) Note

(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 Signature Record's [[TypeParameters]] are the Type Parameter Records of the declared list (1.10.1), and its parameter and return types may name them. The list is subject to the early errors of 1.4.3.1 as a declaration's is, and its parameters bind by BindTypeArguments when a call through a binding of the type supplies type arguments. A function type declares no where clause: a where belongs to a declaration and is not part of a signature's identity, so it is checked at the specialization a crossing performs (4.6) and never read by the relations on types. Identity and subtyping of generic function types are given by 1.10.2.1 and 1.10.2.3.

1.4.3 Type Parameters

An established upper bound supplies operational contracts inside a generic body: member reads, store types and permissions, callable signatures, primitive operations and narrowing tests (1.8.5.4) are checked using the applicable bound. It is a type error when those contracts establish that an operation cannot succeed, even if no specialization invokes the body. This projection does not identify a parameter with its bound: assignment to the parameter, reference invariance, type identity and dependent type expressions retain the opaque parameter. Admitting a store under an upper bound alone does not prove it valid for every narrower specialization. A value parameter's domain supplies the same contracts: in the unspecialized body the parameter reads as a value of its domain, the type 1.4.3.3.3 checks every argument for it against. Cyclic, dependent or unknown bounds do not authorize evaluation of a builder or an assumed closed shape; unresolved obligations remain for specialization or runtime checking.

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 specialization of one (1.4.3.2), and which an entry is follows from its syntax alone: an entry written as a name followed by : declares a parameter, and every other entry is an argument. Whether a name is in scope never decides it.

TypeParameters[Yield, Await] : < > < GenericEntryList[?Yield, ?Await] ,opt > GenericEntryList[Yield, Await] : GenericEntry[?Yield, ?Await] GenericEntryList[?Yield, ?Await] , GenericEntry[?Yield, ?Await] GenericEntry[Yield, Await] : TypeParameter[?Yield, ?Await] SpecializationEntry[?Yield, ?Await] TypeParameter[Yield, Await] : VarianceModifieropt BindingIdentifier[?Yield, ?Await] TypeParameterHolesopt TypeParameterDomain[?Yield, ?Await] TypeParameterConstraint[?Yield, ?Await]opt TypeParameterDefault[?Yield, ?Await]opt VarianceModifieropt ... BindingIdentifier[?Yield, ?Await] TypeParameterDomain[?Yield, ?Await] TypeParameterConstraint[?Yield, ?Await]opt TypeParameterDefault[?Yield, ?Await]opt VarianceModifier : one of in out TypeParameterHoles : < TypeParameterHoleList > TypeParameterHoleList : _ TypeParameterHoleList , _ TypeParameterDomain[Yield, Await] : : Type[?Yield, ?Await] TypeParameterConstraint[Yield, Await] : extends Type[?Yield, ?Await] TypeParameterDefault[Yield, Await] : = Type[?Yield, ?Await] Note 1

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 compile-time constant whose value is a type, which is what it evaluates to in an expression position (4.1). 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 variadic parameter's domain is its collection type, ...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 higher-kinded parameter writes its holes before its domain, W<_>: type, and the domain describes what an application of it yields (4.13). The _ of a TypeParameterHoleList is the IdentifierName _.

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 specialization. A bare name is now always an argument, so neither <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.

Note 2

A VarianceModifier declares the parameter's variance, whose meaning is given by 4.12: 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 reserved word, so it can only be a modifier here. out is not: it remains an ordinary identifier everywhere, INCLUDING as a type parameter's own name, and is a VarianceModifier only where a BindingIdentifier follows it immediately. So <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.

Note 3

A parameter written with ... is a variadic parameter, collecting any number of arguments as a rest parameter collects any number of values, and its TypeParameterDomain is the type of what it collects — ...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 1.4.4 already teaches. What a variadic parameter binds is a tuple Type Record (4.4); the ... 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.

1.4.3.1 Static Semantics: Early Errors

A TypeParameters is a parameter list when every GenericEntry of it is a TypeParameter, a specialization list when none is, including <>, and a mixed list otherwise.

TypeParameters : < GenericEntryList ,opt > Note 1

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.

Note 2

The rule on a constraint states what 17.2 assumes, that a value parameter has a domain and no bound. The kind is read from the domain as written (1.4.3.3), so the rule needs no name resolved, and a value parameter's values are narrowed by a where clause instead (4.10).

The first rule is the trailing-defaults rule stated per run: a variadic parameter counts as defaulted, since an application may leave it empty, and it ends the run, so <...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 typed between them are an error": a pack of types with no bound admits everything, so a pack adjacent to it could never receive an argument positionally. The half that needs a domain or bound resolved — one that resolves to [].<any> admits everything too — is the type error of 4.4.

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 capture at the top level of a mixed list would observe nothing a caller could not already name, so it is refused in favour of a parameter.

1.4.3.2 Specialization Lists

A specialization list after a declared name specializes the family that name already declares: its entries are ARGUMENTS for the primary declaration's parameters, in order, and each is a fixed argument, the wildcard _, or a capture that binds the argument standing in its position.

SpecializationEntry[Yield, Await] : Type[?Yield, ?Await] CaptureBinding[?Yield, ?Await] ... CaptureBinding[?Yield, ?Await] CaptureBinding[Yield, Await] : const BindingIdentifier[?Yield, ?Await] TypeParameterHolesopt TypeParameterDomain[?Yield, ?Await]opt TypeParameterConstraint[?Yield, ?Await]opt TypeArgument[Yield, Await] : CaptureBinding[?Yield, ?Await] ... CaptureBinding[?Yield, ?Await] BindingIdentifier[?Yield, ?Await] : CaptureBinding[?Yield, ?Await] ArrayExtent[Yield, Await] : CaptureBinding[?Yield, ?Await] TupleElement[Yield, Await] : CaptureBinding[?Yield, ?Await] ... CaptureBinding[?Yield, ?Await]

The additional TypeArgument forms let a capture stand inside a nested application of a specialization entry, positionally, Map.<string, const Element>, or under the nested constructor's own parameter name, Map.<K: string, V: const Element>. The fixed extent of an array type within a specialization entry may likewise be a CaptureBinding, [const N].<const Element>, and so may an element of a tuple type, [const Head, ...const Tail], where a ... capture binds the tuple of the elements its run covers. A SpecializationEntry that is the Type _ 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 parameter list, the class's bare name in a type position denotes the class over its own parameters, as this does: begin(): PacketWriter { return this; }. A static member's are those of the specialization it is reached through. A nearer declaration's type parameter of the same name shadows it. Elsewhere, and in a value position anywhere, the bare name keeps its meaning.

In a value position within the body the bare name keeps its JavaScript meaning, the class's inner binding, and each specialization's body is evaluated with that binding naming the specialization. So new B(args) there constructs the specialization being evaluated: its parameters are the class's own, it is typed as the class over its own parameters, as the name in a type position is, and each argument is checked against the constructor at those parameters with the relation of a binding or a return. In the body of 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 specialization explicitly and is unaffected, and outside the body a construction infers its arguments as any generic call does.

An intrinsic family follows the declared-default rules of 1.5.25 in ordinary concrete type positions, including constraints. Thus complex denotes 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 1.5.26.

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 capture in such a position therefore restates its domain as 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 capture's domain is its position's: a primary declaration's parameter at the top level, and the nested constructor's parameter within an application. A written TypeParameterDomain restates that domain and must be the same type, except in a metadata position, where it selects the meta type whose metadata the capture binds, float32.<const D: Dimensions>. A TypeParameterConstraint bounds a type capture. A capture has no default and no variance, which the grammar does not provide: both are promises a primary declaration makes to its callers, and a capture only observes an argument that primary binding has already supplied.

A primary binds before its specializations are matched (1.4.3.2.4), so a specialization list reaches only applications whose arguments the primary can bind. It is a type error, at the declaration, for a fixed argument of a class specialization, or of a 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 1.4.3.2.2 applies to decide whether a callable declaration attaches; a class family has no standalone case to receive a list that fails it, so the list could match no application. It is likewise a type error, reported at the later declaration, for two class specializations of one family that are not 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 specialization for their intersection that resolves an ambiguity between two different lists would be a third copy of the same list.

1.4.3.2.1 Static Semantics: Early Errors

CaptureBinding : const BindingIdentifier TypeParameterHolesopt TypeParameterDomainopt TypeParameterConstraintopt Note

A capture is declared once and may then be used any number of times, as a bare reference, and each further use requires the argument in its position to be the same: SameType for a type and SameValue for a value. Two declarations of one name are refused rather than read as an equality, since a reader could not tell whether equality or two unrelated bindings was meant. A capture's scope is the whole list, the specialized signature or heritage, and the body, so a use may precede its declaration, which keeps Map.<V: T, K: const T> meaning Map.<K: const T, V: T>.

A builder is evaluated forward and never run backwards, so a capture among its arguments would observe nothing, and a ComputedType is a builder call by its syntax alone: class S<Make(const T)> {} is refused before any name is resolved. A capture REFERENCE there is not refused, class S<const T, Make(T)> {}, since it reads a capture bound at a structural position and is checked forward. Whether some other position exposes a component depends on what its name denotes, and is the type error of 1.4.3.2.3.

1.4.3.2.2 Callable Overload Contracts

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 owner of its group whose list accepts it: each entry lands in one of the owner's parameters by position, a fixed argument there is of that parameter's kind and admitted by its domain and bound, and a capture or _ takes the parameter's domain. It borrows the owner's parameter names. A declaration no owner accepts, and one whose list mixes arguments with parameters, is standalone: its unlabeled positions are positional only, and it takes part only in ordinary overload resolution.

An attached declaration whose parameter list is the owner's instantiated at its entries (the same parameter types, optionality, rest element, and reference permissions; parameter names and initializers are not compared) is a replacement. It keeps the owner's contract, and is reached through the owner's generic function value. Its return type must be a subtype of the owner's instantiated return type. An attached declaration with another parameter list is additive, and is never reached through the owner's generic function value.

For both the parameter-list comparison and the replacement return-type obligation, instantiate the owner's contract by substituting the bindings of its parameters into the type positions that refer to those parameters. Preserve the identity of every referenced nominal declaration and any already established identity of its evaluation, including within composite and callable types. Substitution does not create a new nominal declaration or identify distinct declarations or evaluations. Within a nested generic callable type, preserve that callable's own binders; substitute only free references to the owner's parameters, including within the nested callable's parameter and result types. Identifier spelling alone does not establish parameter binding identity. Apply the ordinary type equality and subtyping rules to the resulting contracts. This substitution does not execute the owner's or attached callable's body or ordinary parameter initializers, grant reference authority, or extend reference lifetime.

An owner may omit its body. A bodyless owner declares only the labels and generic contract its attached declarations keep, and an application no attached declaration matches is then no viable overload. In a class declared 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 1.4.3.2's specificity; otherwise a standalone declaration whose list matches them, a position its list fixes being more specific than one an owner's parameter reaches; otherwise the owner's body, bound as any generic call binds it; otherwise there is no viable overload, a static type error where the arguments are known statically and a TypeError otherwise. Two applicable declarations neither of which is more specific than the other are an error at that application only, which names both and the arguments. Source order never decides. A declaration whose parameters cannot receive the call's arguments is not applicable, whatever its list admits: an additive declaration taking one more parameter than the owner is not the case of a call that supplies the owner's.

The fixed type fragments of a specialization list are declaration obligations, including those nested beside captures or wildcards. They are checked in the declaration's lexical environment with its parameters and captures in scope, whether or not an application of the declaration occurs and whether or not an enclosing body is invoked. A capture binding or wildcard is pattern syntax and is not itself evaluated as a closed type. References to bound parameters or captures may resolve symbolically; their presence does not exempt an independently checkable fixed fragment from its obligation.

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 1.6.2.10 to any retained answer.

After the dependencies required by such an obligation have been resolved, failure to resolve its type is a static type error in that fragment. A dependency not yet completed is not a failed resolution, and a dependent computation remains subject to the ordinary specialization rules. A failed resolution required by static selection is not an unsuccessful match and does not permit selection of a fallback. This applies to direct calls and stored applications as well as declaration checking; it does not require an open application to become concrete.

A named type argument reaches each candidate through that candidate's own labels: an attached declaration's are its owner's parameter names, a mixed declaration's are its parameters' names (its fixed arguments are positional), and a declaration whose list holds only fixed arguments, captures, and _ has none. A capture's name is never a label. A candidate that does not know a label is not applicable; a position a named application leaves open takes its parameter's default. A value parameter admits a literal that its domain can represent. It is an error, naming the label, if no candidate knows a label the application uses.

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 additive and standalone declarations, each by its own parameters. A replacement takes no part in that resolution itself: when an owner is chosen, its inferred binding selects among its replacements as an explicit application of those arguments would, and otherwise the owner's body runs, or there is no viable overload if the owner has no body. The call's contextual type does not filter out an owner whose replacements the resolution would reach, since the owner's return type is not the call's when a replacement is chosen; the call produces the chosen declaration's own return type.

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 owner is, as a value, that owner's generic function: a replacement is reached through it, an additive declaration never is, and it conforms to a function type as the owner does. A group whose declarations are all standalone is an ordinary overload set, and conforms to no generic function type.

An application inside a generic body whose arguments name that body's open type parameters is selected for each specialization when it is evaluated, and is checked once for every binding: through an owner that takes the arguments, against the owner's signature; or else through the one declaration whose list admits every binding the arguments' bounds allow, where a capture or _ 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 parameter list instantiated at its arguments. An application satisfying neither is a static error.

Standalone declarations whose lists hold only fixed arguments, captures, and _, of one length, are ordered by specificity as an owner's attached declarations are, a fixed position being more specific than an open one.

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 owner has a body.

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 owner's generic function, and a type's reflection is its structure, which reconstructs the type. The group's declarations are reflected from the group value: 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 owner's parameter name, borrowed by an attached declaration; a parameter's own name in a mixed list; or undefined for a position that takes none) and either its kind or its pattern's text. A capture's name is never a label. For an application's value, selected is the entry it selected, the owner's for its body. A selected declaration's value has that declaration's type with its captures bound.

Declarations told apart by their lists are not duplicate overloads for sharing parameter types: a replacement shares its owner's by definition. Their duplicates are judged by the applications their lists admit.

BodylessOwnerDeclaration[Yield, Await] : function BindingIdentifier[?Yield, ?Await] TypeParameters[?Yield, ?Await] ( FormalParameters[~Yield, ~Await] ) TypeAnnotation[~Yield, ~Await]opt ; BodylessOwnerMethod[Yield, Await] : ClassElementName[?Yield, ?Await] TypeParameters[?Yield, ?Await] ( UniqueFormalParameters[~Yield, ~Await] ) TypeAnnotation[~Yield, ~Await]opt ;

1.4.3.2.2.1 Static Semantics: Early Errors

BodylessOwnerDeclaration : function BindingIdentifier TypeParameters ( FormalParameters ) TypeAnnotationopt ; BodylessOwnerMethod : ClassElementName TypeParameters ( UniqueFormalParameters ) TypeAnnotationopt ;
  • It is a Syntax Error if TypeParameters holds anything but parameters.

A BodylessOwnerDeclaration is a function declaration of its declaration group, and a BodylessOwnerMethod a method of its class body. For each declaration group of a StatementList, ClassBody, or object literal, and each name declared in it by functions, methods, or class operators, it is an early error if:

  • A declaration whose list holds only fixed arguments, captures, and _ is accepted by more than one owner of the group.
  • A bodyless owner has no attached declaration in the group, and is not a method of a class declared abstract.
  • A replacement's return type is not a subtype of the owner's return type instantiated at its entries.
  • Two attached declarations of one owner admit the same applications, compared with captures canonicalized by binding identity, or two standalone declarations do.
  • A capture at the top of a standalone declaration's list has no written domain, since its position belongs to no owner whose domain it could take.

1.4.3.2.3 Capture Scope

The captures of a specialization list are collected before anything in it is resolved. Their scope is the whole list, the specialized declaration's signature or heritage, its constraints and where clauses, and its body, under the ordinary rules for class and static scope. Within that scope a bare IdentifierReference whose StringValue names a collected capture is a capture reference, including where it precedes the capture's declaration, and a capture shadows any outer binding of its name throughout the scope. The declared name, Pair in class Pair<const T, T>, is resolved before the capture scope is entered. A nested constructor, Map in Map.<string, const E>, is resolved within it, so a first-order capture named Map makes that application a type error rather than a lookup that falls back.

1.4.3.2.3.1 CollectCaptures ( list )

The abstract operation CollectCaptures takes argument list (a TypeParameters Parse Node or a type-subject pattern) and returns a List of Capture Records. It performs the following steps when called:

  1. Let captures be a new empty List.
  2. For each CaptureBinding c contained in list, in source text order, do
    1. Let variadic be true if c is the CaptureBinding of a ... CaptureBinding form, and false otherwise.
    2. Append the Capture Record { [[Name]]: the StringValue of the BindingIdentifier of c, [[Declaration]]: c, [[Variadic]]: variadic } to captures.
  3. Return captures.

A capture's domain is the domain of the position it declares, as 1.4.3.2 says: the primary declaration's parameter at the top level, and the resolved nested constructor's parameter within an application. It is a type error, at the declaration, for a written TypeParameterDomain to denote a type other than that domain, except in a metadata position; for a capture to stand in a position that exposes no component, such as an argument of an application whose name denotes no constructor; for a capture of kind constructor to occupy a first-order position; or for a capture whose position takes a value to carry a TypeParameterConstraint, since a value is narrowed by a where clause (4.10) and not bounded, as a value parameter is (1.4.3.1). The kind of a capture's position is known only once the primary or the nested constructor is resolved, which is why this is a type error where the parameter's own rule is a Syntax Error.

1.4.3.2.4 Matching a Specialization List

Matching compares a specialization list with the ordered bindings BindTypeArguments (4.1) produced for an application of the primary declaration. The primary is bound first, by explicit, named, and spread arguments, then the expected type, the value arguments, and the defaults, and the list is then matched against that result. So a capture's name is never an argument a caller can supply, and Box.<uint32, N: 8> and Box.<uint32, 8> meet the same specializations. Matching evaluates no capture as an argument and runs no code other than the forward computations described below. A match that fails binds nothing, and the next candidate starts from an empty environment.

A callable group containing a specialization case remains eligible for explicit type arguments even if its ordinary callable signatures have no type parameters. An unresolved case input does not establish that the callee is nongeneric, that a case matches, or that a default portion may replace a supplied portion. Retain the selection dependency until the required inputs are available, using established String or Symbol key identity for metadata captures. A completed no-match retains its ordinary failure, and a known nongeneric callable without such a group retains its type-argument error.

1.4.3.2.4.1 MatchSpecializationList ( list, bindings )

The abstract operation MatchSpecializationList takes arguments list (a TypeParameters Parse Node) and bindings (a List of Records with fields [[Declaration]] and [[Value]]) and returns either a normal completion containing either a List of Records with fields [[Capture]] (a Capture Record) and [[Value]] (a Type Record) or no-match, or a throw completion. It performs the following steps when called:

  1. Let env be a new empty List.
  2. Let deferred be a new empty List.
  3. Let runs be the GenericEntry Parse Nodes of list assigned to the primary declaration's parameters, one run per parameter, by the positional and variadic rules of BindTypeArguments, with no CaptureBinding or capture reference evaluated.
  4. For each integer k such that 0 ≤ k < the number of elements of bindings, in ascending order, do
    1. Let run be runs[k].
    2. If the primary parameter at k is variadic, then
      1. Match run against the elements of the tuple bindings[k].[[Value]] as MatchSpecializationPattern matches the elements of a tuple type, and return no-match if it does not match.
    3. Else,
      1. If run is empty, let pattern be the Type of the primary parameter's TypeParameterDefault, evaluated over bindings; otherwise, let pattern be the sole element of run.
      2. Let r be ? MatchSpecializationPattern(pattern, bindings[k].[[Value]], env, deferred, specialization).
      3. If r is no-match, return no-match.
  5. For each Parse Node d of deferred, do
    1. Let computed be ? the Type Record d denotes, evaluated over env by 1.2.7.
    2. If SameType(computed, the subject recorded with d) is false, return no-match.
  6. For each Record b of env, do
    1. If b.[[Capture]].[[Declaration]] has a TypeParameterConstraint, then
      1. Let bound be ? the Type Record its Type denotes, evaluated over env.
      2. If IsAssignable(b.[[Value]], bound) is false, return no-match.
  7. Return env.
Note

The entries are arguments of the primary as an application's are, so a variadic parameter of the primary takes a RUN of entries: with 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 wildcard, so with 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 capture bound at a later position. A computation that throws, or that exhausts the evaluation budget, is an error, not a failed match.

1.4.3.2.4.2 MatchSpecializationPattern ( pattern, subject, env, deferred, mode )

The abstract operation MatchSpecializationPattern takes arguments pattern (a Parse Node), subject (a Type Record), env (a List of Records with fields [[Capture]] and [[Value]]), deferred (a List of Parse Nodes), and mode (specialization or type-subject) and returns either a normal completion containing either matched or no-match, or a throw completion. It matches one position. mode is type-subject for a slotted type-subject pattern (26), which shares this operation and differs only in how a fixed leaf is compared. It performs the following steps when called:

  1. If pattern is the wildcard _, return matched.
  2. If pattern is a CaptureBinding or a capture reference, then
    1. Let c be the Capture Record the capture scope associates with pattern.
    2. If c is declared in a metadata position with a written TypeParameterDomain, then
      1. Let M be the meta type that TypeParameterDomain names.
      2. Let m be the metadata carried by subject, or undefined if it carries none.
      3. Set subject to MetadataPortion(m, M).
    3. If env contains a Record whose [[Capture]] is c, then
      1. Let b be that Record.
      2. If c is declared in a metadata position, then
        1. If subject and b.[[Value]] are equivalent metadata values (Table 43), return matched.
        2. Return no-match.
      3. If SameType(subject, b.[[Value]]) is true, return matched.
      4. If subject is a literal type, b.[[Value]] is a literal type, and SameValue(subject.[[Value]], b.[[Value]].[[Value]]) is true, return matched.
      5. Return no-match.
    4. Append the Record { [[Capture]]: c, [[Value]]: subject } to env.
    5. Return matched.
  3. If pattern is an application whose arguments contain a CaptureBinding, a capture reference, or the wildcard _, then
    1. Let C be the declaration the application's constructor denotes, resolved in the capture scope.
    2. If pattern is a metadata application of a primitive that declares no type parameters and subject is that bare primitive, treat subject as an application of C supplying one metadata position with no carried metadata for the following steps. This does not construct a parameterized Type Record.
    3. If subject is not an application of C, return no-match.
    4. Let slots be the arguments of pattern assigned to C's parameters by the positional, named, and variadic rules of BindTypeArguments, with each omitted parameter taking its default and with no CaptureBinding or capture reference evaluated.
    5. For each parameter position q of C, in order, do
      1. Let r be ? MatchSpecializationPattern(slots[q], the argument of subject at q, env, deferred, mode).
      2. If r is no-match, return no-match.
    6. Return matched.
  4. If pattern is an array or tuple type whose extent or elements contain a CaptureBinding, a capture reference, or the wildcard _, then
    1. If subject is not a Type Record of the same form as pattern, which is a tuple for a tuple type, an array with a fixed extent for an array type whose extent is written, and an array of dynamic extent for one whose extent is not, return no-match.
    2. Match the extent and each element of pattern against those of subject by MatchSpecializationPattern, a ... CaptureBinding binding the tuple of the elements its run covers under the variadic rules of BindTypeArguments, and return no-match if any does not match.
    3. Return matched.
  5. If pattern reads a capture, append pattern to deferred, recording subject with it, and return matched.
  6. Let fixed be ? the Type Record pattern denotes.
  7. If mode is type-subject, return matched if subject matches fixed by 22, and no-match otherwise.
  8. If SameType(subject, fixed) is true, return matched.
  9. Return no-match.
Note

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 capture is an equality under SameType, so Pair<const T, T> matches Pair.<uint8, uint8> and not Pair.<uint8, uint16>, and a repeated value capture compares by SameValue, so 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 wildcard makes a position structural as a capture does, so Map.<_, uint8> matches every Map whose value type is 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 capture, and is instead evaluated forward, as a deferred check, from captures bound elsewhere.

Callable selection, the relationship of a specialized callable to its owner, and the contract a callable replacement preserves are specified in 1.4.3.2.2. Class replacement contract preservation is specified in 4.11. Class replacement selection and replacement contracts for alias and interface families remain extension hooks. An implementation reports a specialization it cannot select as an error rather than accepting it and ignoring it.

1.4.3.3 Parameter Kinds

A parameter's kind is decided by how its domain is written: the Type type declares a type parameter, [].<type> on a parameter of the ... form a pack of types, and TypeParameterHoles a constructor. Any other domain declares a value parameter, and is judged by what it denotes, so an alias of a value type is a value domain as that type is. An alias of type is refused rather than read as type.

Note

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>.

1.4.3.3.1 ParameterKind ( p )

The abstract operation ParameterKind takes argument p (a TypeParameter Parse Node) and returns either a normal completion containing one of type, value, or constructor, or a throw completion. It is evaluated once, at the declaration; a domain that reads an earlier parameter is evaluated as a deferred application (1.2.7.2) and classified by its operator. It performs the following steps when called:

  1. Let written be the Type of the TypeParameterDomain of p.
  2. If p has a TypeParameterHoles, then
    1. If written is not the Type type, throw a TypeError exception.
    2. Return constructor.
  3. If p is of the ... form and written is the Type [].<type>, return type.
  4. If p is not of the ... form and written is the Type type, return type.
  5. Let d be ? the Type Record written denotes.
  6. If p is of the ... form, then
    1. If d is not an array or tuple Type Record, throw a TypeError exception.
    2. Set d to the element type of d.
  7. If d is the type type, throw a TypeError exception.
  8. If IsPermittedValueDomain(d) is true, return value.
  9. Throw a TypeError exception.
Note

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.

1.4.3.3.2 IsPermittedValueDomain ( d )

The abstract operation IsPermittedValueDomain takes argument d (a Type Record) and returns a Boolean. It performs the following steps when called:

  1. If d is a primitive Type Record other than the type type, return true.
  2. If d is an enumeration type or a literal Type Record, return true.
  3. If d is the Type Record of a union, then
    1. For each member m of d, if IsPermittedValueDomain(m) is false, return false.
    2. Return true.
  4. If d is a meta type's constraint shape, or an application of one (6.1), return true.
  5. If d is an array Type Record, return IsPermittedValueDomain(the element type of d).
  6. If d is a tuple Type Record, then
    1. For each element type e of d, if IsPermittedValueDomain(e) is false, return false.
    2. Return true.
  7. Return false.

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 (1.4.3.3), and an array's literal type is the tuple of its elements' literal types, so what a generic argument is identified by is that content and never the array's identity. It is what 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.

Note

A meta type is the one record-shaped value domain: its values are the normalized, immutable metadata records its hooks accept, compared by that facility's identity rather than by object identity, which is why 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.

1.4.3.3.3 EffectiveConstraint ( p )

The abstract operation EffectiveConstraint takes argument p (a Type Parameter Record) and returns a Type Parse Node or none. It returns the type an argument for p is checked against: a value parameter's domain, and a type or constructor parameter's bound. It performs the following steps when called:

  1. If p.[[Kind]] is value, return p.[[Domain]].
  2. Return p.[[Constraint]].

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 4.2.

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 Static Type if p.[[Kind]] is type, and the literal type of its value if p.[[Kind]] is value, which requires the argument to be a compile-time constant as every value argument must be. So 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.

1.4.3.4 Computed Constraints and Literal Inference

The Type of a TypeParameterConstraint or a TypeParameterDefault may be a ComputedType (1.4.2.4) that reads parameters declared earlier in the same TypeParameters. It is a type error for it to read the parameter it belongs to or one declared later in the list. The rule on a later parameter holds of every form a constraint or default takes, since the later parameter has no binding when this one's constraint is evaluated or its default substituted. The rule on the parameter's own name holds of every default, and of a constraint where the read is within a ComputedType, a KeyOfType or an IndexedAccessType, the forms that evaluate over their operand. A bound that names its own parameter as a type argument, as 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 any. A repeated structural constraint obligation may close a recursive description edge only when the evaluated interface declaration identity, parameter identity, candidate and preceding bindings are the same and the cycle traverses no executable type computation. The enclosing argument admission and all independent constraints remain required. A different argument or declaration, or a computation reached through an alias, is not discharged by that edge. Required computations and growing applications remain subject to 1.2.7.4. An abrupt completion clears unfinished entries without publishing a reusable result.

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 1.2.7 over the bindings so far, then that parameter is bound and checked against its evaluated constraint, and so on down the list. Within the declaration itself, before any parameter is bound, such a constraint is carried as the deferred application of 1.2.7.2.

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 (1.5.19) and holds for any constraint: the constraint is a contextual type, and 1.6.6 gives a literal in a position with one the type of that position. An UNCONSTRAINED parameter widens, which is the case its example describes. The binding is then checked against the constraint as any binding is, admitting a literal - or a union or tuple of them, which is what several arguments contribute - that fits it.

Note

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 value type binds a value, as the note above says of W: uint32; a constraint that is a union of literal types binds a literal type, whose value is recoverable from it. The design's kit reads that value back out constantly, which suggests these are one mechanism seen from two sides, a binding to the literal type with the value as its view, and the decision is that the literal-type binding is primitive with the value as its view, executed by the generics clause.

1.4.4 Type Annotations

An annotated rest parameter whose target is a BindingPattern uses the same collected-container contract as a named rest. The written annotation takes precedence over a contextual signature. After argument distribution and reference decay, enforce the collected array or tuple type, including element conversion and extent, before binding the pattern. Statically, pass that contract to the ordinary pattern contribution judgment; an object pattern reads indexed storage members, while an array pattern uses the iteration contribution and does not acquire a protocol guarantee from storage alone.

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 ReferenceError. Declarations in the function body are not in the parameter-initializer environment.

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.

TypeAnnotation[Yield, Await] : : Type[?Yield, ?Await] : BindingIdentifier[?Yield, ?Await] [no LineTerminator here] is Type[?Yield, ?Await] TypedInitializer[In, Yield, Await] : := AssignmentExpression[?In, ?Yield, ?Await]

A binding may carry an annotation, or a typed initializer that infers the binding's type from its initializer. The binding of a for-of or for-in head takes the same annotation, as for (const a: int32 of f()), and a BindingPattern in any of these positions annotates its elements through SingleNameBinding.

LexicalBinding[In, Yield, Await] : BindingIdentifier[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt Initializer[?In, ?Yield, ?Await]opt BindingIdentifier[?Yield, ?Await] TypedInitializer[?In, ?Yield, ?Await] BindingPattern[?Yield, ?Await] Initializer[?In, ?Yield, ?Await] VariableDeclaration[In, Yield, Await] : BindingIdentifier[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt Initializer[?In, ?Yield, ?Await]opt BindingIdentifier[?Yield, ?Await] TypedInitializer[?In, ?Yield, ?Await] BindingPattern[?Yield, ?Await] Initializer[?In, ?Yield, ?Await] ForBinding[Yield, Await] : BindingIdentifier[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt BindingPattern[?Yield, ?Await]

A parameter may carry an annotation, an optional marker, and a ref modifier.

FormalParameter[Yield, Await] : refopt BindingElement[?Yield, ?Await] SingleNameBinding[Yield, Await] : BindingIdentifier[?Yield, ?Await] ?opt TypeAnnotation[?Yield, ?Await]opt Initializer[+In, ?Yield, ?Await]opt BindingElement[Yield, Await] : SingleNameBinding[?Yield, ?Await] BindingPattern[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt Initializer[+In, ?Yield, ?Await]opt BindingRestElement[Yield, Await] : ... BindingIdentifier[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt ... BindingPattern[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt

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 array or tuple type rather than an element type, so ...args: [].<uint32> collects uint32 values into a [].<uint32>. It is a type error if the annotation does not RESOLVE to an array or tuple type, and the rule is on what it resolves to rather than on how it is written: a name, a type parameter and a computed type are each judged by what they denote. A type parameter used this way must therefore be constrained to [].<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 Parameter Record's [[Type]] holds that type, and the element type of a rest is the [[Element]] of an array type and the union of the [[Type]] fields of a tuple type's elements; an operation comparing a single argument against a rest compares against its element type. The production is restated because a rest reaches its binding through BindingRestElement rather than through SingleNameBinding, so without this the design's 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 BindingRestElement, including one in an array binding pattern or a nested parameter pattern. It is a declaration well-formedness judgment independent of the source's Static Iteration Contribution: an unknown source, element type or count does not validate an annotation of a different kind. In particular, any or object alone is not an array-or-tuple rest annotation; an unannotated rest and an array with [[Element]] any remain admitted. Resolve aliases and type parameters by the same rules as for formal rests, retaining the existing deferred type-resolution rules. Object rest retains its separate rule in 2.2. This judgment does not replace element, position or extent checks at known contributions or at run time.

A parameter list may hold more than one rest parameter, and a rest may appear anywhere in it rather than only last, which the pinned edition's FormalParameters does not admit. The productions are restated so that a rest is an ordinary element of the list.

FormalParameters[Yield, Await] : [empty] FunctionRestParameter[?Yield, ?Await] FormalParameterList[?Yield, ?Await] FormalParameterList[?Yield, ?Await] , FormalParameterList[?Yield, ?Await] , FunctionRestParameter[?Yield, ?Await] FormalParameterList[?Yield, ?Await] ,opt FormalParameterList[Yield, Await] : FormalParameter[?Yield, ?Await] FormalParameterList[?Yield, ?Await] , FormalParameter[?Yield, ?Await] FormalParameterElement[?Yield, ?Await] FormalParameterList[?Yield, ?Await] , FormalParameterElement[?Yield, ?Await] FormalParameterElement[Yield, Await] : FormalParameter[?Yield, ?Await] FunctionRestParameter[?Yield, ?Await] FunctionRestParameter[Yield, Await] : refopt BindingRestElement[?Yield, ?Await]

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 index type) — and any other use is the escape error a single 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 1.10.4.2 as any rest is.

Reference-run forwarding retains an occurrence independently of its expanded entries. Evaluate the written arguments once in source order and record the source boundary of every zero-length reference-run spread. The occurrence contributes no argument, Reference Value, argument rank or count. Include forwarding permissions in the named and positional binding judgment. Among assignments satisfying the ordinary type, reference and forwarding requirements, select the lexicographically greatest parameter-count List as in SequenceAssignment. Every non-empty forwarded entry must reach a reference-rest position. For an empty occurrence preceding all named contributions, require a reference rest whose selected positional interval contains the occurrence's boundary, including either endpoint or a zero-length interval. Groups supplied by names or by a named rest's returned suffix do not advance that positional cursor. After named contributions have begun, require the currently open named rest to be a reference rest; an empty occurrence does not reopen a rest closed by a fixed named contribution. Multiple qualifying zero-length intervals need no selection, since the occurrence assigns no value.

Apply this forwarding permission to the effective callable signature in view. A compatible implementation with individual reference parameters, or with no parameters for an empty reference-rest view, receives the already admitted actual argument sequence without a second requirement that its own declaration spell a rest. Without such a view, the actual selected signature supplies the contract. An ordinary value boundary, including a reflective built-in with no reference rest, does not admit the run even when it is empty. For an overload set, forwarding compatibility participates in each candidate's binding and viability; it contributes no additional ranking preference. A partition that violates forwarding permission does not exclude a candidate if another assignment satisfies all requirements. A forwarding failure is a TypeError exception before parameter initialization or callee body evaluation. Earlier argument effects and abrupt completions retain their ordinary order. Omitted positional arguments remain omitted, and final binding and later accesses retain all reference-liveness checks.

The forwarding permission is also a static judgment under 1.2.8. If the spread operand is established to name a reference-rest binding and the effective callable contract has no signature containing a reference-rest position, it is a type error even in an uncalled body and even if the run is empty or its length is unknown. An ordinary rest, individual reference parameters, and an empty parameter list do not supply that position. Use lexical binding identity and the effective substituted callable contract, including a callable view, rather than the implementation's parameter syntax. All parameter binding conventions are available while checking defaults, including later reference-rest bindings, without initializing them or changing their temporal dead zones. An unknown callable retains its runtime check; the presence of a potentially eligible overload signature prevents this absence proof but does not establish successful routing, admission or liveness.

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 contribution closes named-rest scope, while a potentially named spread makes that scope unknown until a later established name determines it again. A required callable iteration hook establishes positional routing without establishing a count; array or tuple storage and an optional hook do not by themselves establish that fact. Do not evaluate arguments, iterators, getters or initializers to decide this judgment. A length-independent negative proof may contract every forwarded run to a zero-width occurrence, relax collected extents and element admission, and ask whether any assignment to the remaining fixed, optional and rest positions admits every occurrence at a reference rest. Ordinary positional spreads range over all finite lengths. Account for permitted omission and possible named-rest suffix giveback when forming this relaxation. If even this relaxed routing has no solution, the call is a type error, including in an uncalled body. A solution to the relaxed judgment is not a proof of full admission, overload selection or liveness, and cannot replace their required checks.

UniqueFormalParameters and the arrow forms are defined over FormalParameters and so admit the wider list without restatement; the duplicate-name early errors of the pinned edition apply to it unchanged. It is a Syntax Error for a FunctionRestParameter to carry an Initializer, as it is today. Adjacent rest parameters must each carry a type annotation; a missing annotation in an adjacent pair is a Syntax Error. Each annotation must resolve to an array or tuple type, and it is a type error if either adjacent rest has an element type of any, including through an alias or a substituted type parameter. Adjacent constrained rests may have the same element type. Arguments are distributed by 1.10.4.2: the leftmost rest takes the greatest run that leaves the remaining parameters satisfiable, including required trailing parameters. Assignment preserves each reference argument and does not copy its referent or remove its reference marker.

Note 1

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 design has two such markers already, the declared types of the parameters and the named arguments of 1.10.4, and the adjacent-pair rule above excludes unrestricted neighboring runs. Greedy assignment determines the boundary even when constrained rests have the same element type.

A function may carry type parameters and a return annotation.

FunctionDeclaration[Yield, Await, Default] : function BindingIdentifier[?Yield, ?Await] TypeParameters[?Yield, ?Await]opt ( FormalParameters[~Yield, ~Await] ) TypeAnnotation[?Yield, ?Await]opt { FunctionBody[~Yield, ~Await] } [+Default] function TypeParameters[?Yield, ?Await]opt ( FormalParameters[~Yield, ~Await] ) TypeAnnotation[?Yield, ?Await]opt { FunctionBody[~Yield, ~Await] }

A class field may carry an annotation.

FieldDefinition[Yield, Await] : ClassElementName[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt Initializer[+In, ?Yield, ?Await]opt Note 2

The positions above are representative rather than exhaustive: the remaining function forms, including methods, arrow functions, generators, and class constructors, take the same annotations and are patched by the clauses that specify them (1.10.8). An arrow function's parameter list and return annotation interact with the arrow cover grammar and are given by 1.10.7.

ref before a parameter is a contextual keyword. A parameter named 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 BindingPattern and no Initializer — are stated with the rest of the borrowing syntax in 2.1.

1.4.5 Types in Expression Position

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.

UnaryExpression[Yield, Await] : UpdateExpression[?Yield, ?Await] delete UnaryExpression[?Yield, ?Await] void UnaryExpression[?Yield, ?Await] typeof UnaryExpression[?Yield, ?Await] + UnaryExpression[?Yield, ?Await] - UnaryExpression[?Yield, ?Await] ~ UnaryExpression[?Yield, ?Await] ! UnaryExpression[?Yield, ?Await] [+Await] AwaitExpression[?Yield] TypeOperatorExpression[?Yield, ?Await] TypeOperatorExpression[Yield, Await] : type [no LineTerminator here] Type[?Yield, ?Await]

A conversion converts the value of its left operand to a type, and is written after the value.

RelationalExpression[In, Yield, Await] : ShiftExpression[?Yield, ?Await] RelationalExpression[?In, ?Yield, ?Await] < ShiftExpression[?Yield, ?Await] RelationalExpression[?In, ?Yield, ?Await] > ShiftExpression[?Yield, ?Await] RelationalExpression[?In, ?Yield, ?Await] <= ShiftExpression[?Yield, ?Await] RelationalExpression[?In, ?Yield, ?Await] >= ShiftExpression[?Yield, ?Await] RelationalExpression[?In, ?Yield, ?Await] instanceof ShiftExpression[?Yield, ?Await] [+In] RelationalExpression[+In, ?Yield, ?Await] in ShiftExpression[?Yield, ?Await] [+In] PrivateIdentifier in ShiftExpression[?Yield, ?Await] RelationalExpression[?In, ?Yield, ?Await] := Type[?Yield, ?Await] RelationalExpression[?In, ?Yield, ?Await] [no LineTerminator here] is MatchPattern[?Yield, ?Await] Note

Placing a conversion at RelationalExpression gives it the precedence of instanceof, so a + b := uint8 converts the sum, and the right operand being a Type means x := A | B converts to the union rather than taking the bitwise or of a conversion.

type is a contextual keyword, since a program may already bind the name 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 1.4.2.4 do not widen it: type f(x) was a syntax error and is now the operator applied to a computed type, so the only covered case is an operand that begins with (. 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 covered case rather than change the meaning of any program the cover grammar already accepts, and is a decision worth taking before the syntax is final. It does not reach the two operands below, which stay ambiguous however the operand is restricted.

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 (1.5.19), and a subtraction. Unlike the parenthesized case the two readings do not diverge at a later token: each is complete and ends where the other does, so a refinement has nothing to look at and the ambiguity must be decided rather than covered. It is decided here in the operator's favour: 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 early error for such an operand to appear where the name type is also bound, so that the program is refused rather than reinterpreted.

The operand of type is a full Type and so extends as far as one reaches: 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 Type production defined here, sharing this precedence; is takes the wider MatchPattern (26), of which a Type is one form, so x is A | B tests the union exactly as before.

is is a contextual keyword rather than a reserved word, and the [no LineTerminator here] restriction is what that costs. 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.

1.4.6 Type Arguments and Placement new in Expression Position

A specialization such as f.<uint8>(x) and a placement allocation such as new(buffer, byteOffset) Type(0) are member-expression forms. The productions below restate MemberExpression and CallExpression with the added alternatives (1.1).

MemberExpression[Yield, Await] : PrimaryExpression[?Yield, ?Await] MemberExpression[?Yield, ?Await] [ Expression[+In, ?Yield, ?Await] ] MemberExpression[?Yield, ?Await] . IdentifierName MemberExpression[?Yield, ?Await] TemplateLiteral[?Yield, ?Await, +Tagged] SuperProperty[?Yield, ?Await] MetaProperty new MemberExpression[?Yield, ?Await] Arguments[?Yield, ?Await] MemberExpression[?Yield, ?Await] . PrivateIdentifier MemberExpression[?Yield, ?Await] TypeArguments[?Yield, ?Await] new ( AssignmentExpression[+In, ?Yield, ?Await] ) MemberExpression[?Yield, ?Await] Arguments[?Yield, ?Await] new ( AssignmentExpression[+In, ?Yield, ?Await] , AssignmentExpression[+In, ?Yield, ?Await] ) MemberExpression[?Yield, ?Await] Arguments[?Yield, ?Await] new ( AssignmentExpression[+In, ?Yield, ?Await] , AssignmentExpression[+In, ?Yield, ?Await] , AssignmentExpression[+In, ?Yield, ?Await] ) MemberExpression[?Yield, ?Await] Arguments[?Yield, ?Await] new . Arguments[?Yield, ?Await] CallExpression[Yield, Await] : CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await] SuperCall[?Yield, ?Await] ImportCall[?Yield, ?Await] CallExpression[?Yield, ?Await] Arguments[?Yield, ?Await] CallExpression[?Yield, ?Await] [ Expression[+In, ?Yield, ?Await] ] CallExpression[?Yield, ?Await] . IdentifierName CallExpression[?Yield, ?Await] TemplateLiteral[?Yield, ?Await, +Tagged] CallExpression[?Yield, ?Await] . PrivateIdentifier CallExpression[?Yield, ?Await] TypeArguments[?Yield, ?Await]

A MemberExpression or CallExpression followed by TypeArguments evaluates the expression and specializes the result by the listed types. The value must be generic: a generic function, a generic class, or a parameterized type; the arguments bind by BindTypeArguments (4.3) — positional, named, and spread alike — and the substitution is that of 4.7, of which this production is the explicit written form. For a generic function the result is the specialization as a value (4.5): one interned function object per function and ordered bindings, whose 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 Static Type shows a value that is not generic it is a type error, and otherwise a specialization of a non-generic value throws a TypeError exception. The OptionalChain productions are not extended; a specialization does not follow ?..

When an expression-position application has an established generic interface, alias or class-constructor origin, it performs the same argument-assignment, kind, constraint and default-demand judgments as a type-reference application. These judgments are required even when the application's value is stored or discarded. A statically established failure is a type error at the existing checking boundary, including in an unused function or an explicitly unreachable branch. Retain an application whose determining inputs are pending and complete its obligations when those inputs become available, including through enclosing specialization. A supplied pending argument is not omitted and does not select its default.

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 specialization, execute a static initializer, initialize an unavailable binding, or establish reference liveness. Purely static judgments retain their existing parsing and linking phases; judgments requiring type evaluation retain the availability, evaluability, budget and failure ordering of 1.2.8 and 1.3.8. Dynamic applications retain their runtime admission and ordinary effects.

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. Expansion neither removes the alias's argument obligations nor makes its value have the Static Type of the type it denotes; that value has Static 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 1.3.8.

The retained declaration context includes the original environments of helpers used by the alias's constraints, selected defaults and specialization predicates. At the consumer's required checking boundary, use producer bindings initialized by ordinary module evaluation; do not treat them as pending consumer-local initializations or reconstruct their values from initializers. Evaluability remains a separate requirement. Retain applications appearing inside an alias body independently of its expanded result, including when the applied declaration is imported from another module. On substitution, discharge each inner application's bound and predicate obligations under its own resolved declaration, source module and completed arguments. An incomplete argument set is not a closed predicate check. These requirements preserve transparent alias identity, existing deferral rules, ordinary dependency effects and reference liveness.

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 field of each instance at its laid-out position (Table 39, memory layout); a third argument larger than 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 RangeError exception when the extent so computed exceeds the buffer's length. Over a resizable buffer the extent is recorded: accessing an instance while the buffer is smaller than that extent throws a TypeError exception, regrowth covering the extent restores access, and growing never invalidates. Actual buffer detachment is permanent. Apply the current-bounds and nested-source rules in 12.

Each written placement AssignmentExpression and its nested bodies is subject to the ordinary type-error judgments in its lexical, generic, private-name and reference context. Placement syntax does not exempt a subexpression from checking, even in an unused body. This requirement adds no static buffer-category, offset, layout or extent judgment. Evaluation order and multiplicity, runtime extent checks, detachment and reference liveness are unchanged.

Note

new ( begins both a placement form and an ordinary new whose constructor is parenthesized. Source that continues with a MemberExpression after the closing parenthesis is the placement form; under the base grammar such source has no parse as a single expression, so the added reading changes no existing program. The array form of the design, new(buffer, byteOffset, byteElementLength) [10].<Type>(0), needs no further production: [10].<Type> is an ArrayLiteral specialized by TypeArguments, which the alternative above already admits as the target MemberExpression.

new . Arguments is TARGET-TYPED CONSTRUCTION: it constructs the type its position requires, so the type is not named. It is told apart from new.target by the token after the dot, an Arguments against an IdentifierName, and from both forms above by the dot itself; under the base grammar 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 contextual type is absent. A Syntax Error rather than an inference, because inferring the type would be the binding-type inference this proposal does not perform. It is a type error where the contextual type is not constructible, as in 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 constructor's parameter.

1.5 The Type Universe

This clause adds the value types of this proposal to the ECMAScript language types of 6.1. For each type it fixes the set of values the type contains and the Type Record (1.2.1) that describes it. The operations over these values, the conversions between them, and the literals that produce them are specified by later clauses. This clause settles only what the values are and what the types are called.

1.5.1 Value Types

A value type is a type whose values are ECMAScript language values that are not Objects. A value of a value type has no identity: two values of the same value type that are the same value are indistinguishable, and nothing observable distinguishes one occurrence of such a value from another. The existing primitive types are value types in this sense, and every type this clause adds is one.

The values of distinct value types are distinct. Where two types are described below as containing the same mathematical values, each contains its own values representing them: the 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 1.7.1.1 report it.

Every type added here is described by a Type Record whose [[Kind]] is primitive, whose [[Name]] is the String naming the type, and whose [[Arguments]] is the List of its type and value arguments. uint8 is described by the Type Record { [[Kind]]: primitive, [[Name]]: "uint", [[Arguments]]: « 8 » }, and vector.<float32, 4> by { [[Kind]]: primitive, [[Name]]: "vector", [[Arguments]]: « the Type Record for float32, 4 » }.

Note

Because Type Records are canonicalized and interned (1.2.2.9), a type written two ways is one Type Object: int8 and int.<8> name the same Type Record and therefore compare strictly equal. The shorthand names of 1.5.27 are consequently aliases rather than separate types.

1.5.2 Integer Types

For each positive integer N, int.<N> is a value type whose values are the integers from -2N-1 through 2N-1 - 1 inclusive, and uint.<N> is a value type whose values are the integers from 0 through 2N - 1 inclusive. Each has exactly 2N values. Neither has a negative zero, an infinity, or a NaN.

N is a value argument, so int.<8> and int.<16> are distinct types, and the width of a type is recoverable from its Type Record.

Note

N is bounded: it lies in the inclusive interval from 1 to 216, and a width outside it is a type error. The bound is a specified constant like the budgets, generous enough for any hashing or cryptographic width and small enough that a computed type cannot demand an unbounded layout for one Type Record of cost. The design gives shorthands only through 128 and uses 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 set, to the powers of two, or to a range up to 128 are all defensible; the choice belongs with the committee, and it interacts with the bit-field widths of the memory layout extension, which are the reason a width such as 3 is meaningful at all.

1.5.3 Binary Floating-Point Types

float16, float32, float64, and float128 are value types whose values are those of the corresponding IEEE 754-2019 binary interchange formats, with the same treatment of NaN the Number type receives: the many NaN encodings of the format are represented as a single NaN value. Each therefore has 2w - 2p + 3 values, where w is the width of the format in bits and p its precision in bits, as given in Table 9.

Table 9: Binary Floating-Point Types
TypeIEEE 754 formatWidth wPrecision pValues
float16binary16161163491
float32binary3232244278190083
float64binary64645318437736874454810627
float128binary1281281132128 - 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 Number type does.

Note

float64 and the Number type have the same set of values but are distinct types, so neither is assignable to the other without a conversion. This is the rule stated for every pair of value types and is not a special case: Number is the type an untyped program computes with, and float64 is the type a program asks for when it wants that format by name.

1.5.4 Decimal Floating-Point Types

decimal32, decimal64, and decimal128 are value types whose values are those of the corresponding IEEE 754-2019 decimal interchange formats. A finite value of a decimal type is a triple of a sign, an integer coefficient, and an exponent, drawn from the ranges in Table 10. Each type additionally has a positive infinity, a negative infinity, and a single NaN.

Table 10: Decimal Floating-Point Types
TypeIEEE 754 formatWidthCoefficient digitsExponent range
decimal32decimal3232 bits7-101 through 90
decimal64decimal6464 bits16-398 through 369
decimal128decimal128128 bits34-6176 through 6111

An arithmetic result whose exponent falls outside the range of its type is a RangeError exception. This is where a decimal parts company with a binary float, which saturates to an infinity instead: a decimal's range is a property of the TYPE rather than of the format, so a result the type cannot hold has no value of the type to be, and an infinity would claim the operation merely grew large rather than left the type behind. The same rule governs a conversion into a decimal (Table 19), so a value arrives out of range by no route.

Two finite values of a decimal type that have the same numerical value but different exponents are distinct values of the type. The values written 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 set of values a cohort.

Note

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 design settles two of the three: == compares numerical value, so 1.0 == 1.00 is true, and SameValueZero compares numerical value, so 1.0 and 1.00 are one key in a Map or a Set rather than two. Identity follows the representation: SameValue distinguishes the cohort members 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 SameValue the way the paragraph above already uses it: SameValue distinguishes cohort members, so Object.is(1.0, 1.00) is false for two decimal128 values of different exponents, while SameValueZero and == compare numerical value and find them equal. This is the split the design records for ===/== versus identity, and it is the parallel with negative zero, where SameValue distinguishes +0 from -0 and SameValueZero does not. The alternative, SameValue comparing numerical value, was rejected because it would make Object.is unable to distinguish values that print differently, which no other type permits; its cost, that SameValue and SameValueZero then differ on a case unrelated to zero, is the same cost negative zero already imposes and is judged the lesser one. This remains a decision the committee owns, recorded here because the type universe is where the cohort becomes observable.

An operation with a decimal NaN operand gives the NaN, and one with an infinite operand gives the IEEE 754-2019 result; an operation IEEE 754-2019 signals as invalid, such as the difference of two infinities of one sign or the product of zero and an infinity, and one that would make a NaN or an infinity from finite operands, throws a RangeError exception. NaN and the infinities therefore arrive by conversion and from the bytes of an encoding, and the arithmetic carries them without making them.

1.5.5 Rational Types

For each positive integer N, rational.<N> is a value type whose values are the exact rational numbers representable as a quotient of two values of 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 RangeError rather than rounding.

rational.<bigint> is a value type whose values are all the rational numbers, each as a quotient of two bigint values in lowest terms with a denominator greater than 0. No value is outside it, so an operation on it throws no RangeError for want of range; a zero denominator is still one. It has no layout, as 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 integer literals separated by /, the second unsigned: 0.1 is 1/10 and 3/4 is 3/4, as a literal in a rational position denotes. A literal whose denominator is 0 is a literal whose value no rational type can represent, so parse throws a RangeError for it, as the constructor does for a zero denominator.

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 RangeError if x is not finite, if maxDenominator is less than 1, or if x lies outside the range of 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.

Note

Normalizing on construction is what makes the type a value type in the ordinary sense: without it, one half would have many representations and equality would have to normalize before comparing. It is also what makes the RangeError unavoidable, since a sum of two representable rationals can have an unrepresentable normalized denominator. Refusing to round is the distinction between this type and a float; a rational that cannot be represented exactly is an error, not an approximation.

1.5.6 Complex Types

For a value type T that is number or one of the binary floating-point types, complex.<T> is a value type whose values are the ordered pairs of a real part and an imaginary part, each a value of T. The number of values of complex.<T> is the square of the number of values of T.

Note

The width-named shorthands of 1.5.27 count total bits rather than component bits, following the convention of NumPy and Go, so complex64 is a pair of float32 and not a pair of float64.

1.5.7 Vector Types

For a value type T that is an integer, binary floating-point, or vector type, or a metadata parameterization of one, and a positive integer N, vector.<T, N> is a value type whose values are the sequences of exactly N values of T. T is the lane type and N the lane count. The number of values of vector.<T, N> is the number of values of T raised to the power N.

Note

A vector's lane type may itself be a vector, which is how the boolean vectors of the design are built: boolean8 is vector.<boolean1, 8>, where boolean1 is the design's name for 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 type error to form vector.<T, N> where T is not a lane type or N is not a positive integer, since neither denotes a vector type. Where the lane type is itself a vector, this applies to it in turn.

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.

1.5.8 Vector Lanes

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 compile-time constant the choice is checked, and where it is an expression it is not; a design uses the first wherever it can (design, simd.md).

For a vector type vector.<T, N>:

  • lane.<I>() has the Static Type T. It is a type error if I is not a non-negative integer less than N.
  • withLane.<I>(value) has the Static Type vector.<T, N>, and takes a value of type T. It is a type error if I is not a non-negative integer less than N. Its value is the receiver's, with lane I replaced by value; the receiver is unchanged, since a vector is a value type (1.5.7).
  • A member access with a computed key of type uint32 has the Static Type T, and an assignment to one takes a value of type T. The key is an expression, so no static rule bounds it: reading or writing a lane whose index is not less than N throws a RangeError.
Note 1

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 design's rule is to write the constant form wherever the index is a constant, and the specification's part is only that the two differ in when the bound is enforced.

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 value type, and is stated because a reader arriving from an array is likely to expect otherwise.

Note 2

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.

Note 3

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 1.2.8 where the count is known, and a spread of a tuple of stated extent contributes its extent to the count. 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 1.6 rather than a rule of its own. A value whose type is not the lane type does not convert: for a float32 s, float32x4 accepts s and float64x2 does not, and a design that wants the second converts s to 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 (1.9.4); a scalar of another numeric type is a type error there. A vector type called with its lanes constructs the vector, taking one argument per lane or one argument broadcast to every lane, and each argument is judged at the lane type as an argument is: a literal the lane type cannot represent is a type error (1.6.6), so uint8x16(300, …) is refused rather than wrapped. A single argument that is itself a vector is an explicit conversion instead (1.6.7), and a single argument to a vector of 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 integer, counting from the least significant. The conversion between such a vector and an integer type is that correspondence read in each direction. An integer value converted to the vector type gives the vector whose lane i is bit i of the value, and a value of the vector type converted to an integer type gives the integer whose bit i is lane i.

Note 4

This is what makes a bit vector a bitfield rather than a name for a byte. A boolean8 initialized from 0b00000010 has lane 1 set and the rest clear, so a[1] is 1 and a[0] is 0; assigning 1 to lane 3 and reading the value back as an integer gives 0b00001010.

The numbering is the one 19 states for the masks a comparison produces, and it is stated here as a rule because that clause states it as a remark about hardware. A bit vector converts whether or not a comparison produced it.

A bit vector is not a bit-field. The sub-byte integer members a class may declare, with their own bit order and offsets, are 9's and are a different feature: they pack members of differing widths into a layout, where a bit vector is N lanes of one width that permute, compare, and select like any other vector. The two are stated in different clauses because they answer different questions, and a reader meeting boolean8 here is not meeting the other.

sum() has the Static Type T and is the sum of the receiver's lanes. Its result is that of adding the lanes in an implementation-defined order, which for a binary floating-point lane type is observable: addition is not associative there, so two implementations may return different values for one receiver.

Note 5

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 design needing a fixed order writes the fold it wants over lane.<I>().

1.5.9 Vector Permutation

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 (design, simd.md).

For a vector type vector.<T, N> and indices I₁ through IL, each a compile-time constant:

  • swizzle.<I₁, …, IL>() has the Static Type vector.<T, L>. Lane j of the result is lane Ij of the receiver. It is a type error if any Ij is not a non-negative integer less than N, or if L is not a positive integer.
  • shuffle.<I₁, …, IL>(other) has the Static Type 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 type error if any Ij is not a non-negative integer less than 2_N_, or if L is not a positive integer.

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 Static Type vector.<T, 2>.

Note 1

Each index is a compile-time constant because that is what the instruction this form compiles to takes: the lane selector of 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 design (simd.md) rather than an omission from this clause.

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 implementation-defined, because upholding zero on x86 costs instructions. Each answer has been chosen by a real design and the hardware disagrees with itself, so whichever this specification chose would be extra work on some target — which is why the operation waits on a decision rather than on a clause.

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 1.5.10 name only a vector's first four lanes.

Note 2

The design writes the index list as a variadic value generic parameter, swizzle<...I: [].<uint32>>(), whose length gives the result's lane count. Value generics and their variadic form are specified by 4 and 4.4; the rules above are what that spelling means.

1.5.10 Vector Component Accessors

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 (design, simd.md). 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 property key is a component accessor of a receiver of type vector.<T, N> when all of the following hold:

  • N is at most 4.
  • The key is a sequence of 1 to 4 characters, each drawn from xyzw or each drawn from rgba. A key mixing the sets is not a component accessor.
  • Each character's index within its set is less than N.

Such an accessor has the Static Type T for a key of one character, and vector.<T, L> for a key of L characters. L is independent of N: a two-lane vector's xyxy has the Static Type vector.<T, 4>.

An accessor whose key names no lane twice is assignable, and takes a value of the accessor's own Static Type. An accessor whose key names a lane twice is not assignable, since an assignment to it would give one lane two values; it is a type error to assign to one.

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 type error. This includes compound assignment and a statically known computed key. A repeated-lane read remains valid; an ordinary object property with the same spelling retains its declared permissions. Unknown receivers or keys retain their runtime checks. This rule does not grant a component a reference capability.

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 1.5.8 applies to a computed key of type 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.

Note 1

A vector of more than four lanes has no component accessors, because the two sets have four characters each. Such a vector permutes with 1.5.9, which is why that clause takes indices rather than names.

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 set for them: Rust and Java give wide vectors indices and permutations and no names at all. Extending a set would collide, since 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 design writes the third set GLSL provides, 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 shape, so:

  • v['xyz'] reaches the same value the dotted access does, since the key is looked up rather than parsed.
  • Reflection over the object a vector converts to sees the accessors: 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.
Note 2

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 value type. A String behaves the same way — '0' in 'abc' is a TypeError though '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 ordinary object, at the cost of several hundred prototype names that 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:

  • A read through a key of one character is lane.<I>() for that character's index I (1.5.8).
  • A read through a key of L characters is swizzle.<I₁, …, IL>() for their indices (1.5.9).
  • An assignment through an assignable key replaces the named lanes with the corresponding lanes of the assigned value, leaving the receiver's other lanes unchanged.
Note 3

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.

1.5.11 Lane-wise Math

The function properties of the Math object apply lane-wise to a vector argument, returning a vector of the argument's shape. Where a function takes more than one argument, the arguments are vectors of one shape and the function applies at each lane; a scalar argument beside a vector one broadcasts (1.5.8).

The set divides by whether the scalar function is exactly specified. 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 implementation-approximated as their scalar forms are, and the vector form is approximated independently of the scalar form: lane j of the vector result is not required to equal the scalar function applied to lane j.

Note

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.

1.5.12 Mask Operations

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.

19 states what a comparison between vectors produces. This clause states what consumes one. A mask is a vector whose lane type is uint.<1>, which is what the design names booleanN (1.5.8), and a vector whose lane type is itself such a vector is a mask too: that is the wide form a comparison yields, so a 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 Static Type boolean, and is true when every lane is set.
  • any() has the Static Type boolean, and is true when at least one lane is set.
  • select(whenSet, whenClear) takes two vectors of one type vector.<U, N> and has that Static Type. Lane j of the result is lane j of whenSet where lane j of the receiver is set, and lane j of whenClear otherwise.

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.

Note 1

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 shape of the call, is what bounds the permission. 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 denotes them, and a per-operation mask parameter would give every operator a second spelling for what it already says.

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.

Note 2

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 design that must not evaluate one side writes a branch.

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 shape, so m.x is a lane of it and m.xyxy is a four-lane mask.

1.5.13 Vector Widths

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 compile-time constant, so a type may be formed from it and a generic declaration may specialize on it. Its value is host-defined.

Note 1

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 Type Object and already carries statics — 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 hosts, so it is the one place a program's TYPES may differ between hosts: a 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 design that needs one type across hosts writes the lane count rather than asking for it. The determinism this specification fixes elsewhere is determinism for a given host (1.2.7.4), and this operation does not weaken it: two evaluations on one host agree.

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.

Note 2

Gather, scatter, and masked load and store are not specified. The design defers them (simd.md), and they are named here so that their absence reads as a boundary rather than an omission: they are the operations that vectorize an indirect access, and they need an addressing model this specification does not have.

1.5.14 Wrapping a Vector

A type alias of a vector type is that vector type, so a value of it has the component accessors of 1.5.10 and the operations of the clauses above. A class holding a vector as a member is a distinct nominal type, and does not have them: a class does not acquire the members of its own field's type.

Note

This follows from the rules for aliases and classes rather than adding one, and is stated because it decides a design question the design document poses. 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.

1.5.15 The any Type

any is the type of every value. Every ECMAScript language value is a value of the any type, and a binding whose type is any accepts any value. It is described by the Type Record { [[Kind]]: any }, and it is the type of a binding that carries no annotation, which is what makes an untyped program a program whose bindings are all of the any type.

1.5.16 The void Type

void is the type with no values. It is described by the Type Record { [[Kind]]: void }, and it is written as the return type of a function that returns no value. No value is a value of the void type, so no binding may have it.

It is a type error if an actual binding declaration resolves to the 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 type error. A parameter name in a function-type signature does not itself declare a runtime binding. This rule does not prohibit 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 type error for a program to depend on an expression whose Static Type is void: for it to be a test (1.8.5), the left operand of &&, || 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.

Note 1

A call of a function whose return type is void evaluates to undefined, as a call of a function with no 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.

Note 2

The sentence is about the FUNCTION'S OWN return type, and it holds because such a function cannot return a value at all: 1.7.8 refuses a body that contradicts its annotation, so every call it describes already answers undefined.

A call reaching a function through a void-returning TYPE is a different case. 1.2.3.1 makes a 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 Static Type of _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 checking pass to compute a Static Type for EVERY call, including the ones no position asks about, since a call whose value nothing wants is exactly where the erasure would have to act; and it would not close the gap, because 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 against a type with no values would refuse one and admit the other. undefined is the type to write for a function that returns the value undefined and whose caller may hold it (1.5.17).

1.5.17 The null and undefined Types

null is the type whose one value is null. It is described by the Type Record { [[Kind]]: primitive, [[Name]]: "null", [[Arguments]]: « » }, and it is written with the PredefinedType null.

undefined is the type whose one value is undefined. It is described by the Type Record { [[Kind]]: primitive, [[Name]]: "undefined", [[Arguments]]: « » }, and it is written with the PredefinedType 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 undefined and a binding may hold it.

Note

These are the types 1.7.1.1 reports for null and undefined. The reject type of a typed promise is 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 RuntimeTypeOf in agreement, and an implementation of this specification did so. They agree on a type with no values, so undefined is not a value of the type its own RuntimeTypeOf reports, and with it every uint32 | undefined becomes a union no value can inhabit. The agreement to keep is at the undefined type: RuntimeTypeOf(undefined) is undefined, the name denotes that same type, and void remains a return type that no binding may hold.

1.5.18 The never Type

never is the type with no values: the union of no members, described by the Type Record { [[Kind]]: union, [[Members]]: « » }. It is not a new kind of type; it is the empty case of a kind the type system already has, admitted as a type in its own right because computation reaches it regardless. A union filtered of its every member is this type, whatever this clause says.

Expression-position access to it is the type operator, type never, as it is for every other type name (1.5.27). No separate name is provided for it: a 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 IsSubtype (1.2.3.1) a source of the union kind is a subtype when every member is, and a quantification over no members holds.
  • No type other than never is a subtype of never. A target of the union kind requires some member to accept the source, and there is none.
  • never is the identity of union. Canonicalization (1.2.2.8) flattens a member of the same kind into its parent, and flattening an empty union contributes no members, so 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 CanonicalizeType carries the step for it.
  • An intersection two of whose members are disjoint (1.2.2.6) is never, which is the general case of the preceding: a never member is disjoint from every member, itself included. CanonicalizeType carries this step too, so 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 (1.4.2.1).

No value is a value of the never type: IsOfType (1.7.6.1) over a union with no members is false for every value, and DefaultValueOf (1.8.3.1) of it is none, so a binding of the type can be neither defaulted nor, since no expression's value can satisfy it, initialized. A value of the any type reaching a position of the never type fails its boundary check (1.7.7.1) for every value. That vacuity is the point: never exists to be computed with, as the result of removing every member from a union, not to be inhabited.

Note

Because the empty union canonicalizes to itself and Type Objects are interned (1.2.2.9), every computation that produces the empty union produces one Type Object, so never has the same identity story as every other type. The name is admitted through 1.5.27 on the same terms as every other built-in type name, so 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 enums and sealed hierarchies (1.11.9). It is also distinct from the empty sentinel of the narrowing operations (1.8.5), which reports a test that cannot succeed as a mistake rather than narrowing to the type with no values.

1.5.19 Literal Types

A literal type has exactly one value. It is described by the Type Record { [[Kind]]: literal, [[Value]]: v, [[Base]]: b }, where v is a String, Number, Boolean, BigInt, or Symbol and b is the Type Record of the existing language type of which v is a value. A LiteralType (1.4.2) denotes the literal type of the value it writes: a StringLiteral with base string, a NumericLiteral with base 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 LiteralType may write either, negated or not: Table 43 admits any Number as a metadata value and says that a NaN is equivalent to a NaN, so a meta type whose constraint is a bound could otherwise not state its own default in a parameterization. A Symbol has no literal syntax, so no LiteralType writes a symbol literal type: one arises through construction (1.12.5) or through keyof over a symbol-keyed object type (1.4.2.5), and a program names it with an ordinary binding.

The one value of a literal type is a value of its base, so the type is a subtype of its base (1.2.3.1), '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 set the design's discriminated unions select on, and keyof produces one (1.4.2.5). 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-set equality is the road to deciding language equivalence, which the pattern judgment already declines.

A literal expression whose contextual type (1.6.5) is a literal type, or a union or intersection with a literal type among its members, takes the literal type of its own value where that literal type is assignable to the contextual type, so let a: 'on' | 'off' = 'on'; gives a the literal type 'on'. A contextual type of any other shape leaves the literal to the rules already given for untyped literals, so let s: string = 'on'; still gives s a string and let x: uint8 = 5; is unchanged.

Note

The base of a numeric literal is number however the literal will be used, matching the Static Type a literal takes with no contextual type; a literal type over another numeric type, 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 value type gives a literal argument the literal type over that base, the contextual rule above extended to the constraint's context, so 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 SameValue, which for a Symbol is identity, so a symbol's identity is already its type's identity and the unique symbol of an erased system has nothing left to do. Any Symbol qualifies, well-known, registered, or freshly created, and no declaration form distinguishes them because none is needed. A symbol literal type is reproducible outside its Agent exactly when its Symbol is: one from Symbol.for names itself by its key, while one from Symbol is Agent-local, so a type carrying it cannot be shared, and the extension that shares types across Agents reports that rather than inventing an identity (Table 39).

1.5.20 Parameterized Types

A metadata parameterization refines a base type with a metadata value: the Type Record { [[Kind]]: parameterized, [[Base]]: b, [[Metadata]]: m }. Over a primitive base it is written float32.<{ m: 1 }>; over any base it is reachable through construction (1.12.5), so an object, class, or function type may carry a brand or a pattern. A width or lane argument such as the N of int.<N> is not a parameterization: structural arguments live in a primitive Record's [[Arguments]], and metadata rides this wrapper.

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 (6.1), and whose values are each either a leaf — an ECMAScript language value, such as a brand's tag — or another metadata record, such as the structural form a pattern or a range takes (Table 43). Two metadata records are the same when their keys are the same and, for each key, their values are the same leaf or the same record, however either record was constructed: one written in source, one built by 1.12.5, and one read back from a reflected node are indistinguishable if they describe the same metadata. That sameness is what interning uses, so it is what decides whether two parameterizations are one type.

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 Type Object, as UserId(7), is the construction boundary, and the metadata's validation judgment runs there where one is defined. Two parameterizations of one base with structurally equivalent metadata are one type by interning; two with different metadata are related only as the metadata subtype judgment admits. A metadata value's fields are governed by the meta types that claim them, which supply that judgment and the validation judgment below, and which are required to be pure, compile-time evaluable, and deterministic; 6 defines them all.

A TypeArguments following a PostfixType supplies TYPE ARGUMENTS when the type it follows declares type parameters, and is a METADATA RECORD otherwise. A TypeArguments whose TypeArgumentList is empty supplies no argument, so every parameter takes its TypeParameterDefault; it is a type error where a parameter has none, and the error names that parameter. Supplying more arguments than the base declares is likewise a type error against the parameter list. Where the TypeArguments is a metadata record, it is a type error for its TypeArgumentList to be empty, since .<> 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 (Table 43): P.<uint8> for such a P names no metadata. A base-form meta type receives a leaf, as uint8.<7> does (6.2); a class or an interface, which no base-form meta type governs, takes exactly one argument, and it denotes an object type. Whether the record's keys are claimed is decided by 6.2 whatever names the base, a class or an interface as much as a primitive or an alias.

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 type error naming T. A bare generic name denotes its declaration rather than an application in one position only: as an argument whose receiving parameter explicitly requires a higher-kinded declaration (4.13), and where an argument that is not such a declaration is refused by that clause. In expression position the name evaluates to the constructor, which stands for the declaration wherever a declaration is a value, as in the base of a generic node (1.12.5).

Note 1

There is no bare instantiation for a bare name to denote. A construction of a generic class yields a specialization (4.9), so a type that admitted only instances of the unapplied declaration would admit nothing; and a name that meant the family would make a default meaningless in a type position, since 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 (1.15.6), so Box.<any> is a Box of some element type, and Pair.<any, string> a Pair of some first type and a string second.

Note 2

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" (1.4.2.2) — and which parameter a NAMED argument supplies is decided against "a TypeParameter of the type or function being applied". This is that rule, applied to the one case it had not been.

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 1.5.20 describes as the worst shape a brand can fail in.

A form that takes TypeArguments WITHOUT declaring parameters is unaffected, because it does not reach this rule: an ArrayOrTupleType such as [4].<uint8> carries its arguments in its own production.

A parameterization applies to any PostfixType and composes with itself and with an indexed access in EITHER ORDER: T.<_A_>.<_B_> refines a parameterized type, O["a"].<_A_> parameterizes an indexed access, and T.<_A_>["a"] reads an index of a parameterized one. Attaching arguments only to a TypeName would make the rule turn on whether a type had been NAMED — an array, a generic application, an indexed access, a parenthesized type and a function type each denote an ordinary type, and each could be parameterized the moment it was bound to a name. Each spelling denotes the same type its named form does.

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 (6). A brand, whose meta type defines no validation, therefore admits no bare value of its base except through the construction boundary, which is the point of a brand. That admission rests on the first limb above, and so on the value having a discoverable runtime type: a Number carries its Type Record, and so do an object and an array, while a String, a Boolean and a BigInt do not. On a String the value carries the parameterization as it does a literal string type, so a brand there behaves as one on a Number does. A BigInt carries one likewise. A Boolean does not: true and false are single values, so a Boolean that carried a mark would not be those values, and on a Boolean a brand is checked where it is DECLARED and cannot be recognised from a value in hand, so an implementation must discharge the boundary statically where it can prove the value's type — the check it would insert can never pass otherwise, and a brand on such a base would be declarable and uninhabitable. An object or an array carries one as a MARK ON THE VALUE, the way an array's element type travels with it: their runtime type is otherwise derived from the shape, and two structurally identical object types are one answer to 1.7.1.1, so a brand — which is deliberately not structural — is recorded rather than inferred, and the mark is read in preference to the derived answer. A brand so recorded survives a mutation of the value, which is correct: a brand records PROVENANCE, where a judgment that VALIDATES records a predicate and is therefore re-run at every boundary. A class instance is nominal and mutable in exactly the same way. The two readings of that absence are deliberate and opposite: the MEMBERSHIP test reads "defines no 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.

Note 3

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 type error. Were the brand shed at the operator instead, those two expressions would produce the same type and the operator would erase exactly the distinction the brand was introduced to draw; a caller who wants the base writes the conversion.

RuntimeTypeOf reports the parameterization, not the base, WHERE THE VALUE CARRIES ITS TYPE — a Number, an object and an array do; a String, a Boolean and a BigInt do not, and on those it reports the base, there being nothing else to read, and 1.7.1.1 reports what a value IS. It follows that a branded value is not the same value as its base — 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 1.2.2.1.

A union naming both a parameterization and its base is the base: a parameterization is assignable to what it refines, and a union drops a member a preceding one is a supertype of. So 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 meta type carries a pattern as metadata, a source and flags rather than a RegExp object, so that one pattern written in two modules is one type; a hook that reads it is handed a RegExp built from those. Its validation judgment holds of a String exactly when the entire String matches the pattern, the whole-string discipline 1.13.5 already uses; its subtype judgment holds only of patterns whose source and flags are identical, which structural equivalence already makes one type, so the floor is reflexivity and the conservatism is intentional. No template-literal type kind exists or is planned: a finite string family is ordinary code over a union of literal types, and an infinite one is a pattern.

Note 4

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 GetTypeObject does the rest. The brand is not a lie told to the checker, as an intersection with a phantom symbol is in an erased system: the parameterization is visible to reflection, participates in validation, and can carry conversion semantics when the brand is a unit rather than a tag. Builders still cannot mint a nominal type, a class, an enum, or a sealed hierarchy, and this clause is why they do not need to: the thing nominal minting is wanted for is a brand.

The sanctioned sharpening of the pattern subtype judgment is the design's decision procedure over the regular fragment: pattern pairs free of backreferences and lookaround, within a fixed automaton size, get the exact language-inclusion answer, and pairs beyond the bound get the syntactic one. The tier is decided by syntactic size and never by remaining fuel, so the judgment is identical on every host, the same invariant 1.2.7.4 enforces; the size constant is among the design's open budget numbers. A parameterized field with no initializer takes the base's default where validation admits it or none is defined, and has none otherwise; whether a brand should instead always demand an initializer is a policy its meta type states by defining validation.

1.5.21 The type Type

type is the type whose values are the Type Objects (1.2.1). It is described by the Type Record { [[Kind]]: primitive, [[Name]]: "type", [[Arguments]]: « » }, and it is the Static Type of a type name or type expression in expression position (1.2.6). A function that computes a type annotates its return with it.

A class constructor with an associated Type Object is also a value of type, as is its associated Type Object. This includes declaration bases and completed specializations. The nominal library type Reflect.TypeObject admits the same denotations. Admission preserves the original constructor value; it does not replace it with the associated object, change its runtime function type, apply generic defaults or grant a generic parameter a different kind or arity. An unassociated callable, bound function, Proxy or instance is not admitted by this rule. Reference decay, initialization, access and liveness checks precede this membership judgment in their ordinary order.

Note

type is not a value type: its values are Objects, and they have identity. That identity is exactly what interning fixes, so two occurrences of a type expression that denote the same type are the same Type Object rather than merely equal ones. type is nonetheless a primitive type in the sense of the Type Record kinds, since it is a named type of the language rather than a structural description of an object's members.

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. 1.12 defines what a Type Object exposes.

1.5.22 The Existing Language Types

The names number, boolean, string, symbol, object, and bigint denote the existing ECMAScript language types of 6.1: the Number type, the Boolean type, the String type, the Symbol type, the Object type, and the BigInt type respectively. This proposal adds no values to any of them.

Each is described by a Type Record whose [[Kind]] is primitive and whose [[Name]] is the name above, except object, which is described by { [[Kind]]: object, [[Properties]]: « » }, the object type with no required properties, to which every Object is assignable.

Note

number is a value type like the rest, so no other numeric type is assignable to it and it is assignable to no other numeric type. It is nonetheless the type an untyped program computes with, and the type a numeric literal takes in the absence of a contextual type (1.2.6), which is why an unannotated program continues to mean what it means today.

1.5.23 Numeric Types

The specification already treats a numeric type as a set of values together with a fixed table of abstract operations over them, and defines Number and BigInt that way in 6.1.6. Each integer, binary floating-point, decimal floating-point, rational, complex, and vector type is a numeric type in that sense, and provides the operations of that table for which it is defined.

For a numeric type T, the operations are those named in 6.1.6: T::unaryMinus, T::bitwiseNOT, T::exponentiate, T::multiply, T::divide, T::remainder, T::add, T::subtract, T::leftShift, T::signedRightShift, T::unsignedRightShift, T::lessThan, T::equal, T::sameValue, T::sameValueZero, T::bitwiseAND, T::bitwiseXOR, T::bitwiseOR, and T::toString. A numeric type need not define every operation; one it does not define is not applicable to its values, and an operator that would use it is a type error. The definitions are given by 1.9 and 1.13.

Note

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 shape it arrived at, an operator dispatching on the numeric type of its operands with no implicit conversion between types, is the shape this proposal needs. What this clause adds is more numeric types, not a new mechanism.

The table was written for types that are closed under their operations and totally ordered, and not every type here is either. A complex type has no lessThan, since the complex numbers are not ordered. A vector type's comparisons return a vector of lane results rather than a Boolean, which is why the SIMD extension gives them their own return type rather than reusing lessThan. A rational type's operations can fail where a float's would round. These are the cases 1.9.3 settles, and they are the reason the table above is a list of what may be defined rather than what must be.

Values of two different numeric types are never === and never the same value, as a Number and a BigInt are not; == compares their mathematical values, as 1 == 1n does. Within one type, each type's own sameValue and equal rules hold.

1.5.24 Typed Promise Executors

For a proved intrinsic construction participating through declared type arguments or a typed executor operand, check the executor's callability independently of recognizing its inline function syntax. A known non-callable executor is a type error. Its incompatibility must not defeat an otherwise established constructor-origin proof. Unknown values retain runtime checking, and ordinary untyped Promise construction retains its base-language exception timing.

For an established intrinsic Promise.<R, E> construction, complete omitted type arguments by 1.5.25. The executor receives a resolving function and a rejecting function, both returning 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 thenable whose callable then can receive the two ordinary resolving callbacks. For R equal to void, the direct no-value input is undefined. Resolution does not require the thenable object itself to be of R: assimilation precedes the result boundary.

The intrinsic constructor's executor parameter supplies this function type as a contextual type. Unannotated executor parameters adopt it; explicit annotations are checked through the ordinary function relation. A known incompatible direct argument or executor parameter is a type error before execution, including within an unused body. The structural thenable input may be represented by a readonly then member of function type (any, any) => any; its normal return is ignored. This does not infer arbitrary thenable results, and an unknown assimilation path retains its runtime checks.

Apply the static intrinsic rule only where lexical resolution, the actual intrinsic data binding and a bounded effect analysis establish the constructor origin. A shadowed or replaced constructor retains its own contract. The origin proof may recognize a direct intrinsic construction with a locally supplied executor and scalar resolving/rejecting calls that introduce no unknown user effects; it must defer where user code, an accessor, a thenable or a dynamic lookup may replace the constructor before the selected construction. No executor or thenable executes during checking.

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 thenable assimilation, passes through the ordinary R boundary before fulfillment. A supplied rejection reason passes through the E boundary before rejection. An R of void admits fulfillment with undefined.

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, thenable scheduling, eager executor invocation, and all reference-liveness checks. Untyped construction retains the base language's behaviour. Reflection on an explicitly typed intrinsic construction retains its completed type arguments.

1.5.25 Intrinsic Type Parameters and Defaults

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]].

Table 11: Intrinsic Type Parameters
DeclarationParameters in order
int, uintN: required integer width from 1 through 65536.
rationalN: required integer width from 1 through 65536 or the type bigint.
complexT: a permitted binary floating-point component type, default number.
vectorT: required permitted lane type; N: required positive integer lane count.
RangeT: required element type; S: Bound, default Closed; E: Bound, default Open.
RangeFromT: required element type; S: Bound, default Closed.
RangeToT: required element type; E: Bound, default Open.
RangeFull, RangeBounds, ClosedRange, ClosedOpenRange, OpenClosedRange, OpenRangeT: required element type. The interval aliases retain their fixed bounds.

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 Type Object. 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 constructor inference retain their existing rules. Intrinsic numeric construction supplies no new inference: an explicit application or a declared concrete default determines its type.

Range literals retain their existing contextual endpoint rules and metadata's host-base normalization. Requiring an element argument in a concrete range annotation introduces neither a default element type nor a new runtime representation for an endpoint-free literal. A written full range and a full-range metadata default obey the same complete RangeBounds.<T> shape; neither satisfies a two-ended Range.<T> shape.

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.

1.5.26 Anonymous Family Constraints

Within an argument position in a type parameter's constraint, the bare identifier _ is an anonymous wildcard matching one argument admitted by that parameter's domain. Each written wildcard is independent and declares no source-visible binding. This reading does not apply to storage types or arbitrary type expressions. Omitted positions retain their declaration defaults, including dependent defaults, and missing required positions remain errors. Thus 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 ordered argument entries. An entry is fixed, a wildcard with a private identity, a nested application, or an omitted declaration-default computation retaining its declaration context and earlier argument bindings. Normalization may reduce a default to a fixed entry or a correlated occurrence of an earlier wildcard. Transparent forwarding aliases expand to these entries and preserve repeated parameters. A fixed 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, wildcard packs, and applications of variadic heads are type errors in this version. Ordinary concrete structural constraints remain valid. Intrinsic RangeBounds is supported by the declared range-shape relationship.

A matching witness is the candidate's canonical application or one of its nominal ancestor applications with substituted arguments. A range shape additionally supplies its 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 wildcard admits a candidate argument of its declared domain; another occurrence of that wildcard requires the same argument. After preceding bindings are available, evaluate each omitted default forward in its declaration context and compare its result with that argument. This evaluation uses 1.2.7.4; failure or exhaustion aborts checking and is never a mismatch permitting overload fallback.

A nominal head identifies the evaluated family, including its declaration identity under 4.8. Reusing a source declaration in another activation does not supply the same head. Matching and recursive comparison assumptions preserve this distinction. A deferred default's preceding bindings are available to both type-name resolution and ordinary expressions in its captured declaration environment; a substitution description alone does not initialize an ordinary lexical read. Restore the enclosing context on every completion.

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.

1.5.26.1 SatisfiesTypeConstraint ( candidate, constraint )

The abstract operation SatisfiesTypeConstraint takes arguments candidate (a Type Record) and constraint (a Type Record or a Family Constraint Record) and returns either a normal completion containing a Boolean or a throw completion. It checks a generic argument without converting it or changing its identity. It performs the following steps when called:

  1. If constraint is a Type Record, return IsAssignable(candidate, constraint).
  2. If candidate.[[Kind]] is union, then
    1. For each element member of candidate.[[Members]], do
      1. If ? SatisfiesTypeConstraint(member, constraint) is false, return false.
    2. Return true.
  3. Let witnesses be the canonical applications supplied by candidate as defined above.
  4. For each element witness of witnesses, do
    1. Let matches be ? the result of recursively matching witness against constraint as defined above, with a new empty set of wildcard bindings.
    2. If matches is true, return true.
  5. Return false.

1.5.27 Type Names

A built-in constructor is a type name where its family has no other spelling: 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 denotes that family another way. 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 structural form composes.

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 (1.5.25). complex declares the default T = number; rational declares no default. The remaining names in Table 12 are shorthands, each denoting the same Type Record as its expansion and therefore, after interning, the same Type Object.

Table 12: Type Name Shorthands
ShorthandExpansion
int8, int16, int32, int64, int128int.<8>, int.<16>, int.<32>, int.<64>, int.<128>
uint8, uint16, uint32, uint64, uint128uint.<8>, uint.<16>, uint.<32>, uint.<64>, uint.<128>
boolean1uint.<1>
boolean8, boolean16, boolean32, boolean64vector.<boolean1, 8> and so on for 16, 32, and 64 lanes
rational8rational.<8>
rational16rational.<16>
rational32rational.<32>
rational64rational.<64>
rational128rational.<128>
complex64, complex128, complexcomplex.<float32>, complex.<float64>, complex.<number>
neverthe union of no members, the never type (1.5.18)
TxN, as in int8x16, uint32x4, float32x4, boolean32x8vector.<T, N> for the lane type T and lane count N named
Note 1

The shorthands are aliases, not distinct types, so int8 === int.<8> is true and a value of one is a value of the other. complex expands through number rather than float64, so complex and complex128 are distinct types, as number and float64 are.

Note 2

How these names are bound is not settled by the design and is not settled here. Every other part of this proposal is gated by syntax that is an error today, which is the argument that no existing program changes meaning. Type names are not: they are ordinary identifiers, and 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 admits type names. A scope that shadows a name loses the short spelling there and writes the 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 TypeError rather than an annotation meaning 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) (1.12.1) queries the value's. Both are explicit about which of the two is intended, which an unadorned q cannot be in a language where a name may hold either.

Note 3

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 ReferenceError as it would for any unbound name. q IS bound; it simply does not denote a type, which is a TypeError. A reader who mistyped a name and a reader who reached for a value are told different things because they made different mistakes.

A Script or Module admits type names when its source text contains either a production this proposal adds — an annotation, a type operator, a conversion, an is, a type parameter list, or any other syntax that is an error without this proposal — or an IdentifierReference to one of the built-in type names this clause governs, in any position OTHER than the operand of 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 proposal, so a sloppy-mode assignment to such a name creates the global it creates today rather than reaching an immutable binding. The operand of 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 realm, and it is not established by anything that happens while the text runs.

Note 4

Both halves of that are load-bearing. It is per source text because a realm holds many: a page that loads one typed module beside a hundred untyped scripts must leave the hundred alone, and a realm-wide answer would change 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 "undefined" on one line and "object" on a later one, with a call in between as the only difference. A call written in a branch that never runs still admits, for the same reason: what a name means may not depend on control flow.

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 ReferenceError today in every position but two: typeof reports "undefined" rather than throwing, and a sloppy-mode assignment creates a global. So a source text that reaches one of these names in any OTHER position was already broken before this proposal, and admitting on it cannot change the meaning of a program that worked. The two exceptions are exactly the idioms this clause's compatibility sentence names.

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 "object" there is what its author asked for.

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) (1.9.1.1) are written as bare references to type names, and a text containing them admits by this clause alone.

Two hosts need the rule stated rather than inferred. Code passed to eval or the Function constructor is a source text of its own and admits on its own terms; a direct 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 constructor take only their own, since there is no enclosing text they run inside. A console or REPL that evaluates each entry as its own Script carries the state across the entries of one session, so a name declared in an earlier entry keeps its meaning in a later one; a host that did otherwise would have uint8 resolve on one line and not the next.

Binding them as properties of the global object is what the language does for every constructor it has added, and it keeps var string = "x" working, since a var at global scope reuses an existing property. But the design also asks that these names cannot be reassigned, and a non-writable global property would make that same assignment throw in strict mode and fail silently outside it, so the two requirements cannot both hold. Binding them as immutable lexical declarations of the global scope satisfies the design but makes var string at the top level of an existing script a SyntaxError, which is not acceptable. Requiring them to be imported, which earlier drafts of the design assumed, has no compatibility cost at all and gives up the ambient spelling the rest of the document uses. Resolving them in type position only, as a language with separate type and value namespaces would, is compatible but contradicts types being values, since the same name must denote the type in expression position for reflection to work.

This is the one place where the proposal's compatibility argument does not reach, and it deserves a decision and a web-compatibility analysis before Stage 2.

The rule is EVERY name this specification binds as a type, not only the primitives: the intrinsic metadata interfaces of 7.15 resolve the same way, and a 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 "object" rather than "undefined" in a text that never mentioned a type, an existing 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 (1.7.1.1): it reports what its operand resolved to, and what varies is whether the operand resolves to anything.

1.6 Conversions

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 value type never implicitly becomes a value of another, and every conversion between two distinct value types is written in the source text. This clause states that rule, defines the four things that remain implicit and are not conversions between two typed values, and specifies the explicit conversion that a program writes when it wants one.

Note

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 (1.6.6).

1.6.1 The Conversion Rule

At an assignment, an argument, or a return, the source value's type must be assignable to the target type (1.2.3.2), or an implicit case in Table 13 must admit the receiving boundary. Availability of such a conversion does not establish subtyping or change IsAssignable. Since a primitive type is a subtype only of itself and of the any type, no numeric value type is assignable to another, and a program that requires one to become another states the conversion.

Four things remain implicit. None is a conversion between two typed values.

Table 13: What Remains Implicit
CaseTreatment
A literal in a typed positionThe literal has no type of its own and takes the type its position requires (1.6.6). This is not a conversion; nothing is converted from anything.
A value of the any type in a typed positionThe value is checked at the boundary. At a numeric target, a Number or a BigInt is converted unless the conversion would wrap, truncate toward zero, round a finite value to an infinity, or map NaN or an infinity to an integer, which raises a RangeError. A value of a numeric type added by this proposal retains that type: any does not authorize conversion between distinct numeric types. A value of the wrong type raises a TypeError (1.7.7.1). An untyped program has no typed boundaries and so behaves as it does today.
Two parameterizations of one base that differ only in their metadataThey convert by ConvertParameterization (6.3), whose hooks apply at the same boundaries. Kilometer and Meter are both float32, so this is a conversion within a type rather than between two.
A type that declares a conversionThe conversion applies at the boundary, because the declaring type opted in (1.6.8).

1.6.2 Inferred Return Types

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 Static Type of a call, it is enforced when the function returns, and reflection reports it. What it is not is a second way to declare, and the whole of this clause is the answer to one question — which functions have one — because a rule that answered "all of them" would compute a type for every function in every program ever written, and one that answered "only annotated ones" would lose the type one call away from the annotation that established it.

Override compatibility consumes published returns after inference has established them. A changed publication requires dependent comparisons to be reconsidered. An unpublished or explicitly any return remains unknown; return inference does not change the parameter-based identification of an override.

1.6.2.1 Dependent Result Contracts

A published inferred result can contain parameters bound by its function or by an enclosing generic declaration. Such a result is a symbolic contract in that declaration environment. Determine and use it as follows:

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.

  1. During declaration checking, retain each parameter's binder and effective constraint. A completed symbolic input is not unfinished solely because no runtime type argument has been supplied. Apply the existing subtype and assignment judgments to that input; do not replace the parameter with its constraint or with any. A subtype constraint permits the relations it establishes, but does not identify the parameter with that constraint.
  2. Apply 1.6.2.17 to symbolic inputs as to other completed inputs. When the existing store rule preserves a scalar value's domain without conversion, retain its parameter-dependent domain through the store, subsequent captures and contribution queries. The destination's wider annotation does not replace the stored domain. Preserve all applicable effects, predecessor joins and binder identities.
  3. A nongeneric helper can return a parameter of an enclosing declaration. Establish its call-result contract in that enclosing environment; the helper needs no additional type argument. A completed symbolic result is usable by contribution inference even while its enclosing parameter remains uninstantiated. This does not exempt the call from the replacement judgment of 1.7.9.1.
  4. At a runtime return boundary, instantiate the published contract using the generic arguments of that function invocation and the bindings captured from its declaration environment. Lexically inner binders shadow outer binders; a same-spelled parameter in an unrelated caller supplies no binding. Apply the ordinary return conversion and enforcement to this instantiated contract. Apply the same instantiation when supplying a concise body's result context and the effective result contracts of resumable forms. Do not enforce an unresolved parameter as a closed type, waive an inferred boundary because its declaration was generic, or overwrite a shared declaration contract with one invocation's argument.
  5. Reflection and runtime callable compatibility use the published contract under the function value's captured generic bindings. A still-generic signature retains its own unbound parameters and constraints. Inspecting a closure after its creating call has returned, or from a caller with different generic arguments, does not change its captured contract. Completing or instantiating a type contract does not initialize a captured value or establish reference liveness.

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 captured binding to obtain its contract.

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 parameter list does not erase the adopted generic contract or waive comparison of its inferred result.

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 capture that argument. When a fresh callable literal adopts that signature, its adopted parameters establish its effective own scope for body checking and nested binding contracts. Already captured binding contracts continue to name their original declarations. Contextual trial failure restores the previous context; it does not change shared source declarations or a different activation's contracts.

When alias composition replaces a captured dependency's contributed type with a type containing another declaration's parameters, retain those parameter identities within the contribution. Later application of those declarations instantiates the contribution while the original declaration continues to identify the source dependency. An unrelated same-spelled parameter supplies neither correspondence. A method signature retains its effective bounds, defaults and captures throughout this composition, as a function type does.

Example 1 (Informative)
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 captured Number into String.

A published result which contains a local class retains its nominal declaration dependency. When a concrete class identity is supplied by a function value's captured declaring environment, use that identity for its inferred result contract, including return admission and reflection. A direct forwarding result may obtain the same dependency from the retained result contract of its immutable callee binding. Preserve that binding's declaring environment and the applicable activation; a same-spelled binding in the caller, a later evaluation of the class declaration, or the value being admitted does not supply the identity. Instantiation must not replace a shared symbolic publication with one activation's constructor.

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 site or in an outer environment. Distinct return contributions retain their respective dependencies. If all applicable contributions establish the same concrete class identity independently of executing the body, that identity can complete the corresponding nominal result dependency. Equal class declaration source alone does not establish equal concrete identity. When contributions depend on different activations, or a dependency is supplied only by a body-local initialization, retain those dependencies for the applicable invocation and contribution; do not select the first contribution's identity for all results.

Following an alias for contract instantiation does not evaluate its initializer or initialize its binding. Obtaining a captured result dependency does not choose a return branch or execute a callee. An unavailable or differing dependency must not be replaced with an ambient same-spelled class, nor may the returned value supply its own admission target. These requirements do not alter the ordinary evaluation order, binding initialization checks or result-admission rules.

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 ordered argument bindings, including dependencies inside those arguments. Complete matching unresolved argument dependencies before comparing the arguments. Equal declaration syntax or equal parameter spelling does not identify two evaluated families or substitute one binder for another. Preserve an already completed nominal and its arguments. Compact checking representations of value arguments must be rebound to their declared domains before runtime admission; retaining the resulting typed values must not erase their domains. A compact numeric literal used as a type argument retains its own domain; an equal numeric payload in another domain does not supply a matching argument.

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 specialization or change its parameter, receiver, constraint, default or narrowing obligations. Ordinary callable compatibility still applies. A selected contribution's completed context takes precedence over another activation of the same class declaration in the receiving environment. Independently available lexical declaration contexts complete only dependencies still unresolved after that contribution.

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 contribution only when they establish the same completed contract. No corresponding contribution, or conflicting completed contracts, supplies no replacement identity. Preserve already completed targets and ordinary admission requirements. This rule does not change function type identity, subtype comparison, overload ranking, constraint/default evaluation timing, or reference liveness, and does not inspect the candidate callable to determine its destination.

An implementation dispatcher that forwards an ordinary overload call or a selected owner or case preserves the selected invocation's checked normal-result dependency through its internal call frames. It is not an additional erased result boundary. Preserve the invocation's abrupt completion and restore selection and type-argument context on every exit. Do not reconstruct the dependency from a caller's lexical bindings, a dispatcher's apparent catch-all signature or the returned value. Written result contracts, including erased contracts, retain their ordinary authority. Reflection and runtime callable compatibility describe each overload member using that member's published inferred result under its own captured declaration context; a dispatcher does not supply one publication in place of all its members. Deriving these descriptions does not execute a member body.

For an inferred immutable binding initialized directly by an ordinary function expression or arrow function, the newly created function's verified source declaration and captured environment may complete its unresolved nominal result dependency before initialization admission. Immutable forwarding sources retain their declaration contexts under the preceding rules. This does not execute the function or take an arbitrary candidate callable's observed runtime signature as a new destination contract. Parenthesized syntax preserves the initializer correspondence. Written annotations and already completed targets remain authoritative.

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 stable type-position reference to an immutable ordinary binding initialized by a class expression retains that expression's declaration dependency, including through stable immutable aliases and parentheses. Complete the dependency from the particular class evaluation associated with that binding under 4.8. Apply this to nested generic arguments and inferred callable result descriptions as well as direct nominal results. A shared checking description remains symbolic until its executing use has the corresponding context. This rule neither weakens a known dependency to any nor permits a mutable or otherwise non-evaluable binding in type position.

Completion of a retained nominal dependency descends through a returned callable's parameter constraints and family-pattern heads as well as its parameter and result types. Preserve the callable's own binders and its captured declaration context. Completing those dependencies neither applies the returned callable nor evaluates an unused default, and an already completed family is never rebound to a different activation.

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 contribution, and a throwing finalizer supplies no successful return. Retaining a dependency does not admit a discarded return early, move result admission before a finalizer, repeat operand evaluation, or establish reference liveness. Nested and recursive calls retain their own dependencies; an intervening argument call cannot replace the enclosing return's dependency.

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 abrupt completions and reference-liveness requirements. Retaining a dependency neither repeats those operations nor moves result admission before ordinary completion selection.

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 data property or array element, retain its admitted result dependency with that stored contribution, subject to the property's own authoritative declared contract. An ordinary read may contribute that dependency after the required property-read and reference-liveness checks. Retain the dependency through ordinary component copies and iterator-result transport without repeating a getter, iterator step or initializer. An inherited data read uses the contribution actually selected; an accessor read uses the getter invocation's checked result, not a dependency formerly attached to a data property of the same name. Attribute-only changes preserve the stored contribution. A failed write preserves existing evidence; a successful value replacement or deletion must not retain the previous contribution's evidence as the new contribution's target. A value-preserving copy does not infer an admission target from the copied value.

When an inferred named-object boundary contains matching unresolved nominal dependencies, complete a private boundary contract from independently admitted source contributions at each property path. Distinct paths may refer to different activations of the same nominal declaration. Complete nested named-object paths in the same manner. Preserve already completed targets and authoritative written or erased member contracts. Do not choose a target by inspecting the member value or prototype, use one path's identity for another solely because their symbolic type records are shared, or modify a shared symbolic publication.

Fresh object construction retains its evaluated source contributions in ordinary definition order. A later literal property definition or copied contribution replaces the earlier contribution for that key; a later method or accessor does not inherit an earlier data initializer's dependency. An arbitrary backing mutation, including a decorator's mutation before object admission, does not replace the source obligation. Apply ordinary admission to the resulting object after the ordinary construction and decorator effects, using the independently established target. Retaining or completing this dependency performs no additional getter read, key conversion, initializer or decorator evaluation. Failed admission leaves the destination uninitialized and preserves prior effects and initialized bindings.

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 path absent from the view supplies no replacement contract. A later backing replacement or deletion cannot retarget the established view; any subsequent receiving boundary checks the actual obtained value against its applicable contract. This rule neither makes plain storage invariant or readonly nor creates a receiving boundary where the existing static contribution rules establish none. Object spread and rest retain their separate ownness and enumerability requirements. Defaults selected by ordinary evaluation supply their own contributions.

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 Evaluation creates. An ordinary value-preserving alias retains the source identity; equal spellings or Symbol descriptions do not establish it. Checking this dependency neither executes Symbol creation nor initializes the binding.

At the existing runtime boundary, complete each still-symbolic named key in a private contract. Use the actual key supplied by the matching source contribution at that property path when one is retained. Otherwise, use the actual Symbol of the corresponding already initialized ordinary immutable binding in the applicable lexical environment, when available. A contributing activation takes precedence over another activation of the same declaration in the receiving environment. Distinct nested paths may supply distinct keys. Preserve keys already completed in the target and keys established by authoritative written contracts. An unavailable dependency remains an obligation; it does not authorize erasure or selection of a key from the candidate object's current properties.

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 contribution retains the applicable source key dependency after its ordinary read. The definition-order rule above applies to both key and value contributions. A decorator's backing mutation cannot replace the source key obligation: a different Symbol with an equal description is still a different key. Completing the contract performs no additional property-name evaluation, key conversion, getter invocation or own-key enumeration, and does not change object storage or its ordinary mutation permissions.

A key contribution without a checked value contract supplies only key identity. It does not establish an erased member contract or replace a getter invocation's checked result. A checked member whose target is any remains authoritative under the ordinary projection rule. Excluding a fresh accessor from eager inferred object admission applies to its established computed key as well as to a literal name; a later data-property or method definition for that key replaces the earlier accessor exclusion. This exclusion does not remove ordinary member reads required when admitting an existing object. Failure at an inferred object boundary leaves its destination uninitialized while preserving earlier initialized key bindings, earlier successful bindings and prior effects.

Example 2 (Informative)

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 "12:false".

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 iterator value actually obtained, or of the default when selected, to instantiate a matching unresolved inferred nominal target. Perform the ordinary extracted-binding admission before initialization and before retaining the binding's checked dependency. A written annotation, including an erased annotation, remains authoritative. Failure preserves earlier effects and initialized bindings and follows ordinary iterator closing; it supplies no initialized dependency for the failed binding. This transport does not waive a whole-aggregate admission boundary or turn an already conforming plain aggregate into typed storage.

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 any. Existing nominal membership, conversion, replacement and acquired-liveness obligations continue to apply. The applicable result dependency must also be preserved through multiple return paths and local or imported bindings; an implementation's inability to complete one of those paths does not change these requirements.

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 BindTypeArguments. Declaration identities, rather than equal parameter spellings, distinguish the two operations; preserve the returned signature's parameter declarations and unevaluated default and constraint source environments. No signature application or default expression is executed merely to publish or reflect a result.

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.

Example 3 (Informative)

The inner parameter is distinct from the outer one. The expression evaluates to "1s". Explicitly applying 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;
Example 4 (Informative)

An unchanged typed callback can supply a symbolic result even when its value crossed any. The published boundary of produce below enforces the instantiated Number contract and throws a TypeError; the untyped caller does not waive that boundary. Replacing get through a mutable binding would instead invoke the replacement judgment described in 1.7.9.1.

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);
Note

For example, both closures below retain their respective captured result types. The final expression evaluates to "1s", and each closure reflects the same result type as an otherwise identical function with the corresponding Number or String return annotation.

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 captured contract retain 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 capture must complete under 1.6.2.17 before their consumers are finalized.

1.6.2.2 Anchored Contributions and Participation

A contribution of a function is what 1.6.3 defines: the Static Type of the operand of each return, or undefined where a return carries no value or the body can complete without one, with a return in a nested function literal belonging to that literal.

A contribution that reads a LOCAL BINDING which cannot change is the type of that binding's initializer, widened as a contribution is. A binding cannot change when it is declared 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 Static Type any, this contribution rule gives the binding no type for other purposes; in particular it does not infer a type for an unannotated let. Typed const inference is specified independently in 1.2.6.

Note 1

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 contribution. A typed const now preserves that information through its binding type as well; the contribution rule remains necessary for an unchanged unannotated 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 1.2.6 makes for a numeric constant, where propagation "decides which VALUE a use produces" and "infers no type for the binding, and adds none". A binding that cannot change is a name for its initializer's value, and a contribution asks what the function produces.

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 published type is enforced where the function returns (Table 23) — reading only the initializer of 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 contribution is anchored when its Static Type derives from a DECLARED type: a parameter's annotation, a binding's annotation, a field's annotation, a declared return type, or the inferred return type of a function that is itself participating. Anchoring follows the derivation of the value, not control dependence, so in return _flag_ ? 1 : 2 an annotation on flag anchors nothing: the contribution is the join of the arms, and the arms are literals.

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:

  1. A known parameter, receiver or result position other than any, obtained from a written annotation or an adopted contextual signature, supplies declaration provenance. Preserve symbolic parameter identities and their enclosing substitutions as required by 1.6.2.1.
  2. A completed inferred result supplies provenance when the literal itself participates under this clause. A private contribution result that does not qualify for publication supplies none. For a generator or async generator, determine yield and completion contributions independently: an unknown component does not erase a known declaration-derived component. The existence of a Promise or generator carrier alone supplies no anchor.
  3. A generic parameter list alone, a known literal-only body result, or a signature whose only known positions are any does not supply an anchor. An annotation used only to choose control flow does not supply provenance to literal value-producing arms. An annotation elsewhere in a nested body contributes only through the ordinary derivation and participation rules.
  4. Retain the provenance of the particular contract computation through contribution dependencies. Equal function types need not have equal declaration provenance. Neither another query of the same source under a different context nor an unfinished inference answer establishes an anchor for this query.

These rules do not execute a literal or its enclosing function, apply generic defaults, initialize a captured binding, or establish reference liveness. A literal's declarations and any published result still undergo the ordinary callable compatibility and runtime enforcement judgments.

Example (Informative)

The returned literal's annotated parameter and result anchor outer's callable contribution. The published signature retains the captured String default through R. This program has a type error before evaluation although use is uncalled; changing g(1) to g("ok") admits the consumer. The independent T declared by use does not replace the capture.

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);
}
Note 2

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 participates by its annotated parameter and publishes its String result, which supplies provenance to make. An explicit any result instead remains unknown: 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-any signature, a generic parameter list alone, or a baseline resumable carrier supplies no anchor. Preserve the signature's declaration origin through explicit specialization, determining provenance from the resulting known positions; supplying a type argument alone does not create an anchor. An unchanged unannotated alias retains this provenance. An independently written binding annotation remains authoritative under the binding rule below, including a non-any function type whose result is any. These judgments do not execute the callable, apply otherwise unused defaults, or establish reference liveness.

A call's independently declared or participating published callable result retains its declaration provenance when an inferred binding preserves that result contract, including through capture, import, re-export, namespace selection and specialization. Do not replace that evidence with the returned callable declaration's own participation, or transfer it to a separate read of that declaration. Import and namespace spellings supply no additional annotation; resolve the actual binding with ordinary lexical shadowing before applying this judgment.

Note 3

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 contribution. In contrast, writing helper(): any retains the erased result even when its body returns a known literal.

For a contribution that reads a binding, determine its type and its declaration provenance separately, as follows. Parentheses around the read do not affect either determination.

  1. Resolve the binding in the function's lexical environment. A local or parameter shadows an outer binding even if the local has no annotation, no initializer, an unresolved type, or the type any. The annotation of the shadowed binding supplies no evidence for this read.
  2. A binding with a known declared type other than any supplies an anchored contribution. This includes a literal type, null, and undefined. Having exactly one possible value does not erase the declaration from which the type derives. An inferred binding type preserves the declaration provenance of the initializer from which it was inferred.
  3. For an unannotated local whose unchanged initializer is read through under the preceding contribution rule, carry both that initializer's contribution type and its anchoring result. Widening the contribution changes its type, not its provenance. A literal-only initializer therefore remains unanchored when named by a local; a declaration-derived initializer retains its anchor.
  4. For a captured binding, use its declared or inferred binding contract. Do not import the enclosing evaluation's current-value narrowing into the function's published result merely because that narrowing holds where the declaration is encountered. The captured storage is read when the function executes. The contribution does not change the binding's permitted stores, capture mode, or reference-liveness requirements.
Note 4

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 captured contract contributes string | number; a later valid store of a Number cannot invalidate the published result.

An object literal contribution retains declaration provenance from its effective member contracts under 1.6.2.20. A surviving data property's value contribution retains its provenance, subject to its written or adopted member contract; an explicitly any member contract supplies no anchor. A method supplies provenance from a known non-any written or contextual parameter, receiver or result position, or from its participating published result. A getter or setter supplies provenance from its corresponding surviving contract. Apply ordinary descriptor replacement and accessor pairing before determining whether any such provenance survives. A replaced definition, a computed key alone, a literal-only member or an unrelated annotation in a method body supplies no provenance to the resulting value. Preserve surviving provenance through nested objects and use the same judgment for an unannotated immutable binding's typed object initializer. An unknown aggregate result remains unknown under the existing contribution rules.

Note 5

In function make() { const x: string = "s"; return { x }; }, the object contribution anchors 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; 1.6.2.1 governs its ordinary receiving boundaries. Establishing provenance executes no member, changes no property descriptor, and supplies no reference-liveness evidence.

An array literal contribution retains declaration provenance from the element contributions used to determine its Static Type. Determine each ordinary element's type and provenance in the same lexical and contextual environment, before widening; a singleton annotation retains its declaration provenance. For a spread, first determine its static iteration contribution (1.14.8); retain provenance from the operand only through a known yielded contribution of that protocol. Indexed array or tuple storage alone supplies no such contribution. A literal-only element, an elision or an annotation used only to select literal arms supplies no anchor. Preserve surviving provenance through nested aggregates and unchanged local aliases, and use the same query-specific judgment for an unannotated immutable binding's typed array initializer. An unknown aggregate result remains unknown under the existing contribution rules. This judgment does not create a fixed extent or tuple shape, execute an iterator hook or establish reference liveness.

Note 6

In function make() { const x: string = "s"; return [x]; }, the known element contribution anchors 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 iterator yields.

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 replacement and accessor pairing before selecting the contract. Written singleton, null and undefined member types retain their declaration provenance; an explicitly any contract remains erased. For a finite set of possible keys, combine the evidence of the selected alternatives. Preserve member origins through nested reads, unchanged aliases, inferred bindings, captures and imports. Equal structural types may carry different evidence; canonicalizing a join must not discard a contributing alternative or make the result depend on arm order.

An independently declared receiver contract, or a declared or participating published aggregate result, supplies its established member contracts under the ordinary reading rules, including at nested reads. This evidence is distinct from the aggregate literal's own member provenance. A private inference approximation establishes no such independent contract, and a separate read of the original literal-derived value does not acquire it.

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 1.14.8; do not invent a fixed count. A literal-derived length gains no anchor from an element's annotation. These provenance judgments do not replace the array's element type with a tuple type, establish a fixed extent, execute a getter or iterator, install typed storage, license check elision, or establish reference liveness.

Note 7

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 published result of function producer(seed: number) { return { value: "s" }; } instead supplies an independent object contract, so selecting producer(1).value is declaration-derived.

A conditional contribution derives its anchoring from its value-producing arms in their respective guarded environments. Joining or widening those arms does not create declaration provenance. A cycle of private, unanchored answers cannot make its declarations participate merely by reading one another: without an annotated signature or an independently anchored contribution, each result remains private. A participating external consumer may still ask for those private answers under the following rule.

A logical expression's contribution retains provenance only from operands that can supply its result under 1.9.4. The left contribution is its falsy part for &&, truthy part for ||, 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-any type supplies declaration provenance, including a singleton, null or undefined target; having one possible value does not erase that contract.

Preserve surviving logical contribution evidence through nested expressions, unchanged aliases, inferred bindings, imports and subsequent member selection. Equal structural types do not merge distinct origins. An unannotated immutable initializer applies its existing participation rules to the surviving operand contributions, not to every syntactic operand indiscriminately. Independent declared or participating published results remain authoritative, while private approximations supply no independent contract. Retained evidence does not replace the existing logical-result type, contextual adaptation, bounds or member-read domain. Existing impossible-test and unreachable-operand diagnostics retain their precedence. These judgments execute no operand to discover its type and change neither short-circuit behaviour, receiving checks, failed-state behaviour nor reference liveness.

An optional-chain contribution retains the declaration provenance of its live member read or call under the ordinary reading and invocation judgments. At each optional boundary preserve the non-nullish alternatives' origins through the receiver narrowing; canonicalizing equal types must not discard distinct surviving evidence. Where the chain can short-circuit, the generated undefined alternative supplies no anchor. This is distinct from a live member or result explicitly declared undefined, whose contract retains its evidence. A receiver annotation used only to control short-circuiting, an unrelated member, a computed key or a call argument alone supplies no provenance to the result.

Preserve this optional-result evidence through nested chains, unchanged aliases, inferred bindings, captures, imports, specialization and subsequent logical or member selection. An immutable initializer applies its existing participation rules to the live contributions; the optional wrapper and generated absence supply none. Independent declared or participating published contracts remain authoritative and private answers remain private. Retained origins do not replace the existing receiver or result type, member domain, bounds or contextual judgment. Parentheses, call receivers, required syntax checks, diagnostics, evaluation order, skipped effects, receiving checks, failed-state behaviour and reference liveness retain their ordinary rules. These judgments execute no source code to discover a type.

A destructuring contribution preserves evidence from the selected property or iteration value, recursively through nested patterns and renaming, including established String and Symbol keys and finite key alternatives. Equal structural types do not merge distinct origins. An unrelated member or key supplies no evidence to the selected value. Use the existing pattern property and static iteration contribution judgments; a mutable array or tuple storage annotation does not by itself establish an iteration contract.

A default contributes only where the extracted value may be undefined. Project supplied origins through removal of undefined, then join the surviving contributions with the fallback where it can be selected. An excluded undefined contract and an unselected default supply no evidence. Later defaults may read earlier extracted bindings under their existing contracts; every binding name shadows in its lexical scope, and temporal-dead-zone checks remain. Required checks of every written default still apply.

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 shape only under the existing own-property, enumerability and exclusion proof; structural membership alone does not provide it. Array rest follows the established iteration contribution and remaining positions. An unchanged local binding can transparently answer a contribution without acquiring a binding contract. An unannotated immutable binding participates according to its selected contribution, and a captured read uses the admitted binding contract; an ordinary unannotated mutable binding remains gradual. Explicit annotations, including any, remain authoritative, and private callee answers supply no independent captured contract. These judgments execute no source code and change neither extraction order, skipped effects, admission, iterator closing, failed-state behaviour nor reference liveness.

An unchanged binding does not establish that the contents of its value are unchanged. A private aggregate initializer description used by contribution inference is a source-position value fact, separate from any admitted binding or member contract. Retain the identity of that aggregate through established aliases and nested extraction. A copied scalar retains the value contribution at its extraction position; a copied aggregate retains its alias identity. Later effects do not alter a contribution already collected at an earlier position.

For a simple assignment to an established own data property of such an aggregate, retain the receiver selected before the right-hand side, and transfer the known stored value and its declaration evidence to that property. Preserve other established data properties and distinct aggregates. The overwritten value supplies no further evidence. This transfer supplies neither a new storage annotation nor declaration evidence from a plain literal. An existing admitted contract remains authoritative; the transfer must not replace that contract with the incoming value's type.

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 iterator. Apply the existing flow joins and loop and exceptional-entry widening to these descriptions; a missing description on a possible incoming edge contributes unknown. This invalidation does not erase an admitted contract or a scalar value already copied out.

Within a value-producing expression, determine each component's contribution at its evaluation position. For && and ||, retain the left operand's value contribution, apply its modelled effects, and determine the right contribution under the continuation that permits its evaluation. For ??, select that continuation from the captured left value's nullish alternatives after transferring the left operand's effects; do not reread a changed place to decide which value was obtained. A comma expression transfers preceding operands before determining its final operand's contribution. A conditional expression determines each arm under its own selecting continuation. Apply the existing result-type selection and contextual adaptation to these contributions, separately from joining their outgoing effect environments. A skipped component supplies no effect transfer on that continuation; all required checks of written components and existing impossible-test and unreachable-operand diagnostics remain.

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 contribution. Equal structural types do not identify distinct aggregates. Independent admitted contracts remain authoritative. These judgments retain their source declaration, receiver, generic and contextual environments and completion dependencies. They execute no component to discover its type and establish neither initialization nor reference permission or liveness.

A computed member read retains the receiver value obtained before evaluating its key. Determine the key contribution in that environment, transfer its evaluation and applicable property-key conversion effects, then determine the read from that captured receiver under the resulting environment. Replacing the place from which the receiver was obtained does not replace the captured receiver; mutating its contents can change its private description. An independently admitted read contract remains authoritative. These judgments retain established String and Symbol key identities and finite key alternatives under the existing member rules.

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 contribution; a method or accessor definition transfers its key without invoking its body. Transfer a spread expression and its applicable copying or iteration effects before later components. A possible getter, iterator, conversion or other unknown effect invalidates private descriptions under the existing rules; primitive object-spread sources retain their existing copying semantics. Retain already obtained scalar contributions, while aggregate values stored by earlier components preserve alias identity and reflect later modelled writes to that same aggregate. Preserve those identities through nested containers and subsequent binding of the completed construction. The separately determined outgoing effects apply to later source positions even where an unknown component prevents a complete container type.

These construction judgments preserve descriptor replacement, contextual adaptation, contribution evidence and the existing array element domain, extent and static iteration contribution. They introduce no positional refinement of an ordinary array and no storage or iteration contract. An annotation used only by a key or an unrelated component does not annotate the selected value. Restore the suspended source environment after a type query while retaining its completed result and required dependencies. All required checks of written components remain; these judgments execute no initializer, key, getter, method or iterator to discover a type and establish neither initialization nor reference permission or liveness.

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, iterator or default to discover a type, and neither establish reference liveness nor change extraction, receiving checks, conversion, failure state or ordinary evaluation order.

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 anchored. A participating function whose declared return type is absent has an inferred return type, which is the join of its contributions, and that type is published: it is recorded in the signature's [[InferredReturn]] field and is visible to every other clause. A join of the any type publishes nothing, since a function whose result is unknown is indistinguishable from one that never participated.

A function that does not participate has no inferred return type and no [[InferredReturn]]; a call of it has the any Static Type, exactly as before. Its contributions are still computed where a participating function's inference asks for them, and the answer is used to type the ASKER without being published for the answerer: function g() { return 'foo'; } remains untyped at its own call sites even where a participating function returns g() and infers string from it.

Note 8

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 proposal made mechanical rather than argued. What annotations buy is reach: the type an annotation establishes travels as far through returns as the returns carry it, and stops where a body stops deriving from it.

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.

1.6.2.3 The Inference Fixpoint

A contribution may be the return type of a function whose own inference is in progress, directly or through a cycle of calls. If that dependency has no provisional contribution result yet, its initial contribution is never. Otherwise use its most recent provisional contribution result, including a completed unknown join, and repeat inference until the required provisional results, participation and published types do not change. Because never is the identity of union (1.5.18), a cycle's initial contribution vanishes from a join that has any other member, so a function whose base case returns a 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 contribution, a known contribution (including never), and a completed unknown join. An unfinished prerequisite remains a dependency under 1.6.2.12; its absence is not a completed unknown result. Once a contribution computation completes, its result replaces the previous provisional result even when the new join is unknown. Re-entering that computation does not replace an already computed unknown join with the initial never seed. A change to a private provisional result also requires reconsideration of its dependent contributions; the absence of a published result does not make such a change irrelevant.

Apply the participation and publication rules anew to the updated contribution result. A completed unknown join publishes no inferred result, and a contribution that no longer establishes participation supplies no inferred result merely because an earlier approximation did. Remove the superseded inferred signature result, its declaration-derived provenance and the runtime return contract derived from it. Retain explicit annotations and their runtime enforcement. Preserve any independently specified protocol shape of an unannotated generator; a generic protocol shape does not itself establish participation. These changes neither initialize storage nor change reference capture or liveness.

Example 1 (Informative)

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 type error because its initializer has no Static Type. A Number annotation instead retains the runtime boundary check and fails there.

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 finite propagation within a recursive dependency group before concluding that inference does not converge. Copying a previously established result or joining previously established union members does not construct a new recursive type. In particular, when a group's contribution transfers only copy or join members of a fixed finite set of established types, compute their closure under those transfers, with never as the initial contribution and a completed unknown join retaining its ordinary absorbing behaviour. The number of declarations, their source order and the distance from a seed to a consumer are not grounds for a non-convergence error.

Reconsider a contribution when a required provisional or published input changes, retaining its declaration environment and the applicable type-argument substitution. Dependencies include results read through a function value or an explicit generic application, as well as a direct call. A private answer is such an input even when it supplies no declaration-derived anchor. An implementation may retain an answer while its inputs remain unchanged; invalidating a required binding contract also invalidates answers derived from that contract. Reconstructing an equivalent signature or canonical type is not progress by itself. These requirements permit dependency worklists and component scheduling without prescribing either representation.

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 published answer. This includes a private answer and the initial never contribution of a recursive reference; an unanswered dependency must not become a completed unknown merely because an application copies a signature. A completed unknown remains unknown after substitution. Specialization does not itself publish a private answer or create declaration provenance, and a receiving context cannot replace explicit type arguments already bound by the application. A final published symbolic protocol result replaces any superseded concrete approximation in the runtime return contract. Instantiate that final contract with the invocation's bindings; dependence on a type parameter does not preserve an earlier approximation.

Repetition does not always converge: a recursive result can grow under a type constructor at every step, as function _w_(_a_: uint32) { return [_w_(_a_)]; } does. It is a type error where the repetition does not reach a fixpoint, and the program says what it meant with an annotation. Apply the actual contribution transfers, including projection and unknown absorption, before concluding that a constructor makes a cycle grow without bound. The occurrence of a constructor on a recursive path, or a chosen number of changing approximations, does not by itself establish that conclusion. Inference produces no equirecursive types.

Example 2 (Informative)

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 type error before evaluation.

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);
Example 3 (Informative)

The unknown contribution from globalThis.seed absorbs the constructed recursive contributions. These functions therefore have no published inferred result contract. The occurrence of arrays on the recursive path does not establish infinite growth.

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]);
Example 4 (Informative)

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 type error before evaluation; copying the generic signature cannot erase that obligation.

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 constructor growth is a closed contribution equation built only from the recursive result, a finite set of known seed types, finite joins, and type constructors that preserve their operand as a proper structural component. If a recursive occurrence passes through at least one such constructor and survives every normalization of the equation, repeating that equation produces types of unbounded structural depth. This proves non-convergence. Establish the equation from the actual contribution judgment, including declaration identity, participation, contextual typing and any substitution; recognizing a constructor in source syntax alone does not establish the equation. A projection that removes that component, an annotation that replaces it, or an unknown contribution that absorbs it invalidates this proof.

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 contribution computation with unchanged external inputs: include required dependencies, their provisional and published results, participation, environments and the outstanding work. Repetition of the displayed return types alone, or repetition while an external prerequisite remains unfinished, is insufficient. When no proof is established and inference cannot complete within the implementation's checking resources, apply 1.6.2.8.

Example 5 (Informative)

The projection in this equation removes a structural component at each recursive read. Its finite closure contains the nested object types and Leaf, whose declared getter result closes the projection. Adding further nested value properties increases the finite closure; it does not establish divergence. Replacing "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 realm makes no difference to what a program means. A dynamically imported module has no such relationship to its importer: a call through it has the any Static Type, and the enforcement and reflection of the imported function carry its type instead.

Note

"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.

1.6.2.4 Imported Method Results and Namespace Contracts

Importing or exporting a class does not itself make its methods participate in return inference. Apply 1.6.2.2 and 1.6.2.18 in the producer's declaration environment. A method returning only an unanchored literal publishes no inferred result merely because a consumer supplies a typed destination. That destination retains its ordinary runtime check. A declared or participating inferred result retains its contract across imports, and a statically known incompatible consumer is rejected at the applicable checking boundary, including in uncalled source.

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 constructor uses its static members. Preserve nominal identity and the producer environments of member results, constraints and defaults. This changes neither participation nor runtime admission, reference permission or liveness. No module body, getter or constructor is evaluated to discover these contracts. Compatible source keeps ordinary initialization, temporal-dead-zone and effect behaviour. An unfinished required contract remains a dependency rather than a completed unknown result.

1.6.2.5 Exported constructor value aliases

An established unapplied class constructor retains its declaration origin through ordinary immutable value aliases exported by a module, including an origin established for the expression of an 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 constructor parameters, static members, instance contract and heritage information under their existing rules. A value's constructor origin is distinct from its instance type and from the presence of a type declaration with the same name.

An import's immutable local binding does not establish stability of the producer's live binding. An export default expression captures its value at ordinary evaluation, whereas a named export of a local binding remains a live view of that binding. An origin proof must respect this distinction and the existing rules for mutation, explicit erasure and decorator replacement. A bounded class-or-subclass origin is not made exact by export. A failed producer check publishes no new constructor-origin proof. Determining these contracts evaluates no initializer, decorator, constructor or method and preserves ordinary initialization order, temporal-dead-zone behaviour and reference permissions and liveness.

A construction through an unapplied alias binds its arguments under 4.9, including when its result is discarded. An already applied constructor retains its completed arguments and any pending declaration-aware dependencies in the producing context, as specified in 1.6.2.6. Export, import, storage or aliasing does not reopen that application, select its arguments again, or resolve its captured arguments in an unrelated consumer environment.

1.6.2.6 Stored Constructor Applications

An established expression-position application of a class constructor retains the original class declaration and evaluated family, its ordered bound arguments, and the declaration dependencies of any arguments that remain symbolic. Bind the written argument list in the producing expression's context under 4.3, including named arguments, spreads, constraints and defaults. A subsequent construction from that selected constructor uses those arguments. Neither the construction's contextual type nor its value arguments select them again. An immutable value alias of the selection is not a new type declaration.

The selected arguments determine the constructed instance contract, constructor parameter admission, static members and inherited contracts under the ordinary rules for those consumers. A symbolic argument retains the identity of each free generic parameter's declaration through member access, result inference, structural composition, heritage and import. Substitute only bindings for those declarations, as required by 4.7. Applying an unrelated inner generic with a same-spelled parameter does not replace an outer capture. A source class dependency closes in its declaring activation, never from the candidate value tested against an independently inferred destination contract.

An unavailable required default or other application prerequisite remains a checking dependency under 1.6.2.12; it is not a completed unknown result, an omitted argument or an implicit any binding. Publish a module's selected constructor proof only with its completed argument description, retaining symbolic declaration dependencies when the enclosing generic context remains open. Failed checking publishes no new proof. Preserve default evaluation and failure timing, source environments, ordinary initialization and temporal-dead-zone behaviour. Describing these contracts executes no constructor, method, getter or ordinary initializer. No reference permission or liveness rule changes.

A fresh explicit TypeArguments list on a class constructor retaining an established generic family requests a new application of that same evaluated family. Bind that list independently; omitted arguments use the new application's ordinary defaults and do not inherit bindings from the previous selection. The previous selected value and its aliases are unchanged. This existing class-family capability does not make an arbitrary value generic and does not change the rules for generic-function specialization. Storage, import, member access and construction alone request no such new application.

Example (Informative)
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 is a String. The selected argument captures make's parameter; read's distinct parameter does not alter it. Replacing the destination annotation with boolean is a type error before evaluation.

1.6.2.7 Constructor Growth in Dependency Components

The sufficient proof in 1.6.2.3 extends to a finite system of contribution equations. Each equation belongs to a declaration and its required result component in its declaration environment. Identify dependencies by their resolved declarations, including applicable type-argument bindings, rather than by identifier spelling or formatted types. An edge denotes a result actually used by the contribution judgment; a name that is shadowed, an unresolved input or a potentially replaced callee supplies no such identity proof.

For this proof, establish all of the following before classifying a component as growing:

  1. Each equation in the required dependency closure is built from a finite set of known seed types, results of other equations in that closure, finite joins, and constructors preserving their operands as proper structural components. Apply the actual participation, contextual typing, substitution and normalization rules. Every result dependency has an equation in the closure. An unsupported or unfinished outgoing dependency prevents this proof for every equation depending on it, even if an apparent cycle can be drawn without that dependency.
  2. For each occurrence of another equation's result, record an edge from the consuming equation to that dependency. Mark the edge as CONSTRUCTING only when at least one proper constructor enclosing that occurrence survives the complete contribution transfer and normalization. An unwrapped result copy is not constructing. Joining results is not by itself constructing.
  3. Partition the dependency graph into strongly connected components: two equations belong to the same component exactly when each is reachable from the other by following dependency edges. A single equation with no self edge has no cycle. A component is growing under this proof exactly when it contains a constructing edge whose target is in that component. Such an edge lies on a cycle; every traversal of that cycle preserves an additional proper structural component, giving unbounded structural depth.
  4. A growing component has no finite result under these equations and produces the non-convergence type error. The diagnostic identifies a declaration in that component; an acyclic consumer is not itself evidence of a growing cycle. Do not publish an intermediate approximation as a completed inferred contract. A component with only copy/join edges is not rejected by this proof; complete its finite closure under 1.6.2.3.

A constructor on an acyclic path to a finite result does not prove growth. A projection, a substituting annotation or an absorbing unknown contribution can remove a would-be constructing edge or prevent the closed-equation proof altogether. Merely seeing a constructor in a source expression is therefore insufficient. Failure to establish these premises is not a type error or a completed unknown result; continue the ordinary contribution computation, use another sound proof, or apply 1.6.2.8 on resource exhaustion. An unrelated unsupported component does not invalidate an independently closed proof.

Example 1 (Informative)

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";
Example 2 (Informative)

Here the constructor is outside the recursive component. The two recursive functions close over the finite Number seed. The array in 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";
Example 3 (Informative)

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 finite closure. The source evaluates to "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";
Note

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-site caches or reference-liveness checks.

1.6.2.8 Return Inference Resources

Resource exhaustion during a required return-contribution computation is distinct from a proof that its equation has no finite fixpoint. An implementation may bound contribution evaluations, distinct provisional answers and the number of contribution queries active at once. These are host-defined resource limits, not alternative rules for inferred types. A source check that exceeds such a limit is abandoned with a StaticTypeError identifying inference-resource exhaustion and the declaration being computed. It supplies no inferred result. In particular, a chosen number of changed approximations establishes only that the implementation spent that allowance, not that further approximations would change forever.

  1. One resource state covers one invocation of the containing source check, including declaration preparation, dependent initializer queries, nested function queries, protocol-component queries and any reconsideration required before the check finishes. Reconsideration does not reset that state. A subsequent independent source check starts a new state; an interleaved check in another agent has a distinct state.
  2. Charge one contribution evaluation before recomputing a function body's contribution for a requested result component. The active-query count includes that query until it returns. Reading a previously available answer without recomputing its contribution is not an evaluation. Required input invalidation still requires reconsideration under 1.6.2.12.
  3. Charge a distinct-answer unit at the completion of a contribution query, before supplying its answer to a consumer or publication step, according to 1.6.2.9. This boundary applies to function declarations, expressions, arrows, methods, accessors, contextual callbacks and protocol-component queries. It is not conditional on publishing a named signature or runtime contract. Recreating a query or signature does not create a new declaration for a per-declaration allowance.
  4. The counters begin at zero. A request that exactly consumes its allowance is permitted. The next request marks exhaustion before performing the charged work or installing the result. Exhaustion is sticky until the containing check ends; unwinding a query does not clear it.
  5. At that checking boundary, exhaustion takes precedence over an incidental result, absent result or mismatch produced while abandoning the computation. Do not publish or retain provisional signatures, inferred runtime contracts, successful checking obligations or inference-cache entries derived from the abandoned check as completed results. Retain explicit contracts and restore the suspended checking environments. Evaluated user code cannot handle exhaustion and select a fallback.

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 1.2.7.4. A return annotation may remove a required inference; it does not waive the checks required by that annotation. These resource rules change neither ordinary evaluation order nor reference liveness.

Note

The accounting units above distinguish recomputation, reuse and distinct answers; they do not prescribe a common dependency schedule, a common acceptance floor, or a total cost model for all static analysis and type evaluation. Different conforming resource limits or schedules can therefore cause one implementation to report exhaustion while another completes the same required judgment. A cache cannot turn an incomplete result into a successful answer, and allocation or formatted type text does not establish input identity.

1.6.2.9 Return Contribution Answer History

Distinct-answer accounting associates each answer with its source function declaration or function-producing syntax node and with the requested contribution component. For a generator DoExpression, the expression is the source owner of its body-contribution queries. The components are the ordinary return result, an async function's fulfilled result, a generator's yielded result, and a generator's completion result. Async generator yield and completion queries use the same component distinctions after their specified awaited transfers. A reconstructed signature, a new query object or a repeated preparation pass retains the same source owner for this accounting.

  1. Each source owner has one distinct-answer allowance within the containing source check. Its history contains pairs of a component and an answer. Known types, including 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 contribution-evaluation allowance. An available recursive approximation, including the initial never contribution, remains subject to 1.6.2.3.
  2. Two history entries are equivalent only when their components are the same and their answers are equivalent. Two completed unknown joins are equivalent to one another, but not to never or an explicit any type. Compare known answers with the existing type-equivalence judgment. Equality of a generator's yield and completion types does not merge those two components.
  3. If an equivalent pair is already recorded, no additional distinct-answer unit is charged. Otherwise request one unit from the source owner's remaining allowance before recording the pair or using its answer. All components and all required checking contexts of that source owner share the allowance. A zero allowance permits no first answer. Returning to a previously recorded answer consumes no new unit.
  4. The history denotes answers as they were when recorded. Later completion or mutation of an implementation's signature, alias or composite-type records must not rewrite earlier entries or make a novel answer appear to have been seen already. Preserve the nominal declarations, activation identities, deferred environments, symbols and other identities required by type equivalence; copying an identity-bearing object does not manufacture an equivalent identity.
  5. Constructing a Promise or Generator carrier from component answers, copying an answer into a provisional signature, or publishing its runtime contract does not itself constitute another body-contribution answer. If one of those operations initiates a further contribution query, that query is accounted for at its own completion. The distinct-answer allowance therefore cannot be bypassed by avoiding signature publication or multiplied merely by representing an answer at several publication sites.

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 1.6.2.10 when reusing a judgment. Actual recomputation still spends a contribution-evaluation unit. Resource exhaustion uses the containment, precedence and restoration rules of 1.6.2.8; it is not evidence of non-convergence.

Example 1 (Informative)

The arrow's return contribution is an answer even though no named function declaration is published. With a distinct-answer allowance of zero, a checker computing that contribution reports inference-resource exhaustion before evaluating this Script. With sufficient resources the Script evaluates to "ok".

const identity = (value: uint32) => value;
"ok";
Example 2 (Informative)

The yielded and completion answers are both uint32, but belong to different components. Recording those two answers consumes two units from this function's distinct-answer allowance. Repeated computation of either same component answer does not add units. Building its Generator result is not a third contribution answer.

function* values(value: uint32) {
  yield value;
  return value;
}
"ok";
Note

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 set a portable acceptance floor or define complete query-input identities for caching, and they change neither runtime object layout nor reference liveness.

1.6.2.10 Inference Query Identity and Validity

A logical inference query identifies the judgment being requested, its source owner and its applicable checking context. For a body-contribution judgment, the owner and component are those of 1.6.2.9. For a dependent initializer, expression, flow transfer or checking obligation, retain that judgment's source position and role as well. A query's identity, the validity of a previously computed outcome, and equivalence for answer accounting are separate judgments. A new allocation does not necessarily denote a new query; equal answers do not establish the same query.

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.

InputRequired distinctions
Source and lexical resolutionThe source owner and source position, declaring lexical environments and resolved binding identities, including shadowing, imported bindings and nominal declarations. Identifier spelling alone does not identify a binding.
Parameters and generic bindingsEffective 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 contractsThe 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 capturesThe applicable receiver contract and its declaring owner, lexical receiver adoption and captured binding contracts. An ordinary nested function does not borrow the receiver of the query that requests it.
Flow and completionThe source-position current-value domains, effects, places, admitted predecessors and completion state required by the transfer. A captured operand, a later store and a fixed declaration contract remain distinct. Retain any reference-origin, permission or liveness input required by the judgment; a cached declaration contract supplies none of those facts by itself.
Checking boundaryThe containing source check and its applicable phase, enabled proposal features, checked-code classification, available declarations, imported contracts, realm-dependent intrinsic assumptions and completed host or type-evaluation inputs. A parse-time query with unresolved imports is not a completed link-time query.

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 published inferred contract are distinct from an unfinished computation. Participation, declaration provenance, required diagnostics and any dependent transfer outputs are parts of the outcome when the consumer uses them; recording only the displayed return type is insufficient.

  1. Before reusing a completed outcome, establish that the request denotes the same judgment in an equivalent applicable context, and that every dependency observation on which that outcome relies remains valid. Establish equivalence by the existing rules for each input: preserve declaration and other identity-bearing inputs by their required identity, and compare value or contract inputs by the equivalence appropriate to their use. Assignability alone is insufficient.
  2. If a required dependency changes or completes, invalidate the dependent outcome unless its continued validity is established. Reconsider the affected judgment with its source-position inputs under 1.6.2.17. If its validated outcome remains equivalent in every part used by a downstream consumer, that consumer need not be recomputed solely because an upstream computation ran again. Equality of the result type before validating the changed inputs supplies no such proof.
  3. Retain the dependency relation when an outcome is reused. A cache hit must not make a required dependency invisible to a later invalidation, nor make an unfinished dependency appear completed. A recursive approximation can be reused only as an approximation under 1.6.2.3; it cannot be installed as a completed result by a cache lookup.
  4. Keep the meaning of saved inputs and observations stable. Mutating a compiler-owned signature, type record, environment map or dependency record cannot retroactively change what a prior query observed. Preserve source, nominal, activation, deferred-environment and symbol identities. An immutable representation, a snapshot or validated dependency versions may establish this requirement; the specification does not require copying runtime values.
  5. Reuse must retain the requested judgment's complete effect on static checking, including required diagnostics and dependent outputs. Discarding a speculative contextual attempt's diagnostics does not turn its failed checking outcome into a successful one when that same judgment is requested again. Conversely, a failure belonging only to an inapplicable contextual alternative must not reject an applicable alternative. Reusing a scalar expression type is insufficient when the request also requires its source-flow transfer or deciding-test judgment. Restore the suspended checking environment after a nested query. No reuse initializes a binding, executes user code, establishes a runtime effect, extends reference liveness or authorizes check elision.

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 contribution answer imported into a new source check is subject to that check's answer-history allowance before use; a retained cache cannot bypass its first-answer charge. Reading a valid available answer does not spend a body-contribution evaluation, while actual recomputation spends the evaluation specified in 1.6.2.8. This distinction does not prescribe a portable cache, dependency schedule, numeric limit or acceptance floor.

Example 1 (Informative)

The yielded String violates the generator's contextual component contract. The Script has an assignability type error before evaluation, including when array-member adaptation first asks about that operand speculatively. A cached Generator type cannot substitute for the failed operand check. Replacing "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"; }]
};
Example 2 (Informative)

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);
Note

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.

1.6.2.11 Lexical Environments of Contributions

The contribution judgments in 1.6.2.2 use the binding environments of the contributing source position. They do not use a single name map for the entire function. Determine these environments as follows, without executing source text.

  1. Establish the function's parameter, variable and body lexical bindings according to declaration instantiation. A VariableDeclaration belongs to its variable environment even when its occurrence is nested in a block, loop, handler or switch. A declaration without an initializer does not replace an existing binding's value. An unannotated var redeclaration, including a destructuring one, does not erase the contract of the binding it reuses. Preserve the parameter/body distinctions required by 1.7.9.1.
  2. On entering a lexical block, establish its declared bindings before reading any contribution in that block. This includes names in destructuring patterns and declarations that follow the contribution in source order. An unknown contract still shadows an outer same-named binding. Establishing a name or contract does not initialize its runtime binding or suppress its temporal-dead-zone checks.
  3. Establish local function declaration signatures throughout the scope that owns the binding, including before their declaration text. All sibling declarations in that scope are available before following a result dependency. Construct the signatures and overload sets by the ordinary declaration rules, using declared information for overload identity and ranking. Resolve signature annotations and body dependencies in their declaration environments. A local declaration with an unknown result still shadows an outer same-named function. Its body supplies a separate inference query when needed; its returns and yields are not added directly to the enclosing function's contributions.
  4. A switch discriminant is outside the CaseBlock's lexical environment. All clauses of that CaseBlock share one lexical environment, including declarations in later clauses. A nested block in a clause has its own environment.
  5. A lexical loop-head binding belongs to that loop's environment; a var loop-head binding belongs to the enclosing variable environment. Preserve the base-language head, iterable-expression and per-iteration binding scopes. Loop-body blocks have their own environments. A loop-local binding supplies no type or provenance to a read outside its scope.
  6. Each catch clause has its own parameter environment and body block environment. Its filter and binding annotations supply contracts according to 1.4.4; an unannotated parameter with no known filter is unknown. Sibling handlers and the finally block do not share these local bindings. Collect each syntactic handler once, regardless of an implementation's representation of a list of handlers.
  7. Associate initializer contribution types, numeric-constant facts and anchoring provenance with the binding identity that supplied them. Leaving a scope removes its local facts from lookup and reveals the enclosing bindings again; it does not remove a surviving variable-environment binding. A nested function's returns and yields belong to that function, and a class body or static block does not contribute to its enclosing function.

Every contribution computation, including a function literal's contextual result query, may read an unparticipating callee's provisional answer as specified in 1.6.2.2. That use does not publish the callee's result or create a declaration-derived anchor. Apply 1.6.2.12 to required local results and captured binding contracts as to other dependencies. Reconstructing an equivalent signature or temporary inference record is not growth of its result type.

A local class constructor used by a construction contribution is resolved to its established lexical class origin at that source position. An unapplied constructor reached through stable ordinary immutable value aliases retains that origin, including a class expression bound by an ordinary immutable declaration, alias chains and parenthesized expressions. Written arguments are interpreted in the construction's source generic environment; contextual, inferred and defaulted arguments follow 4.9. The resulting specialization supplies the contribution's type. This declaration information is available to a required contribution query before an ordinary source walk reaches the class or alias declaration. A same-spelled class from another lexical environment does not supply that information, and a local binding with a built-in's name does not acquire that built-in's constructor contract. Explicit erasure, mutation, reference and decorator rules still determine whether an origin is established. This does not evaluate a class or initializer, initialize its runtime binding, bypass its temporal dead zone, or change reference liveness.

Retaining a constructor origin preserves its class declaration dependency and the runtime class identity associated with the corresponding lexical binding instance under 4.8. The alias introduces no new class identity. Another evaluation of the same declaration does not supply the target of an independently checked result contract, and the arbitrary value tested against that contract cannot establish its missing dependency.

A transparent initializer contribution is local evidence about that contribution, not an annotation on the captured storage. When querying a nested function, a captured ordinary unannotated let does not acquire a contract from the enclosing contribution walk's initializer alias. A typed constant retains the binding contract supplied by its participating initializer, and a reference binding retains its referent's contract under the existing reference rules. Preserve declaration provenance separately in each case. A constant initialized from a contract-free mutable alias does not acquire a typed capture contract merely because that alias was transparent to the enclosing contribution query.

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 capture or liveness.

These rules select bindings; the existing contribution, iteration, catch, mutation-stability and dependency rules determine their types. They do not infer a type for an ordinary unannotated mutable binding, make a literal-only contribution participate, establish a path as reachable, or substitute source visitation order for unresolved dependencies. A published inferred result still cannot justify check elision. Reference aliasing, reference liveness, effects and runtime initialization order are unchanged.

Note

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.

1.6.2.12 Publication Dependencies and Checking Order

The results required by 1.6.2.3 are independent of declaration visitation order. Establish the lexical identities and available contracts of a declaration scope before finalizing obligations in that scope that consume inferred returns. This applies to Scripts, Modules, function bodies, blocks and switch scopes, including consumers preceding a function declaration or the declaration of a captured binding. It also applies when the consumer is in an uncalled body or a branch whose ordinary Evaluation is not reached.

  1. Resolve each inference dependency in the environment of the declaration whose contribution is being computed, including its parameter and type-parameter environments. A caller that requests the computation does not supply its own same-spelled bindings. Untyped and unresolved bindings still shadow outer bindings.
  2. Read a captured binding's contract as specified in 1.6.2.2. A required inferred binding contract may itself depend on the Static Type of its initializer and on published returns. Establish those dependencies using their existing participation and type rules. If that Static Type depends on flow at the initializer, retain that dependency; replacing it with the broader declared type of an initializer operand is not an equivalent computation. This does not evaluate the initializer, invoke a function, obtain a runtime value, or initialize a binding.
  3. When a contribution requests an unpublished callee result, compute the callee's contributions under the inference-fixpoint rules. A provisional result is available to a participating inference that asks for it, but the mere existence of that provisional result does not make the callee participate and does not anchor the caller. A declared result or a result published by a participating callee supplies declaration provenance under the existing anchoring rule.
  4. Complete the applicable inference dependencies before finalizing a consuming assignability, narrowing or other type-checking obligation. If publication changes an input previously used by an obligation, reconsider that obligation. An unavailable input remains unresolved under its defining rule; declaration visitation order alone cannot be the reason to treat an established contract as any.

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 contribution rules of 1.6.2.23 before building the callee's result type: an async result uses its fulfillment contribution, and a generator keeps its yielded and returned contributions distinct. A generic protocol shape alone supplies no annotation-derived anchor. A provisional protocol result remains private on the same terms as a provisional ordinary return; requesting it does not publish it for a non-participating callee.

For an async fulfillment contribution, apply the established awaited-result projection to each known union alternative before joining the contributions. A conditional union of a direct value and an intrinsic Promise supplying that same value type does not add a Promise layer to the fulfillment result. Apply the same projection to an async generator's returned contribution, independently of its yielded contribution. An unresolved assimilation alternative remains unknown; no hook is invoked by this inference. Joining raw Promise wrappers before projecting them is not an equivalent transfer and cannot justify a recursive-growth error.

For generator inference, an unknown yielded or returned contribution remains any in that component; it is not replaced by void or by the other component. An empty yielded-contribution set contributes never, while normal completion contributes undefined only to the return join. Declared locals and unchanged initializer aliases use the same contribution and provenance rules in both joins. Delegation supplies its yielded contribution under 33 and retains the provenance of that contribution. Neither a known return nor a known yield makes an unknown component known, and the input component is not inferred.

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 1.3.8.

Contract preparation does not change ordinary initialization order or reference liveness. If no type error is established, a forward value read still observes its ordinary temporal dead zone or uninitialized value. In particular, preparation neither runs a captured initializer early nor converts an ordinary runtime Reference Error into an inferred-return diagnostic. A known inconsistent typed consumer is rejected at the specified checking boundary even if ordinary execution would first encounter a different runtime error.

1.6.2.13 Initializer Contracts and Dependent Obligations

An obligation that consumes an inferred binding contract follows 1.6.2.12. Its inputs include the binding's participation, the Static Type of its initializer, and the incoming flow facts required to compute that type. A dependency identifies a declaration and a source position, not only a name or a type representation. A completed unknown Static Type and a computation whose required inputs are not yet established are distinct: only the former is subject to the ordinary rules for any.

  1. Establish the incoming flow for the initializer under 1.8.5, and compute its Static Type in its lexical, generic and contextual environments. Apply the binding form's existing participation and widening rules. An earlier established branch or store can therefore affect the inferred contract; a later store does not replace that contract with the later value's type.
  2. If an input to this computation is unfinished, retain the dependency and complete that input under its defining rule before the binding contract is final. This includes an inferred callee result used by an earlier test or store. When that result becomes available, recompute the affected flow transfer and its dependent initializer facts; merely completing the callee does not validate the flow facts computed without it. Apply the existing fixpoint rules when these dependencies are recursive.
  3. Recompute a consuming obligation whose input changed, retaining the consumer's own source position, lexical and generic environments, contextual type, receiver and enclosing function context, and applicable incoming flow. Resolve each producer in its own declaration environment. Do not use the environments or outgoing flow at the point when the work happens to be scheduled.
  4. Finalize the obligation only after its required inputs are established. An intermediate acceptance or rejection that depended on an unfinished input is not a final judgment. A completed unknown input retains the existing gradual rules and runtime checks; it supplies neither an annotation-derived anchor nor a proof of incompatibility. A failure to complete required work follows its existing failure rule, and is not converted into a completed unknown type.

Default-value compatibility is a consuming obligation. Apply these requirements to parameter defaults, defaults within binding patterns, and instance or static field initializers whenever their existing rules require checking the initializer against an established target type. The target is the declared or contextual type required at that source position. The fact that a function is uncalled, a default is not selected by a particular invocation, or an instance is not constructed does not discharge an established incompatibility.

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 field initializer retains its class, type-parameter, private-name and receiver contexts; an instance receiver and a static receiver are not interchangeable. These are requirements on the source judgment, not instructions to evaluate a default or allocate an instance during checking.

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 unit under 1.2.8.

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 any, suppressing a required diagnostic, or making completion depend on an implementation retry or time limit.

Example 1 (Informative)

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 type error. Moving the call to 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. Completion propagates both a known participating contract and a completed unknown result; the latter does not prevent completion of dependent computations, but it supplies no new participation. A cached provisional type or an earlier visit to the declaration does not establish that its prerequisites are complete.

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 1.6.2.11, including when its initializer follows the requesting contribution. Resolve the initializer in the binding's own parameter, type-parameter, lexical and receiver contexts. A requesting local function does not supply its own parameters or receiver to that initializer. The initializer's contextual type and incoming flow are those of its source position.

  1. Where the binding has a declared contract, use that contract under the existing binding rules. Otherwise, determine whether its binding form requires or admits an inferred contract. An ordinary unannotated mutable binding does not acquire a contract merely because its initializer is transparent to an enclosing contribution query.
  2. For an inferred contract, establish the initializer's required binding, result and flow dependencies. Apply typed-constant participation or the typed-initializer rules, as applicable. A provisional callee answer available to a contribution query is not thereby a published contract available to constant inference.
  3. Keep a completed binding contract distinct from any initializer contribution type and provenance that the enclosing function may use locally. An enclosing contribution that reads a transparent initializer need not wait for an unrelated capture-contract computation. Conversely, reading that binding from a nested function requires its binding contract; the local contribution type cannot substitute for it.
  4. Propagate completion through every dependent result and consuming obligation under this clause. Encountering an initializer dependency again while computing it does not establish any or a type error; apply the existing recursive dependency rules. Reconstructing a lexical environment or dependency representation does not create a new source binding or change its contract.

A required reference-binding contract is also an initializer dependency. Apply 2.5 in the reference declaration's own lexical, parameter, type-parameter and receiver environments, including when the declaration follows a requesting contribution. For an initializer that resolves to a named lexical location, use that location's declared or completed inferred binding contract, not the current-value narrowing of a read. Parentheses do not change this location judgment. An alias chain retains the contracts and declaration environments of its source bindings. A transparent initializer contribution of an ordinary untyped binding does not supply a location contract.

Keep the value type of a reference binding separate from its current origin, write permission, runtime initialization state and reference liveness. Establishing its contract neither obtains a runtime location nor reads its value, initializes the reference, executes an initializer or permits an otherwise invalid borrow. Rebinding retains the fixed binding type under 2.5; an established type does not prove that every location subsequently selected through that alias admits a store. If selecting the source location requires a receiver, key, call result or other unresolved input, retain that dependency under the existing location rules. Do not substitute the requesting function's flow or turn an unresolved input into a completed unknown contract.

Example 2 (Informative)

The reference s has the String contract of the parameter x, even though the contribution of g requests it before the declaration is visited. The Number consumer is a type error. A String consumer is compatible, but executing the call still reads 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 value type.

function outer() {
  function f(x: string) {
    return g();
    function g() { return s; }
    let ref s = x;
  }
  const q: number = f("s");
}

An unavailable contribution result does not prevent discovery of a declaration on which that result depends. Establish the required declaration and initializer dependencies even when their source text follows the return or yield requesting them, or is unreachable under ordinary Evaluation. A completed unknown contribution affects the join under the existing gradual rules; it does not exempt other source text from its declaration, dependency or checking obligations. Merely encountering one unknown or unresolved result is not a completion criterion for the enclosing source scope.

If a contribution judgment is performed after its source position has been traversed, retain that position's binding identities, contextual type, receiver, parameter and type-parameter environments, initialization state and incoming flow. Do not use the flow after a later store, a sibling branch or a suspension in place of that source judgment. Traversing a contribution's operand for effects or further contributions is distinct from obtaining its type: deferring a yield's type does not omit a nested yield, delegation input or contribution inside a test, and does not move suspension effects. Completing a required binding contract invalidates any dependent result or obligation that used an unfinished answer. The completed contract remains distinct from contribution-only initializer evidence.

For a contribution within a switch, use the clause entry formed by the selected-label environment joined with normal fallthrough from the preceding clause. Establish the default selection environment after all failed labels, including those textually following the default. A CaseClause or DefaultClause without a StatementList contributes no declarations or statements, but remains part of label selection and the normal fallthrough relation. Its absence of statements does not discard its label, label effects, or incoming flow. Establish the shared case-block declaration environment before resolving clause contributions.

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 path are inputs to later selections and to the default. A write that replaces the original discriminant's place prevents refining its new value from the captured discriminant. Apply the existing expression-effect and place-identity rules, and consume only breaks targeting this switch when forming its successor. A contribution query is not permitted to replace these predecessor relations with independent copies of the switch's original entry.

Contribution inference records and consumes immutable Boolean producer relations under 1.8.5.1 in the same declaration and source environments used by ordinary checking. This applies to direct and composed aliases, tests controlling statements or expression operands, conditional result arms and switch selection, including an initializer contract requested by a nested contribution. Process successive bindings of one declaration in source order, including their initializer effects. A Boolean copy does not freeze a mutable subject or supply a narrower captured storage contract to a nested function.

A delayed expression-type query retains its source position's normal-completion state as well as its flow facts. A return or yield contribution is queried where its operand is evaluated, before the enclosing abrupt completion or suspension; a later return, throw or sibling branch cannot make its conditional arms lack their original incoming environments. Restore the requesting computation's completion state after the query. This requirement preserves the existing rules for inspecting unreachable source and joining contributions; it does not introduce a new unreachable-code exemption or change runtime initialization.

A captured switch discriminant retains the result-to-source relations established by 1.8.5.1, including the result environments of a Boolean expression or preserved predicate and the evaluated and skipped alternatives of an optional chain or its typeof result. Apply the effects of each reached label to those relations before selecting its entry. Pass the remaining alternatives along the failed-selection path, excluding only values established to match that label under the applicable selection rule. In particular, a failed singleton label can exhaust an optional result's evaluated alternatives; subsequent labels and the default cannot restore those alternatives. A non-singleton label's type does not by itself exclude its whole domain. This rule applies both to a direct contribution and to an initializer whose contract a contribution consumes; reducing the captured result to its scalar type does not discharge the required source-flow judgment.

The incoming flow used by a contribution query is the flow at the contributing expression or initializer under 1.8.5.1. In particular, a guarded abrupt completion does not contribute a normal predecessor to a following initializer. Join the remaining normal predecessors, and retain the destinations of breaks and continues. Loop entries and exits, switch selection and fallthrough, exceptional entry and finally transfer use their existing flow rules; syntactic visitation order is not an additional predecessor. An inference query for a nested function has its own completion state and cannot terminate or revive the enclosing query's path.

Source flow includes the current-value facts established by successful participating initializations and stores under 1.8.5.1. Determine the source value at its production point using the ordinary Static Type judgment and the contracts available to that judgment. Process operand evaluation, required conversions and store effects in the specified order, then establish any permitted destination fact from the resulting stored domain. For sequential expressions and successive bindings, the effects of each preceding expression are inputs to the next expression. A right operand's type before conversion is not necessarily the type of the stored value. Preserve the declared destination view for mutable objects, callable contracts, predicates and contextual void; a more specific initializer shape or signature cannot replace that view.

A provisional callee result available only to contribution inference does not become an ordinary store or capture contract by being assigned to a local. Likewise, transparency of an ordinary untyped local to an enclosing contribution query does not make that local a typed storage destination or a participating source for constant inference. Apply the existing alias and effect invalidations even when a store establishes no new fact. A reference store is a write to its referent, with that location's contract and alias effects; it does not turn the reference into an immutable copy. If a required source, conversion or flow input remains unresolved, retain the dependency and reconsider its affected transfer and consumers when it completes. A completed unknown supplies no unsupported stored-domain fact.

Apply the existing effect and invalidation rules in evaluation order, including between bindings in one declaration and across suspension. A fact invalidated before an initializer is not an input to that initializer's contract. Once established, an inferred constant contract is a contract of the new binding: a later store to the initializer's source binding does not alter it. A nested function reads that contract, but does not inherit a narrowing of other captured mutable storage from the point where its result is requested. A reference binding retains the existing location contract and liveness rules; it is not an immutable copy of the referent's currently narrowed value. These requirements do not discard contributions or exempt unreachable source from checking under the existing rules.

Example 3 (Informative)

In this example the only normal predecessor of s has narrowed x to string. The inferred contract of s, and hence the contribution of g, is string. The String consumer is compatible; changing its annotation to number establishes a type error at the checking boundary. Replacing 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 Static Type judgment. In particular, using a wider declared operand type in place of an initializer's required narrowed type is not such a proof. A flow-independent implementation path does not discharge dependencies left outside that path, and an unsupported preparation path is not a completed unknown type.

Apply a binding form's validation to the result of its completed initializer computation. In particular, the requirement that a typed initializer have a Static Type other than any is not a requirement that an implementation already possess that type when it first visits the declaration. Conversely, scheduling a later computation does not discharge that requirement: a completed unknown result still fails it. A cycle follows 1.6.2.3; waiting for an unfinished dependency alone establishes neither a completed unknown result nor non-convergence.

These requirements apply at the checking boundary of 1.2.8, including obligations inside uncalled functions and otherwise unreachable source. They do not execute an initializer, call a function, initialize a value binding, extend reference liveness or authorize check elision. An implementation may store obligations or recompute equivalent judgments; no particular queue, frame representation or traversal count is required.

Example 4 (Informative)

In the following source, the initializer of s has Static Type 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-any requirements to that completed result. These computations execute none of those initializers or calls.

Likewise, in function f(x: string) { return g(); function g() { return s; } const s = x; }, the captured constant has a contract derived from the parameter of 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.

Example 5 (Informative)

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 capture gives the same judgment.

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 captured contract is 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.

Example 6 (Informative)

The contribution of 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. Evaluation of the call still encounters the ordinary temporal dead zone; establishing these contracts does not execute or make the initializer reachable.

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");
}

1.6.2.14 Completed Binding Contracts at Consuming Positions

A read of an inferred binding consumes the completed contract required by 1.6.2.13. The binding's first observed type is not a permanent answer when its prerequisites were unfinished. Apply the following requirements to each dependent consuming judgment.

  1. Identify the source binding by lexical identity. Determine its inferred contract in its initializer's source environment, using the binding form's participation and widening rules. Complete required binding and result dependencies under 1.6.2.12; an implementation's cached unavailable answer is not a completed unknown.
  2. Use that completed contract together with the flow facts valid at the consuming source position. Do not replace a subsequent store's current-value fact with the initializer's earlier fact, or compute the initializer under the consumer's later flow. A constant's inferred contract is not changed by a later write to the initializer's source.
  3. If completion changes an input to a judgment already attempted, reconsider that judgment at its original source position. This requirement propagates through chains of inferred constants, typed initializers, inferred function results, and their consumers. Preserve the original lexical, contextual and checked-code environments.
  4. Apply the existing deciding-test and transfer rules to the resulting type and source facts. This includes tests in conditionals, iteration, logical and nullish expressions and assignments, switch selection, and guarded patterns. A value query does not become a deciding test merely because completing its operand establishes a constant answer. An unchecked source position does not become checked through the completion of a dependency.
  5. Retain diagnostics established by completed declaration obligations even when their contracts were requested during a speculative query. A discarded speculative consumer does not discharge an independent declaration requirement. Conversely, an unfinished attempt is not a completed diagnostic. Apply the existing recursion rules where dependencies form a cycle.

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.

Example (Informative)

The completed contract of s is String, so f publishes String and the participating constant q has that contract. The deciding test is a type error even if an implementation first encountered 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();

1.6.2.15 Dependent Store Facts and Captured Writes

The completion requirements of 1.6.2.14 apply to source-flow transfers as well as declaration contracts. A store whose result depends on unfinished inference is not a completed unknown source. Determine its required judgment under 1.8.5.1 using the following requirements.

  1. Identify the destination binding or established place and the value produced by this store separately. Retain the source environment at the assignment's evaluation point, including the declared destination contract, required contextual typing and incoming facts. Complete the source's required dependencies in their own environments.
  2. Check the completed source against the declared destination. Apply operand evaluation, conversion and store effects in their specified order. Establish only the stored domain justified after successful completion by the existing transfer rules; the source's pre-conversion domain is not automatically that domain. A completed unknown source retains ordinary runtime boundary handling and supplies no additional refinement beyond an established successful-store contract.
  3. Reconsider each judgment that consumed an unfinished store fact, including initializer contracts and return contributions. A constant capturing that store's value uses the fact valid at its initializer, even when completion is requested after another assignment to the source. Completing the old store does not overwrite a later store's fact or restore a fact invalidated by an intervening effect.
  4. At each flow join use the actual incoming alternatives and their completed transfers. A missing fact is an unknown predecessor; an unfinished fact remains a dependency. Neither is an impossible predecessor. An implementation's internal placeholder is not the any type and cannot discharge a dependent obligation. Complete dependent deciding tests and their result environments at the original source positions before finalizing their consumers. The same requirements apply to normal and abrupt completions, loop widening, switch selection and finally transfer.

Call-effect invalidation identifies lexical bindings and their invocation-owned cells, not merely names occurring in assignment syntax. For the bounded analysis in 1.8.5.1, a write in callable code that captures a binding is a possible write to that binding at a call or other operation that can invoke such code. A write only in the binding's own invocation is handled at its ordinary source position; a recursive invocation has distinct parameter and local cells. A same-spelled binding in another lexical scope supplies no captured write to this binding. A nested callable's write to the outer binding remains a captured write even if the declaration is textually later.

Apply this distinction to the existing call, getter, setter, conversion and suspension effects and to construction, including constructor bodies and instance field initializers. It is not a general proof that arbitrary calls have no effects: exposed references, uncertain alias effects, dynamic name resolution and other existing invalidations remain in force. An exposed mapped arguments object permits writes to the parameters it aliases without a lexical assignment to their names. An arrow's lexical arguments can expose its enclosing ordinary function's object. Unmapped arguments, including those of strict functions and functions with non-simple parameters, supply no such parameter alias. Checked-code admission alone does not make a legacy function strict; annotation-owned strictness retains its existing rules.

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.

Example (Informative)

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 type error; changing its annotation to String makes that consumer compatible. Calling the compatible 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 capture Number. The same assignment after that initializer cannot change s to Number. A call between the store and capture invalidates u if callable code captures and may write that binding. The assignment to u in outer itself is not such a captured write.

1.6.2.16 Deciding Tests with Unfinished Dependencies

An unfinished input to a deciding test leaves a checking obligation under 1.6.2.14 and 1.6.2.15. It does not turn the test into a permitted dynamic test or a value query. Retain the test's source position, lexical and checked-code environments, deciding role, incoming source facts, and required binding and result dependencies.

  1. Complete each required input in its own source environment. An independent later value producer may supply an input even when an earlier transfer remains unfinished. Determine independence from the producer's actual value and flow dependencies, not merely from whether it occurs later in the same statement list. In particular, a fresh initialization from an established value can supply a contract independently of an earlier unrelated test; a read of a place whose required incoming facts remain unfinished cannot.
  2. Apply the completed test judgment in its retained source environment. Use the deciding and truthiness roles established by the surrounding syntax, including their propagation through tested expressions. A later assignment, shadowing declaration, call or abrupt completion cannot replace the facts at that earlier position. A plain value query remains a value query, and the existing checking-unit and literal-idiom rules remain in force.
  3. Discard a provisional diagnostic that required an unfinished input. Retain independent declaration diagnostics. If reconsideration discovers another required unfinished input, the judgment remains an obligation until that input is completed; an intermediate attempt is not a final verdict.
  4. Complete the test's dependent result environments and consuming transfers using the existing narrowing, evaluation-order, feasibility and completion rules. Reconsidering only a diagnostic does not discharge a required branch-fact or downstream contribution judgment. At joins, every admitted predecessor must establish the retained fact; incomplete predecessors are not removed as unreachable.

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 captured write or reference effect invalidates the affected place facts by the ordinary rules; an unrelated same-spelled local does not. Retain the distinction between the selected call result and a binding that the call may replace. Original-source diagnostic applicability and deciding roles remain unchanged.

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 finite dependency judgment. The recursion rules of 1.6.2.3 still apply.

Example (Informative)

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 captured write before the test can invalidate that input by the ordinary effect rules. Replacing the test with 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.

1.6.2.17 Completion of Dependent Flow

The completion requirements of 1.6.2.16 apply transitively to flow transfers. A transfer consumes the source-position facts required by an existing narrowing, store, effect, selection or completion judgment and produces that judgment's outgoing facts and completions. An unfinished incoming fact is a dependency of every output that requires it; it is neither a completed unknown value nor evidence that an incoming edge is unreachable.

  1. Identify the source declaration or value-producing operation and the source-position judgment that requires its result. Distinguish a binding's fixed contract from the current value supplied by an assignment or initialization. A declared target type can be complete while the initialized value's domain remains unfinished. In particular, an annotation does not complete the domain that a subsequent inferred constant captures from that initialization.
  2. Complete each required input in its defining lexical, generic, contextual and flow environments. A completed declaration contract may be reused only for that same declaration and applicable type context. It does not carry the producer's current-value facts, runtime initialization state, selected reference origin or liveness into the requester.
  3. The applicable context includes the producer's declaring lexical environment and the effective parameter, contextual signature, receiver, generic and result contracts used by its judgment. Preserve each identity or signature distinction on which that judgment depends; assignability or type equivalence alone does not establish interchangeable contexts. In a contextual object method, apply the adopted receiver and its owner under 8.1 in both contribution inference and body checking. A nested arrow uses its lexical receiver; an ordinary function uses its own receiver contract. A provisional overload or contextual alternative cannot supply a completed contract to a different alternative merely because they share source text.
  4. Apply the consuming transfer using the completed inputs and the original source state. If the transfer was provisionally performed, replace its dependent outputs with the outputs of this completed judgment. Do not merely merge new facts into the provisional output: the completed input can change a refinement, an effect, an admitted predecessor or the presence of a normal completion.
  5. Complete every downstream judgment that consumes an affected output before finalizing its result. This includes subsequent transfers, inferred binding and callable-result contracts, and diagnostics. A completed predecessor test does not by itself complete a later test, and a completed diagnostic does not by itself complete the test's outgoing environments.
  6. At a join, apply the existing joining rule to the completed outgoing environments of all admitted predecessors. A fact must be justified on every such predecessor. Include the prescribed loop entry, back-edge, exit and abrupt-completion paths; do not infer a permanent loop fact from only the first iteration. Preserve separate value identities where an operation captures an operand before a later effect.
  7. Continue through the required dependency relation. Completion that establishes an unknown result under the existing rules also completes that input: re-evaluate its dependents with the completed unknown result even when no precise type was inferred. This supplies no additional value-domain or reference-liveness proof and does not permit treating an unfinished or cyclic dependency as completed unknown. A finite acyclic chain is completed regardless of source order or length. For recursive dependencies, apply 1.6.2.3; absence of progress in an implementation's selected worklist is not a new language rule completing an input as any or waiving a diagnostic.

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 finite acyclic completion requirement still applies, without adding a type argument or executing an instantiation.

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 finite acyclic dependency chain from completion. Reuse of a completed contract must preserve the producer context above and the source-position dependencies of its consumers. A fresh source traversal or an implementation cache is not an additional source of type information. Rechecking a judgment does not evaluate a body, initialize a binding, select a runtime activation, or make a reference live.

Completion is a property of the inputs required by a particular judgment, rather than of every fact stored in its environment. Apply the following dependency distinctions:

  1. A refinement of a place consumes that place's domain and the other operands and effects required by the existing narrowing rule. Propagating the refinement through a retained value-copy or equality relation additionally consumes the relevant relation, its stability evidence and the domains needed by that propagation. A relation between unrelated places introduces no dependency solely because it was recorded in the same environment. Place identity includes the declaring lexical binding and any selected member path; matching written names alone is insufficient.
  2. For a tested expression with no relevant write, call, property-read or other effect, a completed input domain and its justified refinements remain complete on each admitted outcome when all inputs required by that outcome's judgment are complete. Other facts may remain unfinished. This does not complete the whole outgoing environment. A subsequent join still requires justification on all admitted predecessors, and a subsequent effect can invalidate the proof under the ordinary flow rules.
  3. Reading the established contract of a function declaration whose binding is proved unchanged does not consume an unrelated unfinished current-value fact. An unfinished result contract is still a dependency of a consumer of that result. Callable replacement, dynamic name resolution, aliases capable of replacing the binding and the effects of invoking the function retain their existing rules.
  4. A call in a tested expression does not, solely by being a call, make every current-value fact unfinished. Apply the existing callee, receiver, argument, conversion and invocation judgments in their specified order, including their effects on captured bindings and aliases. Retain an independent input proof on an admitted outcome only when those judgments preserve that proof; an invalidated proof must not be restored from the pre-call environment. Complete every result-contract or flow dependency required by that outcome or a consuming judgment. An independent argument does not complete an unfinished callee result, and an available callee result does not complete an unfinished argument. Apply the same requirements to calls in extra arguments and nested deciding expressions. This is static application of the specified judgments, not execution of the call or a purity assumption based on its name or an identity-shaped body.
  5. When an applicable effect judgment invalidates a place's current-value refinement and supplies its established contract as the widened domain, complete that output from the contract and the inputs required by that effect judgment. It does not additionally depend on the overwritten refinement or on an unfinished result of the effectful expression. Record the resulting domain at the affected place's declaration identity and on the continuation to which the effect applies. A pre-effect domain cannot substitute for this output, and an evaluated continuation cannot supply the domain of a skipped continuation. Any later refinement, store, relation propagation or join consumes this output under its ordinary rules and retains its own required dependencies. This requirement neither introduces a new invalidation rule nor permits replacing an unfinished stored value with its destination annotation when no such effect judgment applies.
  6. An unfinished test result does not by itself make a different place's completed source-position domain unfinished. Retain that domain on a potential continuation only if its justification does not require the unfinished selection, refinement or effect. Include dependencies through retained copies, equality relations, saved predicates and saved type queries; absence of a relation in a provisional judgment does not prove that the completed judgment cannot establish it. A write elsewhere in the tested expression does not by itself invalidate an unwritten place. Apply the specified destination and alias effects before retaining proofs; a pre-evaluation proof cannot restore an invalidated domain. This rule supplies no evidence that the continuation is reachable.
  7. Distinguish the dependency selecting a predecessor from dependencies common to all its alternatives. Different independently established domains on potential predecessors do not make their union independent of an unfinished selection: completing that selection can remove a predecessor and change the joined domain. An identical justified domain on every potential predecessor can be independent of that selection. A completed selection can apply its ordinary join while unrelated facts or common incoming dependencies remain unfinished.
  8. Before a lexical initializer, use the binding's established contract when the existing source-position rules supply that contract and no applicable earlier refinement or effect changes the judgment. A later initialization does not supply an earlier read's domain, complete a pending store, or establish runtime initialization. In particular, an unrelated unfinished test cannot make an otherwise complete annotated contract depend on its own later initializer. The temporal-dead-zone and reference-liveness rules are unchanged.
  9. An independent input proof belongs to its source-position judgment. A query that suspends that judgment to complete another producer uses the producer's own source state, even when the two positions refer to the same lexical binding. On returning to the suspended judgment, restore its original proofs; do not lend them to the producer or replace them with the producer's current-value facts.

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. Evaluate the component only under its governing predecessor, apply its specified effects and refinements there, and join the resulting environments under 1.8.5.1. A parenthesized expression, built-in logical negation, a comma expression or an established built-in Boolean conversion preserves this component distinction under its ordinary transfer rule. A declared logical-not operator or a replacement Boolean callable instead uses its selected call judgments; its spelling does not supply this transfer. These rules neither preserve a proof invalidated on an evaluated path nor discard a real selection or relation dependency.

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.

Completion of a switch transfer has the following additional requirements:

  1. Complete the discriminant's value and applicable effects in its original source state. Retain a different place's completed input domain only when it is independent of those unfinished judgments and preserved by the specified effects. This requirement applies even when the case block has no labels, only a default clause, or no statements.
  2. Complete each evaluated label and its effects in the state supplied by the preceding failed selections. Retain the captured discriminant value separately from its source place, which a label may subsequently change. A default clause does not evaluate a label; its selection depends on the applicable failed selections, including labels textually after it.
  3. Keep a successful selection and its corresponding failed selection as distinct dependent outputs, even when their provisional fact sets are identical. A clause's entry combines its selected predecessor with any admitted fallthrough predecessor; a default entry, break successor and continuation without a matching clause keep their respective selection dependencies. No dependent output is finalized from the equality of provisional fact sets alone. Independently justified identical domains on every potential predecessor retain the independence permitted above.
  4. Apply the completed selection, fallthrough and abrupt-completion judgments to obtain the switch continuation. Recomputing these static judgments does not evaluate the discriminant or a label at runtime, change which labels execute, or convert a value query within them into a deciding test.

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, iterable evaluation and other effects that precede that predecessor under the existing transfer rules. Account for the prescribed widening, test, iteration, suspension, back-edge, break and continue transfers. Do not replace the zero-iteration predecessor with a body result, restore a fact invalidated by an applicable effect, or finalize a joined domain using only one potential exit. These requirements apply both when checking the loop and when obtaining a binding or callable-result contract from a dependent source judgment.

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 path. A loop-head or block-local store does not widen a distinct outer binding with the same name. A loop-head binding already in the current environment still widens its own domain where the loop can write it. Merely checking a nested callable's parameters or body does not execute their effects; creation-time computed names, decorators and actual calls retain their ordinary effects.

Completion of a MatchExpression additionally observes the following requirements. The pattern, guard, result and exhaustiveness rules of 26.3 remain unchanged.

  1. Establish the clause-entry state after the subject evaluation and its specified effects. A completed input domain that is independent of a later unfinished guard remains available on each potential continuation on which the ordinary pattern and effect rules preserve it. A failed pattern, a failed guard and a selected arm are distinct predecessors; do not replace their joined state with the state of only the selected arm.
  2. Apply a guard's judgments in the lexical environment of its clause's pattern bindings. Completion evidence for a different outer binding remains associated with that outer declaration when an inner binding shadows its name. Shadowing supplies neither a value for the outer binding nor evidence that its source-position domain is unfinished. Reads still select the binding prescribed by lexical resolution, and producer queries still use their own source contexts.
  3. For ordinary 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.
  4. When a match result is used as a test, apply its existing deciding role to its result producers and join their applicable outcome states. An enclosing test retains independently justified output domains produced by those component judgments even when it has no independent proof for the entire expression's input state. This does not allow copying an arbitrary pre-expression domain into the output: every retained proof must survive the component effects and be justified on every admitted predecessor. The same requirement applies through enclosing parentheses, negation, logical expressions and conditional expressions.

A whole-expression truthiness judgment for a ConditionalExpression uses its source-position Static Type, including the arm types under the selecting condition's applicable refinements. Facts conditioned on a particular true or false outcome of an arm are outputs of that arm's test; they must not become the input state for retyping the whole conditional. In particular, visiting the false outcome last cannot make an open Boolean result a necessarily falsy value. Apply the ordinary operand effects and captured-value rules; a join of post-evaluation stores is not a substitute for the conditional's result type.

Example 1 (Informative)

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 type error, as is a Number-only consumer. String-or-Number is compatible.

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 captured-write effect. Their ordinary join supplies String-or-Number to 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 capture chain: a String consumer is then compatible and a Number consumer remains a type error.

Example 2 (Informative)

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 contribution query and body checking use Shape as the owner of the method's adopted receiver. The String consumer is compatible; a Number consumer is a type error. Changing the declared property and its initializer to Number instead reverses those two judgments. Identical local names in another method do not share this result.

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 captured binding.

Example 3 (Informative)

The unfinished guard does not make the source contract of seed depend on the match's selected arm. Completing answer and id allows the capture chain to publish String, so the Number consumer is incompatible. Changing the consumer's annotation to String is compatible. A pattern binding also named 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.

Example 4 (Informative)

A captured write changes the preceding example's completion dependencies. The call effect below invalidates the narrower stored domain of 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 is a Number-only consumer. Changing it to String-or-Number removes this contract error. The effect does not establish that 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.

Example 5 (Informative)

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.

Example 6 (Informative)

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 capture 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";
}
Example 7 (Informative)

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.

Table 14: Dependent flow transfers
Source formRequired dependent outputs
if, conditional expressions and logical short-circuit expressionsEach 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-ofThe 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 allThe subject and clause-entry state; pattern effects and bindings; matched, failed-pattern and failed-guard successors; arm result and abrupt completions; and the applicable result or continuation joins. match all retains subsequent-clause paths after a successful arm. Existing per-clause and exhaustiveness distinctions are unchanged.
switchThe captured discriminant, each evaluated label and its effects, successful and failed selection facts, default selection, fallthrough, and break successors. A later write to the discriminant's source does not replace the already captured discriminant value.
Blocks, labelled statements, try, catch and finallyThe applicable lexical environments and normal or abrupt completions, including the replacement or propagation of a completion by finally. Completing unreachable source does not create a reachable successor.
Assignments, initializations and value consumersThe established destination contract, the converted stored-value domain, applicable effects, and every inferred capture or consuming judgment that reads the result at its own source position. Later stores do not alter an earlier copied value.
Example 8 (Informative)

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.

Example 9 (Informative)

Each completed String producer below supplies the next capture's contract through an open truthiness test. The Number consumer is incompatible. Changing its annotation to String is compatible; neither result depends on the number of intermediate helpers or their declaration order. The uncalled body is checked without executing it.

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 replacement for Boolean uses its own declaration and effects; the spelling is not evidence of intrinsic identity.

These requirements specify source judgments and their dependencies, not a traversal count or compiler architecture. An implementation may recompute a source region or retain individually dependent transfer results if it produces the same completed judgments. It must not evaluate ordinary source expressions to complete a static dependency, repeat program effects, or infer reference availability from contract completion.

1.6.2.18 Class Method Contribution Receivers

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 1.6.2.11 and 1.6.2.3:

  1. Establish the class's effective member contracts under 1.11.2, including the applicable inherited contracts, private-name identities, overload sets and field precedence. An unfinished inferred member result is a dependency, not a completed any result.
  2. For an instance method, the contribution environment's 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.
  3. Query each contribution using that receiver and its source-position environment. A mutable field read uses the effective storage contract and any source-flow refinement justified at that read; a field initializer's particular value alone does not narrow every later method invocation. An ordinary nested function establishes its own receiver environment; an arrow retains the lexical receiver.
  4. Complete dependencies on other inferred method results using their own declaration environments. Preserve the resulting contracts through member lookup and specialization. Substitute class and method type arguments into a published result as into a declared result, respecting nested parameter scopes. Specializing a member must not silently restore its earlier unfinished result.
  5. Apply the existing participation, contribution-join and publication rules. For asynchronous and generator methods, apply 1.6.2.23. A receiver environment alone does not publish an untyped method returning only an unanchored literal. An explicit result annotation retains precedence.
  6. Use completed published results for the consumers in 1.6.2.22, including calls, assignability and inherited contracts, before completing the checking boundary. Signature identity, overload ranking and declared-only viability remain unchanged.

Computing this receiver environment neither binds a runtime method value to a particular receiver nor executes a field initializer, getter or method. Runtime receiver binding, return checks, reference decay and liveness retain their existing rules. These requirements prescribe judgments, not a class layout, hidden property, inline cache, inference queue or traversal count.

Example (Informative)
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 type error at the checking boundary, before the Script body begins. Moving 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 published result.

1.6.2.19 Class Getter Contribution Receivers

Apply 1.6.2.18 to a class getter's result contributions. A property read that requires an unfinished getter result has that inference dependency even though no call expression is written. Complete dependencies between getters and methods in their respective declaring instance or static receiver environments, including private members, inherited members and established imported class contracts.

An unannotated getter has no parameter annotation to establish participation. Publish its inferred result only under the existing anchored-contribution rules. The effective getter's completed result supplies the property's read contract; the setter's independent write contract is unchanged. Apply the existing accessor-pair and override obligations after the required result completes. Effective descriptor replacement and field precedence still determine the member selected by lookup; inference does not restore a displaced getter or setter. An explicit result annotation retains precedence.

A getter result that depends on a class parameter retains that parameter's declaration through member lookup and specialization. A same-spelled parameter of a consuming method does not bind it. A local class retained in an inferred result also retains the free generic inputs needed by its member template. Applying an enclosing declaration closes those inputs while preserving the class's own binders and the binders of its members. Do not equate different member-query environments merely because their nominal descriptions name the same source class. This context requirement does not replace evaluated class-family identity under 4.8.

When an inferred union result contains such local class contributions, an independently admitted producer result may complete a matching contribution's declaration-activation dependency. Match the source declaration, type arguments and retained free inputs before using that evidence. A captured value parameter uses its own declared domain representation, as does a direct class argument; an exact numeric value is not a different capture solely because its checking-time description precedes that representation. The receiving boundary must still admit the actual value at the completed arm. This does not complete an unexecuted alternative's family, acquire evidence from an arbitrary candidate value, or authorize omitting the result check. If no contribution matches, the ordinary unresolved-dependency and admission rules remain applicable.

These judgments execute no getter, field initializer, constructor or method to discover a type. Preserve ordinary evaluation order, temporal-dead-zone behaviour, getter effects, receiver binding, reference permission and reference liveness. Computing a getter's contract does not bind a reflected getter function to an instance or infer a receiver for an otherwise uncontextualized object method.

Example (Informative)
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 type error before the Script body begins, whether or not unused is called. Replacing Boolean with String removes this incompatibility. Replacing the getter body with only return "ok"; removes the annotation-derived contribution and does not publish a result merely because a class receiver is available.

1.6.2.20 Object Callable Contributions

An object literal's static contribution preserves the contracts of its methods and property values. Determine these contracts without invoking a method, getter, iterator, or initializer to obtain a result:

  1. For an ordinary, async, generator, or async generator method, establish the effective signature under its annotations and any applicable contextual signature. Complete its publishable inferred result under 1.6.2. Preserve the ordinary async and generator result carriers and their declared variance.
  2. Such a method supplies a typed contribution to an unannotated immutable binding when a parameter or result annotation, contextual contract, or published annotation-derived result establishes participation. An unanchored literal-only method result does not acquire publication merely because the method occurs in an object.
  3. A shorthand property 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 1.6.2.21. The shorthand spelling grants no contextual parameter adoption to an already created function value.
  4. Preserve these contributions through the existing immutable-binding, nested-object, and returned-contribution rules. Apply the ordinary source-order descriptor rules: a later data property definition replaces the earlier descriptor at that key; it does not introduce a second simultaneous property requirement. Accessor pairing and declared overload rules are unchanged.
  5. Compare the resulting contract under the existing assignability rules. Writable members remain invariant, readonly members retain their specified depth subtyping, and generic substitution and reference-result restrictions remain applicable. Completing a signature is not evidence that a reference outlives its source.

For example, function outer() { const box = { m(x: string) { return x; } }; const target: { readonly m: (x: string) => number } = box; } is a type error even if 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.

1.6.2.21 Publication Dependencies of Callable Values

Result inference can depend on a function value without containing a call of that function. A contribution that returns, yields, resolves with, or retains a callable value carries the result contracts needed by that value's consumers. Merely reading a previously collected signature is not completion of those contracts.

  1. Resolve the contributed value in its source-position environment, using the binding-stability, shadowing and capture-contract rules. Where that resolution establishes a particular callable declaration or overload set, an unfinished inferred result of a retained signature is a dependency. A write that makes the binding's value unknown does not establish that the original declaration still supplies the value. In particular, an immutable alias does not restore the original declaration identity if its initializer may read a replaced binding; a write to an unrelated same-spelled binding is not such a replacement.
  2. For each required signature, retain its explicit result if one is declared. Otherwise complete its result inference in that declaration's lexical type, parameter, capture and receiver environments, using 1.6.2.11, 1.6.2.18 and 1.6.2.3. The requesting contribution's same-spelled names cannot substitute for those environments.
  3. Carry the completed publication through the value's aliases, returned callable layers and generic specialization before discharging a consumer's result obligation. A retained or copied signature must not permanently conceal a dependency that was unfinished when it was obtained. Apply the same rule to further callable values on which the result depends.
  4. Keep completion separate from participation. A function returning only an unanchored literal does not acquire a published result merely because another function returns it; a completed unknown join still publishes nothing. A private provisional result must not become the callable value's public result contract. An unfinished dependency is neither of those completed outcomes.
  5. A recursive edge that retains a callable value is not a recursive invocation and does not, by itself, contribute the 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.
  6. After completion, apply 1.10.2.2 at callable conformance boundaries. Declaration identity, overload formation, ranking and declared-only viability retain their existing rules. Invariant carrier arguments, writable storage and reference-liveness obligations retain their respective relations; publication does not turn an invariant comparison into a covariant one.

Determine which signatures a callable value retains by 1.4.3.2.2. In particular, a bare group with an owner contributes the owner's generic function contract, not an intersection of that contract and its attached cases. Reading that value does not bind a case's captures. Complete the participating result dependencies of the retained owner signatures before publishing a factory's callable result. The declaration group's candidates for direct calls and explicit applications remain governed by the selection rules; standalone-only groups and ordinary overload sets keep their existing contracts.

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 specialization; none of those operations binds a receiver. This applies to the retained callable contract without changing ordinary member lookup, binding stability or explicit erasure.

Instantiating an inferred binding with its enclosing generic environment substitutes the free inputs of any retained callable signature, including its published result, while preserving that signature's own binders. This substitution also completes nominal member-context captures by the captured parameter declarations and their enclosing bindings. A nested callable's same-spelled binder does not replace an outer captured declaration, and completing that outer capture does not apply the nested callable. Source-class dependencies nested in an async or generator result protocol retain the declaring callable's lexical activation under 4.8. An arbitrary candidate value cannot establish an independent destination contract. Concrete value arguments in a nested callable contract use their declared domain representation before invariant comparison, as in a direct class result. Retain a parameter's declared-default information without evaluating its initializer when obtaining or comparing the callable contract.

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 captured binding initialized. It creates no implicit promise on a replaceable function declaration binding and licenses no runtime check elision. An implementation may complete dependencies eagerly or on demand after their environments are established; a fixed syntactic pass that treats unfinished work as unknown is not an equivalent judgment.

Example 1 (Informative)
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 type error before the Script body begins. Changing the interface's nested result to string removes the incompatibility. Moving f before the return or introducing a stable local alias for it does not change the judgment.

Example 2 (Informative)
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 captured initializer. Contract discovery executes no initializer. Evaluating the final call still throws a ReferenceError because s was never initialized.

1.6.2.22 What a Published Type Is Used For

A published inferred return type is read in three places and not in others, and the division is what keeps inference from becoming a second declaration.

  • The Static Type of a call of the function is its declared return type where one is declared, and its published inferred type otherwise. This is what makes an inference an early error at a use: 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.
  • Subtyping and assignability read the declared type where one is declared and the published one otherwise, so a participating function satisfies an interface, an override, or a function-typed position by what it actually returns. Where there is NEITHER - a participating function whose join is the any type, which publishes nothing - the return is unknown, and a signature whose return is unknown is required nothing in that position (1.10.2.3), exactly as a signature that declares nothing at all is.
  • A return check is inserted as for a declared type (Table 23), so the value leaving the function is of the published type or the function throws.

It is NOT read by the identity of a signature, by the formation of an overload set, by the ranking of Table 30, or by the viability filtering of 1.10.5, all of which read [[Return]] and [[Untyped]] alone. And it never licenses eliding a check (1.7.9.1).

Note

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 catch-all however much is inferred for it — because a distant annotation must not change which overload a call selects.

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 contextual type of number and a published 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.

1.6.2.23 Function Forms

A getter in an object literal participates in return inference on the same terms as a class getter. Its declared or published result is the property's read type, including when the literal's shape is preserved by an unannotated 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> denotes Promise.<T, any>, and a bare Promise denotes 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 published type joins the shape its member belongs to. A setter declares no return type, and a constructor has none to infer.

A constructor nevertheless REFLECTS a signature, whether or not it declares anything, and so does the implicit constructor of a class that writes none — one for each constructor the class declares, since a class may declare more than one (1.11.3). Its parameters are the ones it has, with any for a parameter that carries no annotation, and it has no return entry. Nothing is inferred there: a constructor's parameters are syntactically present, and what a construction yields is fixed by 1.11.2 rather than derived from a body. This is why a constructor differs from an unannotated method, whose signature is absent because giving it one would be inference the program did not ask for.

An async function infers the type its result RESOLVES with and publishes Promise.<T, any>. A contribution whose Static Type is a promise contributes the type it resolves with rather than the promise, as 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 AwaitExpression, and at the value entering implicit assimilation from an async function return (including a concise async arrow), an async-generator return, or a plain async-generator yield, it is a type error when the participating operand's effective callable then, on every possible selected assimilation path, cannot accept the actual resolving and rejecting function values supplied by promise assimilation. The argument proof uses only guaranteed facts about those values, including their callable Object category and their being values rather than references, and the actual two-argument mapping with optional, default and rest parameters. It does not invent a declared callback signature or structural properties. An unresolved contract or viable signature prevents rejection. A non-callable 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 synchronous return or yield does not select assimilation merely because it occurs in a nested body within async code; each body uses its own context. An async yield* does not assimilate its raw iterable as a yielded value. This judgment is applied only to the value entering the named consumer, to the typed yielded-value boundary in 33, and to the specified iterator stages in 28, not to arbitrary Promise APIs or unselected iterator stages. The query executes no getter, method or reference, changes no scheduling or liveness requirement, and infers no arbitrary thenable result or rejection effects.

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 typed over the receiver's arguments. On a Promise.<R, E>:

Table 15: Typed signatures of Promise.prototype methods
MethodSignature
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) denotes the established normal fulfillment contribution of a handler result with type T, using the existing await contribution recursively for known Promise arguments and independently for union alternatives. It is explanatory notation, not a new public type operator. An unknown contribution remains gradual. This judgment executes no handler or thenable and does not infer arbitrary thenable results or rejection effects.

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 proposal infers no parameter types, and N is one.

A generic function's inference runs over its uninstantiated body, so a contribution may mention its type parameters and the published type is an expression over them, substituted at each specialization. A primitive operator block is exempt, its annotation naming what the result carries rather than a boundary its body satisfies (6.5).

A reference return participates: where every contribution is a reference to the same target type, the published type is that reference type and a call of the function is a location (2.8), so an accessor written without an annotation is still a writable target. Where contributions mix a reference with a value, or references to different targets, the references decay to their referents' types and the join proceeds over values, since a location has one layout and a union of a location with a value denotes none.

1.6.2.24 Typed Standard Library Statics

A named STATIC function of the standard library may carry a typed signature. Where a call's callee is a member expression whose base is an unshadowed global naming such a function, the call's Static Type is that signature's result, and a function literal passed as an argument takes its parameter types from the signature as it would from any other position (Table 17).

The following carry one:

Table 16: Typed Standard Library Statics
FunctionSignature
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 property key type
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 undefined, every outcome being reported as a settled result rather than raised. PromiseSettledResult.<R, E> is { status: string, value?: R, reason?: E }, structural in the way 28's records are rather than a nominal, so a program may write the shape.

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 proposal adds carry signatures too, and those are stated with each operation in 1.12 rather than in the table above: that clause is where the reflection API's shape is settled, and stating it twice is how two statements of it would drift. Reflect.isAssignable is typed; the Reflect functions ECMAScript already defines are not, their signatures being the language's question rather than this proposal's. Reflect.typeOf is typed <T>(value: T): Reflect.TypeObject.

That row was WITHDRAWN once, written as Reflect.Type, because a Type Object does not report 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 Type Object is the type itself, reified as a value. The two sit on opposite sides of the reflection relationship, and a node's own members ARE Type Objects, so a walk alternates between them. Reflect.TypeObject is the name for the second, and naming it is what the earlier row lacked rather than anything a Type Object failed to do.

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%-typed, and 1.13.1 is why: a value type "would change what every existing call returns", so 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 Table 34 already gives per family.

T is the element type of the items and K is the type the callback returns. Where either cannot be determined the call has no Static Type, rather than one naming 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 1.15.7 requires.

The two differ in their key. Object.groupBy produces PROPERTY KEYS, so K is constrained to the property key types and a callback returning anything else leaves the call with no Static Type; Map.groupBy accepts any key type, keying with SameValueZero as a Map does. That difference is the reason the two are listed separately rather than as one rule with a parameter.

Note

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 typed receiver: the value named Map is a constructor, and its type is not 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 typed call site infers its callback's parameter and publishes a result an annotation can be checked against. Nothing here changes what any call evaluates to.

The callback's second parameter is the index type. standardlibrary.md writes uint32 for it, which predates the dfn: index type says one type describes every count a container reports or accepts, "an index used to read or write an element" among them, and naming it once is what keeps such counts comparable without a conversion. A callback's index is such a count, so it is uint64 here and the design's 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 Static Type. Where they ARE written it carries { 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 constructors, 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 constructors carry NO Static Type, deliberately. An unparameterized collection has none the call supports - the same reading that gives a library generic its type only where the arguments are written - and 1.15.5 already has the adoption at an annotation do that work, which is a better answer than a type the constructor cannot justify.

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 design and is not here. Its parameter is a union of two iterable forms, its elements may be promises contributing the type they resolve with, and it has two overloads. The first of those is no longer an obstacle - a type variable inside a union parameter is inferred from the arm the argument matches - so what remains is the promise-valued elements and the overloads.

Note

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 published type is enforced at the function's own return, so a body that stops producing what its callers rely on fails at a boundary with a location rather than propagating a wrong value. And the escape is one token: a function that means to promise nothing writes : 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 typed one pays over the region its annotations reach — but the bound is on the region, not on the per-function cost.

1.6.3 The Inferred Result Type of a Function Literal

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 Static Type of the body expression. For a block body it is the join of the contributions below, and a literal with a declared return type contributes that type instead of any of this.

  • Each return within the literal contributes the Static Type of its expression, or undefined where it has none.
  • Where the body can complete without returning, undefined is contributed as well, since falling off the end answers undefined.
  • A return within a NESTED function literal contributes nothing, belonging to that literal.

The join of a set of contributions is the single type where they are all that type, and the union of the distinct ones otherwise. Where any contribution is the any type the result type is any: a union with an unknown member is unknown, and answering the remaining members would state more than the body supports. Where the set of contributions is EMPTY, because no path returns and none can complete, the join is never. Where every contribution is undefined, because no 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 undefined to the join can only widen the result.

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 contribution is valueless. undefined remains the contribution, and so remains a member where a body MIXES a valueless path with a value-carrying one: uint32 | undefined is a type an annotation can write, while a bare undefined result would be an inference of something no annotation says. Inference never produces a type an annotation could not.

Note

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 1.6.2, which shares this join and adds what publication requires.

The rule exists so that the two spellings of one function agree. Without it a.map(_x_ => _x_) carries its element type through and 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 (Table 17) or from its own annotation, and those types are what the body is checked under while this operation reads it.

1.6.4 Numeric Value Provenance

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 Type Record identity, subtyping, Type Object interning, or a JavaScript value's representation.

For contextual numeric adaptation, apply these rules before using an exact Number value to establish that a literal fits a numeric value type:

  1. A numeric literal expression, and the constant expressions and unannotated numeric constant references admitted by 1.6.6 and 1.6.5, retain their specified adaptation eligibility. Parentheses do not change that eligibility.
  2. A read of a binding with an explicit 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.
  3. Check a value without adaptation eligibility using its existing numeric domain and the applicable assignability and conversion rules. Do not authorize an implicit Number-to-value-type conversion solely because its singleton payload would fit. Apply the same distinction at stores, arguments, returns, composite members, generic constraints, and operator operands. In particular, a narrowed literal Type Record alone does not exempt an operand from the numeric-domain judgment of an ordered comparison. Preserve the ordinary Number/BigInt ordering exception and any selected declared operator or applicable conversion. Contextual analysis of literal arms occurs before joining their contributions; joining does not itself authorize conversion.
  4. Retain the exact domain for equality, narrowing, and deciding-test judgments (1.8.5.1). Neither loss of adaptation eligibility nor an explicit annotation widens an established flow fact back to the entire Number domain.

Thus let n: number = 5; let x: uint8 = n; is a type error before evaluation. 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.

1.6.5 Contextual Types

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 captured arguments retain their declaration identities; explicitly written type parameters retain precedence. The original body remains subject to its ordinary pre-evaluation checks even when a decorator may replace the resulting function. This rule adds no contextual candidate where the surrounding position has not established one.

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 element type of its collected homogeneous Array, or the effective tuple position type for the count assigned to that rest after substituting the selected signature's type arguments. Nested tuples and finite rest runs contribute their effective positions, not their written rest-entry indices; required suffixes align with the assigned count, and omitted trailing defaults are not supplied arguments. Static contextual checking and runtime construction use this same positional domain. A reference destination supplies no fresh value-construction context. Where correspondence depends on unavailable spread facts or value-dependent distribution, do not guess a neighbouring destination or evaluate an expression, getter, iterator, default, or body to obtain a context. This rule does not change ordinary argument distribution, arity, or admission checks.

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 element type. This is a context for a possible named contribution, not proof that the evaluated object uses named spreading. Do not execute an iterator lookup or other source effect to obtain it. Ordinary spread evaluation still determines the actual protocol from the evaluated value. A callable iterator uses its ordinary yielded values and gains no pending construction association from the object's properties. An unavailable destination, including a reference destination or a tuple-rest position whose count is undetermined, supplies no guessed context. Earlier source expressions superseded by a later contribution of the same fixed name retain their construction contexts and independent body checks, but only the surviving argument is admitted to that parameter. The same rule applies to earlier data initializers superseded within the fresh spread literal.

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 finite source sequences and explicit arguments determine the correspondence without evaluating values, include elisions as positions and concatenate the potential sequences before ordinary explicit-name pinning and fixed/rest position projection. Parentheses preserve the fresh source. Apply the same declaration identities, captured arguments and substituted tuple positions in checking and runtime construction. These are construction contexts, not proof of an iterator's identity, yielded values, count or successful argument admission, and they do not establish a tuple storage contract for the source Array. Actual iteration retains its ordinary behaviour even when a hook changes that potential correspondence. An unavailable or value-dependent correspondence supplies no guessed neighbouring type. Reference destinations and ordinary stored sources gain no fresh value-construction context.

A for-of binding, including the binding's type within the loop body, consumes the declared static iteration contribution of a structural iterable (1.14.8). A known incompatible element is a type error even in an unused enclosing body. Apply the contribution's uncertainty and union rules; indexed storage alone does not prove what a replacement iterator yields. This judgment invokes no iterator hook and changes no iterator closing or reference-liveness rule.

Adoption preserves the effective Parameter Record, including omission, default and rest information, rather than only its [[Type]]. An unannotated optional parameter with no effective default has body type T | 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 iterator. The same effective contracts govern body checking and the created function value. Reference-rest restrictions are unchanged (2.4).

A fresh function expression, arrow or object method without a written type-parameter list adopts the effective Type Parameter Records of a single contextual generic signature. Use that list in its inferred signature, body scope and created value, preserving the constraints, defaults and captured declaration context of those records. Written declarations retain precedence. NamedEvaluation of a literal in an annotated binding initializer receives the same position context as ordinary evaluation of that initializer. Adoption changes neither evaluation order nor captured-value initialization or reference liveness.

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-parameter list. Keep such a dependency symbolic until its ordinary binding point. When checking a nested literal under an enclosing specialization, exclude that literal's own declarations from substitution by same-named enclosing parameters. Independent closed obligations, including independent fragments of an annotation that also contains an own dependency, retain their required checks and failure phases under 1.2.8 and 1.3.8. These rules apply equally to functions, arrows and methods. They do not select a parameter default, execute an ordinary body, initialize an unavailable capture or change reference liveness. Restore the effective checking scope on every completion.

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 capture, export, reflection and specialization; a shared source node does not determine one adopted list for every activation. An argument belonging to an enclosing or calling declaration does not bind an own parameter of the called declaration merely because their names match. Reuse a pre-binding only as an argument of the declaration being applied, preserving explicit and selected contextual arguments and the ordinary inference/default order. These requirements change no alias-forward-reference, captured-value availability, evaluation-order or reference-liveness rule.

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 17.1 remains separate from choosing an alternative, and nested literals retain their own freshness checks.

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 abrupt completion occurs. Keep independent closed body obligations at their required checking phase, and validate the selected effective defaults, body and result before accepting the program. Selecting an alternative neither executes ordinary code nor changes reference liveness. The selected context governs the created value as well as its Static Type: NamedEvaluation of an anonymous function or arrow in an object property receives the same property context as ordinary evaluation. Preserve its effective generic declarations and captured arguments for subsequent specialization and reflection.

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 1.6.6 so the created literal values have the selected representations; a later union boundary must not change that choice merely because a wider alternative is listed first. Conflicting per-member preferences do not establish a complete preferred alternative. These requirements supply no source-order tie-breaker for competing callable contexts.

An argument expression receives the context of its established destination parameter in the signature in view. A construction's argument receives the corresponding constructor parameter's context. Preserve its positional or named correspondence; do not apply one parameter's context to unrelated arguments or use an unresolved overload or argument distribution as evidence for a particular destination. Resolve the destination contract in its declaration environment with its established captured and explicit arguments. A generic signature nested in that contract retains its own type-parameter declarations; the presence of such declarations does not by itself make the surrounding contextual contract unavailable. If admission depends on an unresolved outer parameter, retain independently determined contextual information and ordinary deferred obligations without treating that admission as proved.

Carry a selected complete object alternative through its immediate receiving admission as specified in 1.7.2. Creating the effective callable is not sufficient if the boundary would then specialize it to a different, overlapping alternative.

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 captured arguments after the creating activation ends, independently of the literal body's lexical environment. Explicit arguments bypass their defaults, and obtaining a callable or its type does not itself select a default. Restore suspended environments and bindings on every completion. Retaining this context does not initialize an unavailable capture or confer reference authority or liveness.

A callable that adopts a generic signature retains the effective input Parameter Records mapped to its own formal positions. Use those records in reflection, compatibility, result inference and implicit generic argument inference, including an invocation through any. Written annotations, including an explicit any, retain precedence; names and reference/rest syntax remain those of the implementation. Preserve effective omission/default information and the collected rest type. An effective typed signature does not acquire the wholly untyped compatibility exemption merely because its input annotations were omitted. Instantiate its own declaration-identified parameters independently of enclosing captures, including before deciding whether a result needs further structural inference; preserve ordinary result variance and required source-result admission. For an implicit invocation, determine required adopted bindings before parameter initialization checks the collected or destructured inputs. Obtaining or comparing these contracts executes no body, changes no function identity, initializes no capture and grants no reference authority or liveness. Restore the suspended type context on abrupt completion.

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 replacement library method does not remove these obligations. Written annotations take precedence; reference binding and reference-rest restrictions remain unchanged. An uncontextualized parameter gains no contract from its caller's method name.

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 captured type context, use it to constrain the callee's output parameter, then apply the ordinary argument and result obligations after substitution. An unresolved output parameter is not already a completed return obligation against which to reject the body. Written callback annotations take precedence; an existing callback contributes its established signature, including an explicitly gradual result. Do not replace unresolved input dependencies with any to manufacture a completed answer. This judgment executes no callback and does not change reference binding or liveness.

The contextual type of an expression is the type its position requires, or none where its position requires nothing. It is determined by the position rather than by the expression, and it is what a literal consults.

Parentheses preserve the contextual type of their enclosed expression. A comma expression passes its contextual type to its final operand only; earlier operands are evaluated for their effects and receive no contextual type from the comma expression's consumer. Discarding an operand's result does not discard its applicable static operator or call obligations. Judge those obligations with the facts at that operand's source position, preserving evaluation order, abrupt completions and effect invalidation. This transfer does not make a stored numeric value eligible for literal adaptation (1.6.4).

Table 17: Contextual Types by Position
PositionContextual type
The Initializer of a binding with a TypeAnnotationThe annotated type.
An argument of a call whose callee has a single applicable signatureThe declared type of the corresponding parameter.
An argument whose corresponding parameter's declared type is a generic parameterThat parameter's evaluated constraint (1.4.3.4), or none where it has none.
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 contextual type has already bound (4.9)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 field initializer in a classThe field's declared type, where it has one; none otherwise.
A property of an object literal at a position of known object typeThe type the object type declares for that property, where it declares one; none otherwise.
The operand of a return in a function with a return annotationThe annotated return type.
this within the body of a non-arrow function literal whose contextual type is a function typeThe [[ThisType]] of that type's applicable signature, where it is not none (8.1).
The right operand of an assignment to a target of a known typeThe type of the target.
An operand of a binary operator whose other operand has a known value typeThe type of the other operand.
An arithmetic expression every operand of which is compile-time evaluable (1.2.7.1) and integer-valuedThe contextual type of the expression, applied to the expression's mathematical value: the expression is folded to that value exactly, as a literal is read exactly, and the value takes the type as a literal does. So 9007199254740992 + 9007199254740993 at a uint64 is 18014398509481985, and 200 + 100 at a uint8 is a type error, the value 300 not being a value of the type. The folding precedes the typing: the expression's value is what takes the type, not each operand's, so the typed arithmetic's wrapping never applies to it and 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 typeThe element type of that position.
The operand of an explicit conversion to Tnone. A conversion converts a value, so its operand is typed on its own.
The iterable of a for-of head whose binding has a TypeAnnotation, where that iterable is a range literal or an array literal, possibly parenthesizedFor a range literal, the range type whose element type is the annotated type; for an array literal, the array type whose element type is the annotated type.
Any other positionnone.

A contextual signature retains the identities of the declarations that supply its bound parameters, their applicable captured arguments, and the source contexts of its defaults and constraints. Canonical type identity may equate signatures that differ only by bound-parameter renaming; it must not replace the contextual signature at a source occurrence with the binder names or captured context of a previously interned representative. The public Type Object and its equivalence rules are unchanged.

A successful type computation can retain a type-result context: the resolved Type Record with its contextual signature's declaration identities and captured inputs, separately from the Type Object that is its language value. Evaluating a type expression supplies its resolved context before interning. A successful ordinary read of an initialized alias, including through its actual import or namespace binding, can supply the alias's published context after the required read and reference checks. This context belongs to that evaluation result, not to the shared Type Object, function object or source syntax. Where a computed type supplies a contextual signature, use this retained context if present; an alias initialized from that result retains it under the alias-publication rules below.

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 constructor that returns a Type Object instead of its allocated instance retains that returned result's context. A simple, destructuring or logical assignment expression retains the context of the value it actually produces, after any required store succeeds; this does not attach context to the stored slot. A skipped logical-assignment right operand supplies none. A getter that is actually invoked during an otherwise permitted type computation may produce such a result; do not invoke a getter merely to recover context. Apply ordinary return admission and contracts first. A failure, replaced value or overridden completion does not forward the abandoned result's context; a successful overriding return supplies its own. Unselected or discarded evaluations supply no context to the selected result.

Storing the language value in an ordinary variable, data property, array element, argument/parameter or caught-exception binding does not retain this alias provenance. A later read of that stored value does not recover context by Type Object equality. A helper that returns a selected alias or another call's result is distinct from a helper that returns an ordinary parameter containing an equal Type Object. These requirements add no public wrapper or observable property, perform no extra value read or evaluation, and neither alter the evaluable fragment and resource accounting nor establish reference permissions or liveness. Without a retained type-result context, the ordinary value and type rules apply.

For a class-field initializer, retain the context established when its annotation is resolved in the applicable class and generic environment. Use that context when constructing the initializer's function-shaped expressions and nested contextual contributions. Do not re-evaluate the annotation per instance to recover it. Preserve once-only initializer evaluation, inferred function names, ordinary class-element ordering, and the existing admission and publication boundary. Retaining a declaration context grants no additional reference permission and does not relax reference-liveness requirements.

An initialized type-alias binding retains the resolved declaration context it supplies separately from any interned Type Object representative. Alias chains and imports follow their actual binding and export resolution, preserving that context. An unrelated value binding containing an equal Type Object does not thereby acquire the alias declaration's provenance. Publish no context from a failed or incomplete alias initialization; preserve ordinary binding reads, temporal-dead-zone behaviour and module evaluation failures. These requirements neither execute a getter to discover provenance nor move a default, constraint or initializer to an earlier evaluation phase.

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 path through imports or immutable aliases, follow its resolved exports to the actual alias declaration, including renaming and namespace re-exports, and retain that declaration's source context. Do not execute a property read, getter or module body to discover this provenance. A nearer lexical binding selects its own meaning; an arbitrary object or Proxy does not acquire export provenance from its shape, property names or an equal Type Object.

Evaluation of such a qualified reference still performs its ordinary reads in order. Only after a successful namespace read may the resolved exporting binding supply its published alias context; missing, ambiguous or uninitialized inputs retain their existing resolution and failure rules. Retaining context performs no additional read or alias evaluation and supplies no initialization or reference-liveness permission. The available source declaration used for checking is distinct from evidence of successful runtime alias initialization.

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 path without a property read. A nearer binding, including an uninitialized or non-alias binding, blocks fallback to an outer alias; an opaque object environment supplies no such evidence. Indirect 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 Type Object identity.

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 iterable. An iterable that is a call, a binding, or any other expression keeps its own Static Type, and the annotation constrains the elements it produces as for any iterable; the row is what lets 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 21.6.

Note

An argument of an overloaded call has no single contextual type until an overload is chosen, and the choice depends on the argument. The literal ranking of 1.6.9 is what resolves that circularity for a literal argument. 1.10.3 specifies the rest of overload resolution.

A contextual type does not reach INTO a call. Where a function neither declares nor publishes a return type, a caller's contextual type says nothing about the values that function's body constructs, so a numeric literal deep in such a body is a Number and the value crosses the caller's boundary by conversion, losing what a wider type would have held exactly. Carrying a contextual numeric type through the returns of a non-participating function — so that the literal is built at the type its result is going to, as a literal written at that position would be — is a refinement of 1.6.6 that this specification does not yet make. It is recorded here because the case it serves is real: 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 contextual type is a function type takes that type's signature for every position it leaves unannotated: a parameter's type, the return type, the this type, and the type-parameter list. A function that declares any of these keeps what it declares. The signature so taken is the Static Type of the expression, and it is the type of the function object the expression creates, so a literal { 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-typed binding is a specialization of it (1.4.6). Without the last part the boundary check (1.7.7) would keep its promise for every value but this one: the program would pass the check with a generic Static Type and be refused at run time on the first specialization, because the value was not what its type said.

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 type error independently of whether a particular call supplies the argument.

1.6.6 Literal Propagation

A numeric literal has no type of its own. In a position whose contextual type is a numeric value type, the literal takes that type, and a literal whose value that type cannot represent is a type error rather than a silent truncation. This refines the Static Type given for a numeric literal in Table 2, which is the type of a literal whose contextual type is none.

A contextual type that is a literal Type Record whose [[Base]] is a numeric value type admits a numeric literal on the same terms: the literal is converted at the base, and the result is then a member of the literal type exactly when it is that type's one value. Without this such a type is nameable and not fillable. 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 1.8.3.1 decides for 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 (6.5) — so 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 1.8.3.1 decides for 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 (6.5) — so uint32<{ bounds: 5..=5 }> refuses 5 until such an operator is written, and admits it once one is. 1.5.19 gives it a [[Value]] that is a value OF the base — "the uint8 3 as a type" — while 1.7.6.1 decides a literal by SameValue against that value, and no Number is SameValue with a 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. 1.5.19 fixes that base — "the base of a numeric literal is 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 1.2.2.8 sorts its members, so such a rule would depend on an order the grammar does not give it, and would mean something different for a member reached through a type parameter than for one written directly.

1.6.6.1 LiteralValueInType ( literal, t )

The abstract operation LiteralValueInType takes arguments literal (a Parse Node) and t (a Type Record) and returns either a value of the type t or unrepresentable. It returns the value of the type t that literal denotes, or unrepresentable if t cannot represent it. It performs the following steps when called:

  1. Let mv be the mathematical value denoted by literal, as defined by the numeric literal grammar, before any rounding.
  2. If t is an integer type, then
    1. If mv is not an integer, return unrepresentable.
    2. If t has a value representing mv (1.5.2), return that value.
    3. Return unrepresentable.
  3. If t is a binary floating-point type or the Number type, then
    1. Let v be the result of rounding mv to the nearest value of t, with ties to even.
    2. If v is an infinity and mv is finite, return unrepresentable.
    3. Return v.
  4. If t is a decimal floating-point type, then
    1. Let q be the number of digits literal writes after its decimal point, negated, adjusted by any exponent literal writes.
    2. Let c be the integer such that mv = c × 10**_q_.
    3. If c is outside the coefficient range of t, return unrepresentable.
    4. If q is outside the exponent range of t, return unrepresentable.
    5. Return the value of t with coefficient c and exponent q.
  5. If t is bigint, then
    1. If mv is not an integer, return unrepresentable.
    2. Return the BigInt value whose mathematical value is mv.
  6. If t is a rational type, then
    1. Let n be the numerator of mv expressed in lowest terms with a positive denominator.
    2. Let d be the denominator of mv expressed in lowest terms with a positive denominator.
    3. Let ct be the integer type of the components of t.
    4. If n is a value of ct and d is a value of ct, return the value of t with numerator n and denominator d.
    5. Return unrepresentable.
  7. If t is a complex type, then
    1. Let ct be the type of the components of t.
    2. Let r be LiteralValueInType(literal, ct).
    3. If r is unrepresentable, return unrepresentable.
    4. Return the value of t whose real component is r and whose imaginary component is the zero of ct.
  8. Return unrepresentable.
Note 1

"The mathematical value denoted by literal ... before any rounding" is a requirement on the IMPLEMENTATION as much as on this operation, and it is easy to violate without noticing. A lexer that produces a Number for every numeric literal has already rounded by the time any contextual type is known: 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.

Note 2

bigint takes a plain integer literal like any other numeric type, so 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.

Note 3

The mathematical value of a literal is exact. 0.1 denotes one tenth, not the Number nearest to one tenth, so 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 mathematical value and rounding it only when producing a Number.

A decimal type reads its cohort member from the source text rather than from the mathematical value, since 1.0 and 1.00 have the same mathematical value and are different values of the type (1.5.4). A literal therefore carries its significance into the type, which is what makes a price written to two places a value with two places.

Representability is exact for an integer, a decimal, and a rational, and is range membership for a float, which rounds. A float is the one family where a literal that is not representable exactly is still accepted, because requiring exactness would reject 0.1 in a float32 position and no useful program could be written.

The Static Type of a numeric literal whose contextual type is a numeric value type t is t. It is a type error if LiteralValueInType(literal, t) is unrepresentable.

Note 1

The error is an Early Error (1.2.8), so 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.

Note 2

One case in the design does not follow this rule. A store to an element of a typed array, as in 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 1.14.10 decides for the literal rule: the store of an out-of-range literal is rejected like every other typed position, and the wrap of Table 22 continues to govern a stored value that is not a literal.

1.6.7 Explicit Conversion

An established Type Object conversion target is preserved through parentheses and immutable lexical value aliases, including alias chains. Check the aliased call by the same explicit-conversion availability and exact-literal rules as a direct call. Resolve each initializer in its lexical scope; shadowing, explicit any erasure, mutable or unknown bindings do not establish this origin. Terminate cyclic origin queries without executing an initializer or type-building function. These facts do not alter initialization order, the temporal dead zone, identity or reference liveness.

When establishing such an immutable value's Type Object origin, judge the source lookup at the position where that value is obtained. Account for effects that can precede the lookup, including implicit conversion and checking at earlier annotated boundaries and earlier bindings in the same declaration list. A later consumer of the captured value is not a prerequisite for establishing that earlier origin, and a later replacement of its original name does not change the captured value. If the capture can be evaluated again, effects preceding that later evaluation still require their ordinary proof. An import's prerequisites, a reachable helper's effects, and unknown lookup or conversion effects cannot be omitted merely because their declarations are written later.

This distinction also applies when an annotation or another Type Object capability judgment consumes the established origin. A cyclic query through an initializer or its own binding annotation does not establish a value by assuming the queried origin. An independently established earlier origin must not be discarded solely because a later annotation consumes it. Complete the required source judgments without executing user code or treating implementation recursion as source-level resource exhaustion. An immutable reference alias remains a location alias, not an immutable copy of its current value; origin evidence supplies no initialization or reference-liveness permission.

An explicit conversion is written as a call on the type, uint8(v), or with the conversion operator after the value, v := uint8 (1.4.5). The two are the same operation. A literal operand, whether a numeric literal or an expression built only of literals, is converted from its exact value, the mathematical value LiteralValueInType reads before any rounding; a value that is not a literal converts from what it holds. So 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. ToNumber of one throws a TypeError, so no such value reaches a Number through an implicit conversion: in a 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 Number type is explicit, spelled _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 ToNumber for every other value. It follows the rows of Table 19 to the Number type: a 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.

1.6.7.1 ConvertValue ( value, from, to )

The abstract operation ConvertValue takes arguments value (an ECMAScript language value), from (a Type Record), and to (a Type Record) and returns either a normal completion containing an ECMAScript language value or a throw completion. It converts value, of the type from, to the type to. It performs the following steps when called:

  1. If to is a numeric metadata parameterization, or value carries a numeric metadata parameterization whose [[Base]] is to, return ? ConvertNumericParameterization(value, to).
  2. If SameType(from, to) is true, return value.
  3. If from.[[Kind]] is any, return ? RequireType(value, to).
  4. If to.[[Kind]] is any, return value.
  5. If from is a numeric type and to is a numeric type, return ? NumericConvert(value, from, to).
  6. If to is string or boolean, return ? PrimitiveConvert(value, to).
  7. Let conversion be the user-defined conversion from from to to, if one is declared (1.6.8).
  8. If conversion is not empty, return ? the result of applying conversion to value.
  9. If to is an object Type Record, or is a nominal Type Record whose [[Structure]] is one, and value is an Object that is not an Array, then
    1. Let shape be to if to.[[Kind]] is object, and to.[[Structure]] otherwise.
    2. For each element prop of shape.[[Properties]], do
      1. Let key be prop's key.
      2. If ? HasProperty(value, key) is false, then
        1. If prop is optional, continue.
        2. Throw a TypeError exception.
      3. Let current be ? Get(value, key).
      4. Let converted be ? ConvertValue(current, RuntimeTypeOf(current), prop's type). The source type is read from the value because a member's static type is not carried here.
      5. If converted is not current, then
        1. If ? Set(value, key, converted, false) is false, throw a TypeError exception.
    3. An own String-keyed property of value that shape.[[Properties]] does not declare, but whose key is of the key type of some element of shape.[[IndexSignatures]], crosses by the same steps against that element's value type.
    4. Return value.
  10. If IsAssignable(from, to) is true, return value.
  11. Throw a TypeError exception.
Note 1

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 (Table 23), and an object boundary that only TESTED membership would leave 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 false because the question is whether the write SUCCEEDED, and the property may be inherited.

A COMPOSITE is frozen from its creation (25.1), so this step refuses one whose member needs converting: 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.

Note 2

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 type error before evaluation (1.2.8), so the exception is thrown only where a type reaches the conversion dynamically, as when a Type Object computed at run time is called as a function.

1.6.7.2 ConvertNumericParameterization ( value, to )

The abstract operation ConvertNumericParameterization takes arguments value (an ECMAScript language value) and to (a Type Record) and returns either a normal completion containing an ECMAScript language value or a throw completion. It performs explicit construction or removal of numeric metadata. A numeric metadata parameterization is a parameterized Type Record whose ultimate base is a scalar numeric type. It performs the following steps when called:

  1. Let from be RuntimeTypeOf(value).
  2. If from is a numeric metadata parameterization, then
    1. If SameType(from, to) is true, return value.
    2. If to is a numeric metadata parameterization and SameType(from.[[Base]], to.[[Base]]) is true, return ? ConvertParameterization(value, from, to).
    3. If SameType(from.[[Base]], to) is true, return the numeric value with value's representation carrying to.
  3. Assert: to is a numeric metadata parameterization.
  4. Let baseValue be ? ConvertValue(value, from, to.[[Base]]).
  5. Apply each applicable defined metadata validation judgment of to once to baseValue. If a judgment rejects, throw a TypeError exception; propagate an abrupt completion. An absent judgment imposes no construction check.
  6. Return the numeric value with baseValue's representation carrying to.

Determine the ultimate base through the finite chain of base types; an implementation helper limit must not classify a deeper numeric parameterization as a mutable object construction. The existing type-evaluation resource limits remain applicable. The defined judgments include those of governing metadata portions and a validation hook declared on the base. Admission precedes retention of the target record and is not repeated merely to retain it. A written default portion remains a distinct construction target. Source metadata is read from the value even when its source expression has Static Type any. A different parameterization of the same base cannot bypass ConvertParameterization through a successful value-membership test; its result carries the metadata that operation determines, including transported unpinned portions.

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 integer or rational components, and complex components. They do not mutate the source value or other bindings holding it. An implementation using an Object internally for a numeric value must preserve these value semantics. The same requirement applies when constructing an intersection of numeric parameterizations over one base. This does not require an observable allocation.

Both explicit spellings use this operation. Literal operands keep their exact contextual evaluation. IsOfType and ordinary value admission do not become construction or acquire permission to retag a source. Implicit admission to the bare base continues to preserve metadata. Failure publishes no constructed value and does not initialize a failed binding, while prior source effects remain. None of these steps converts a reference location or changes evaluation order, the temporal dead zone or reference liveness.

1.6.7.3 NumericConvert ( value, from, to )

The abstract operation NumericConvert takes arguments value (a value of a numeric type), from (a numeric Type Record), and to (a numeric Type Record) and returns either a normal completion containing a value of the type to or a throw completion. It converts value between two numeric types according to Table 19. It performs the following steps when called:

  1. If Table 19 specifies a RangeError for value converted from from to to, throw a RangeError exception.
  2. Let result be the value given by Table 19 for the families of from and to.
  3. Assert: result is a value of the type to.
  4. Return result.

1.6.7.4 PrimitiveConvert ( value, to )

The abstract operation PrimitiveConvert takes arguments value (an ECMAScript language value) and to (a Type Record for string or for boolean) and returns either a normal completion containing a value of the type to or a throw completion. It converts value to one of the ordinary primitive types, where a conversion for its source is defined. It performs the following steps when called:

  1. Assert: to is string or to is boolean.
  2. If to is boolean, return ToBoolean(value).
  3. If Table 18 gives no conversion for value, throw a TypeError exception.
  4. Return ? ToString(value).
Note

The boolean target is reachable only from 1.6.7.1, which is the EXPLICIT conversion: v := boolean asks for a truth value and ToBoolean is total, so it cannot fail. 1.7.7.1, the boundary an untyped value crosses, passes only string, so let b: boolean = v for a v of the any type is a TypeError rather than a truthiness test — a boundary reports what a value IS, and every value would pass a truthiness test.

The signature and the assertion named string alone while 1.6.7.1 passed either, so the operation refused an argument its own caller was written to supply.

Table 18: Conversion Sources for the String Type
SourceConversion
A StringThe value.
A value of a numeric type, including bigintToString of the value.
A BooleanToString of the value.
undefined, null, an object, or a SymbolNone. A TypeError.
Note 1

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 denotes them, ToString of them is total, and nothing is lost: 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: ToString of a number is total and lossless, while ToNumber of a string is partial and lossy, so the direction that cannot fail is admitted and the direction that can is written as a parse (1.13.5).

undefined, null, an object, and a Symbol are refused because their text is a diagnostic rather than a denotation. The values they would produce are the language's best-known silent failures: a 'undefined' reaching an interface that was meant to show a name, a '[object Object]' where a field was meant, and an array quietly becoming its comma-joined elements. A program that wants those writes String(_v_) and has then said so. A Symbol is refused for the reason it always was, which is that ToString of one throws.

The boolean type is not among the targets above: at a boolean boundary a value that is not a Boolean is refused, and a program that means the truthiness writes Boolean(_v_). An earlier form of this specification converted here, on the ground that ToBoolean is total, every value has a defined truthiness, and 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 1.7.7.1: coercing first and then checking "would make an annotation a coercion rather than a check", and a total conversion "could not fail at all, so a missing field would become a NaN that surfaces far from the annotation that admitted it". ToBoolean cannot fail at all, and a missing field does become false — absence laundered into a legitimate-looking answer at the very position that was annotated to catch it. 'false' becoming true is the same failure with a different source. Totality is what makes a conversion unsuitable here, not what recommends it.

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 field that was false and one that was never there. Those are the distinctions the rest of this clause is built to preserve, and the boolean type is not an exception to them.

The table above does not reach an identity-bearing position — a keyed collection's key or element (1.15.4). The reasoning of this note carries the qualification with it: "nothing is lost" is a claim about a value, and a key is a value that is also an identity, which a total conversion can destroy by mapping two distinct sources onto one. The numeric targets never could, admitting only what their type represents exactly; the string target is the one that could, and at those positions it does not.

Table 19: Numeric Conversions
FromToResult
integerinteger of width MThe mathematical value of the source modulo 2**_M_, interpreted as a value of the target. Signed targets wrap in two's complement.
integerbinary floatThe source rounded to the nearest value of the target, with ties to even.
binary floatinteger of width MIf the source is NaN or an infinity, 0. Otherwise the source truncated toward zero, modulo 2**_M_, interpreted as a value of the target.
binary floatbinary floatThe source rounded to the nearest value of the target, with ties to even. A finite source outside the target's range becomes an infinity of the same sign.
the Number typefloat64, and the reverseThe source unchanged. The two types have the same values (1.5.3), so the conversion is exact and total.
rationalbinary float or the Number typeThe exact quotient rounded to the nearest value of the target, with ties to even.
integerrationalThe source's value over 1. A RangeError if it is not representable.
rationalrational of width MThe source's exact value. A RangeError if it is not representable.
bigintrationalThe source's value over 1. A RangeError if it is not representable.
binary float or the Number typerationalThe source's exact value, which is a dyadic rational, in lowest terms. A RangeError if its numerator or denominator is not representable, or if the source is NaN or an infinity.
any numeric typedecimalThe 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 claims no significance the source did not carry, and one that rounded holds every digit the source did. A RangeError if the source's exponent is outside the target's range, since a decimal's range is a property of the type rather than of the format.
decimalbinary float or the Number typeThe source rounded to the nearest value of the target, with ties to even, which reintroduces the binary error a decimal exists to avoid. A finite source outside the target's range becomes an infinity of the same sign.
decimalinteger of width MThe source truncated toward zero, then modulo 2**_M_, interpreted as a value of the target, as the integer rows above give. The truncation reads the source's digits, so a decimal carrying more significant digits than a double holds does not lose them on the way.
decimalrationalThe 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.
decimalbigintThe 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.
integerbigintThe mathematical value of the source, unchanged. bigint has no width, so there is nothing to wrap to.
binary float or the Number typebigintThe source truncated toward zero, as the binary float to integer row gives without the modulo, since bigint has no width. A RangeError if the source is NaN or an infinity.
rationalbigintThe exact quotient truncated toward zero.
rationalinteger of width MThe exact quotient truncated toward zero, then modulo 2**_M_, interpreted as a value of the target, as the integer rows above give.
bigintinteger of width MAs the integer to integer row: the mathematical value of the source modulo 2**_M_, interpreted as a value of the target. Signed targets wrap in two's complement.
bigintbinary float or the Number typeThe 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 vectorany other family, and the reverseNot defined by this clause. The complex numbers and SIMD extensions define the conversions of their types.

A literal is judged against its contextual type by the rule a boundary applies to its value, whatever its numeric type: a Number literal 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.

Note 2

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 claims that many digits are meaningful. Where it does not, the target's full precision results: 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 claims the source supports.

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.

Integer wrapping is the same operation the language already performs when storing to a Uint8Array, and the float to integer rule is the shape of the existing ToInt32 and ToUint8 operations: truncate, then reduce modulo the width, with a non-finite source becoming zero. Reusing that shape means a typed array store and an explicit cast agree.

bigint takes that rule's truncation but not its zero. A fixed-width conversion is total, and gives a non-finite source a value because a reduction modulo the width must send every input somewhere. bigint has no width to reduce modulo and no maximum to saturate an infinity to, so a non-finite source has no integer and is refused, as it is when converting to 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 integer exists to keep. BigInt is unaffected, and continues to refuse a fraction as well.

1.6.8 User-Defined Conversions

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.

Table 20: Declared Conversions
FormDeclared onConverts
A constructor taking one parameter of type SThe target class TS to T. A converting constructor, so let t: MyType = 1; is legal when MyType's constructor takes a float32. Where a class declares several one-parameter constructors (1.11.3), each is a converting constructor and the conversion resolves by the argument's type.
operator T(), a parameterless memberThe source class SS to T, by converting the receiver.
operator T(value: S), a member taking one parameterThe target TS to T, running with no receiver. This is the form a type declares when its constructor is already spoken for, as Temporal.Instant's is by epoch nanoseconds.

The one-parameter inbound form is called with undefined as its receiver. Check its parameter initializers and body 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 captured type and value parameters still resolve in their declaration context. The parameterless source form retains the source receiver. A conversion's result remains subject to its declared result contract.

1.6.8.1 Declared Conversion Result Admission

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, captured type and value parameter bindings, Realm, and Script or Module. In the absence of a written return annotation, T is the return expression's contextual type. A written return annotation retains its contextual and checking behaviour and imposes an additional contract; it does not replace T.

After the body completes its return expression evaluation and applicable finally evaluation, and before leaving the declaration's execution context, admit its result as follows. A normal fallthrough supplies undefined. Existing static return requirements still apply at their required phase.

  1. Let value be the returned value, or undefined for normal fallthrough.
  2. If there is a written return annotation, apply its ordinary return admission to value, propagating an abrupt completion and retaining its admitted value.
  3. Let target be the Type Record of T resolved in the declaration context.
  4. If target.[[Kind]] is nominal and value is not a member of target, throw a TypeError exception.
  5. Return ? RequireType(value, target).

The nominal membership precondition prevents a nonmember returned by the selected operation from initiating another constructor or inbound conversion. Other targets retain their ordinary checked conversions, including numeric representation, metadata transport, and structural storage admission. Preserve the existing value-copy and result-obligation rules. Primitive operator blocks retain their separate raw-result protocol.

An explicit receiving conversion does not relax this declared return boundary: a dynamic out-of-range result for uint8 throws a RangeError exception even when the caller uses :=. 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 abrupt completion or a result-admission failure without retrying another outer declaration or form. Checked admission of a non-nominal result may itself invoke a conversion, which is an independent nested boundary, not speculative search of outer candidates. Preserve earlier effects, including 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 specialization, resolve the conversion target and signature in the specialized declaration context and associate the callable with that specialization. A bare self target denotes the class at its own arguments. An ambient generic frame, including a same-spelled enclosing parameter, does not by itself apply a nested class. Declaration and body checking still occur at their required checking phases.

A converting constructor and an inbound conversion belong to the concrete target specialization. If a boundary reaches a closed generic target whose class has not yet been materialized, resolve that specialization before consulting its target-side conversions. The unspecialized constructor retained by an annotation is not an alternative lookup location. Reuse the existing materialization for the same application; conversion lookup does not repeat its static initialization. Preserve the existing initialization order and propagate a materialization failure without retrying another conversion form.

Materializing the target for conversion uses the actual declaration's retained lexical environment, enclosing type and value parameter bindings, private environment, Realm, and Script or Module. The supplied arguments bind that class's own parameters; a caller's same-spelled bindings do not replace declaration captures. Restore the caller's execution context after either normal or abrupt completion. These rules introduce no speculative conversion calls and change no reference-liveness requirement.

When both a converting constructor and a declared conversion could apply to the same pair, the constructor is preferred. It is a type error if two conversions of the same form apply and neither is more specific.

For the bounded ordinary identity-class targets and simple parameter signatures covered by 1.14.3 and 1.14.4, use the same declaration-context applicability proof, exact target matching and selected-operation rules at a non-rest receiving boundary. Existing membership precedes conversion. Otherwise prefer an applicable constructor, then a source operator, then an inbound operator. A display name or the last declaration of a form cannot establish selection. A proved inapplicable parameter may leave a later form available; unavailable evidence is not such a proof.

A class Type Record's [[Structure]] supplies type-relation information, not a structural conversion recipe. If the original value is not a member of the class, consult its applicable declared conversions without first reading or converting the source's properties according to that structure. A shape-compatible nonmember with no applicable conversion remains a nonmember; do not modify its properties merely to discover the nominal failure. Structural object and interface conversion retain their existing field-admission rules.

At a non-rest receiving boundary, the same bounded selection proof also applies when the resolved, non-generic source class target is a value type class with the ordinary permanent prototype identity required above. Preserve membership-first behaviour and the constructor, source, and inbound order. The selected operator's result follows 1.6.8.1, including value copying; a selected constructor retains its construction and copy-elision rules. Shared selection does not add a second copy of a freshly constructed result. Verify selected nominal membership and preserve its independently completed result obligations. This extension of the target domain does not make value-class parameter admission a pure applicability proof, or add value classes to the scalar-rest storage domain.

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 TypeError exception before a conversion body runs. Retain a pure parameter conversion failure while considering viable candidates; if none remains, propagate the retained failure, or throw a TypeError exception when there is no such failure. Do not execute user conversions to discover applicability.

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 abrupt completion without retrying another declaration or form. Verify that a normal result is a member of the selected ordinary class and complete its independent exact prototype obligation before publishing it. Preserve earlier effects and independently completed parameter/result obligations after a later failure; neither selection nor result validation grants reference authority or extends reference lifetime. These rules do not make the bounded proof sufficient for the general parameter forms excluded by the referenced clauses.

Note 1

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 contextual type - const [_x_, _y_, _z_] = _a_ performs GetIterator(a) - so a declared tuple conversion is consulted when the applicable iterator method is absent. Where the value of an ArrayBindingPattern or an ArrayAssignmentPattern has no applicable iterator method and its class declares a conversion to a tuple type, the conversion is applied and its result is destructured in place of the value.

For this fallback, obtain the applicable iterator method once and retain the value obtained. If a method is present, use that captured method to acquire the iterator. A throwing lookup, a non-callable method or a failure while calling a present method does not select the tuple conversion and does not cause another lookup. Consult the declared tuple conversion only when the iterator method is absent under the ordinary method-lookup rules; acquire the iterator of the converted result normally. Composite tuples retain their kind-based iteration rule. Binding and assignment patterns obey the same acquisition rule.

Iteration is attempted FIRST, so a class that both declares [Symbol.iterator] and a tuple conversion is destructured by its iterator. A declared conversion says how the value converts, not that the value is a sequence: it does not make _a_ spreadable, nor usable in a for-of head, both of which continue to require an iterator.

Note 2

An earlier draft of the note above attributed this to the destructuring position carrying a tuple contextual type. It does not carry one - a destructuring pattern cannot be annotated at all, const [_x_]: [number] = _a_ being a Syntax Error - and reading the rule that way leads an implementation to look for a contextual type that is never there.

1.6.9 Literal Overload Ranking

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 Table 21, and the first that can represent the literal is chosen. This ranks literals. An argument that is a typed value is not ranked; it selects the signature its type is assignable to.

Table 21: Literal Ranking of Numeric Types
RankTypes, in order
1float64
2float128, float32, float16
3decimal128, decimal64, decimal32
4uint128, uint64, uint32, uint16, uint8
5int128, int64, int32, int16, int8
Note

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 typed.

The ranking as the design gives it does not include number, and it should. number is the type a numeric literal takes with no contextual type, the type an untyped program computes with, and the type the design's own justification for ranking 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 assumed because it changes which function a call selects.

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. 1.10.6 says where they fall.

1.6.10 The Existing Abstract Conversion Operations

It is a type error for a reached implicit ToPrimitive operation on a participating operand to have an effective declared Symbol.toPrimitive contract for which every possible selected path is known to fail. Participation is the operand-contract judgment of 1.9.7. A present non-nullish hook known to be non-callable fails. A callable contract fails if its actual hint argument cannot satisfy any applicable signature, or every applicable result is known to be an Object. Use the consumer's actual hint, the well-known Symbol identity, and inherited and specialized member contracts. A nullish hook selects OrdinaryToPrimitive; a missing declaration on an open object, an open callable possibility, an unknown result or a viable alternative supplies no negative proof. The Object judgment concerns runtime values rather than Type Record kinds: Type Objects and composite objects are Objects.

When the effective exotic hook is proved nullish, the negative primitive-conversion judgment may follow OrdinaryToPrimitive's two effective member contracts in hint order: toString, then valueOf for string, and valueOf, then toString for number or ordinary default fallback. A known non-callable candidate is skipped, not rejected as a GetMethod failure. A callable Object-only result continues to the next candidate; a possible primitive or unknown result prevents an exhaustion proof. A separately proved argument-boundary failure terminates its path rather than selecting the next candidate. It is a type error when every selected path either fails such a boundary or exhausts the candidates without a primitive. An undeclared member on an open type does not prove absence of that member or selection of ordinary fallback. No candidate, getter or reference is evaluated by this judgment.

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 type error when all selected paths either fail conversion or produce primitives inadmissible to that consumer: Symbol cannot feed implicit string, numeric or ordered conversion, and BigInt cannot feed unary plus. Numeric categories so established are also subject to the selected binary operation's exclusions. Unknown results and viable alternatives, including unresolved fallback, prevent the negative proof. This judgment does not change the original expression's Static Type or narrow its binding to the conversion result. Symbol property keys, BigInt negation and text conversion, skipped optional access, Boolean conversion, raw tagged substitutions and applicable declared operators retain their existing behaviour. Mutable lookup, runtime checks, side effects and reference liveness remain required for admitted executions.

This judgment applies to computed property keys and implicit conversions actually selected by the operation. A Symbol result remains a valid property key. Applicable declared operators precede built-in coercion; tagged raw substitutions, ToBoolean, strict equality and a short-circuited optional key access do not gain a ToPrimitive step. A call is not identified as an intrinsic conversion from its identifier's spelling alone. Checking evaluates no getter, conversion method or reference, infers no arbitrary method body's effects, and changes no reference-decay or liveness requirement. Unknown paths, ordinary JavaScript and any retain their runtime conversion and exception timing.

ImportCall is an implicit string-conversion consumer: its specifier uses the string hint. It is a type error when a participating specifier's selected conversion paths all fail or produce primitives inadmissible to ToString, including Symbol. Number, null, BigInt and successful object conversions remain valid specifiers; this is not a requirement that the input already have String type. Unknown conversions, viable alternatives, ordinary JavaScript and any retain the runtime promise-rejection behaviour. Checking invokes no conversion hook or module loader, evaluates no reference, and changes no liveness rule. Import options, attributes, namespace typing and host resolution retain their separate semantics.

For a participating ImportCall options expression, it is also a type error when every selected alternative is proved to fail the options stages. The options value must be an Object or undefined. For an Object option, the effective with member must likewise be an Object or undefined; null satisfies neither stage. Combine these failures within each alternative before judging all alternatives. Use effective inherited and specialized contracts and getter-declared result types without executing any getter. Optional members, open views, unresolved contracts and viable alternatives prevent a closed proof. Ordinary JavaScript and any retain runtime promise-rejection timing. This judgment makes no claim about arbitrary import-attribute values from their declared property types: attribute enumeration selects enumerable own keys, which a structural type alone does not establish. Attribute support and host resolution remain separate judgments; no loader or reference is evaluated during checking.

The abstract operations of 7.1, including ToNumber, ToString, ToPrimitive, and ToBoolean, continue to apply to the values of the existing language types unchanged. A value of a type added by this proposal is not an Object, so ToPrimitive returns it unchanged, and ToBoolean of a numeric value is false exactly when the value is a zero or a NaN of its type.

Note

ToNumber is where the conversion rule and the existing semantics meet, and the meeting needs care. ToNumber of a uint8 would produce a Number, which is a conversion between two value types that the rule forbids, so an operator specified in terms of ToNumber cannot simply be applied to a typed operand. The resolution is that an operator on typed operands dispatches through the numeric type protocol (1.5.23) rather than through ToNumber, so +a on a uint8 is a uint8 and never a Number. ToNumber retains its meaning for the any type, where the operand's type is not known statically and the existing dynamic semantics apply. 1.9.1 specifies the dispatch; this note records why ToNumber is not the mechanism.

1.7 Runtime Enforcement

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.

Note 1

The checks exist because of the any type. A value reaches a typed position with its type unknown only when it passes through 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 typed program has is not that its author annotated carefully, but that the boundary with untyped code is checked.

1.7.1 The Runtime Type of a Value

1.7.1.1 RuntimeTypeOf ( value )

The abstract operation RuntimeTypeOf takes argument value (an ECMAScript language value) and returns a Type Record. It returns the Type Record describing the type of which value is a value. It performs the following steps when called:

  1. If value is a value of one of the types added by 1.5, return that type's Type Record.
  2. If value is undefined, return the Type Record { [[Kind]]: primitive, [[Name]]: "undefined", [[Arguments]]: « » }.
  3. If value is null, return the Type Record { [[Kind]]: primitive, [[Name]]: "null", [[Arguments]]: « » }.
  4. If value is a Boolean, a String, a Symbol, a Number, or a BigInt, return the Type Record of the corresponding type named in 1.5.22.
  5. If value is a Type Object, return the Type Record for type.
  6. Assert: value is an Object.
  7. If value has a [[RuntimeType]] internal slot, return value.[[RuntimeType]].
  8. If value is an instance of a class, return the nominal Type Record of that class.
  9. If value is an Array, return the array Type Record whose [[Element]] is the element type value carries where it has one, and otherwise the union of the runtime types of its elements, or the any type where it has none; and whose [[Extent]] is value's extent where it carries one and dynamic otherwise.
  10. If value is callable and is not a Type Object, return the function Type Record whose [[Signatures]] are its declared signatures where it declares any, and otherwise the one signature whose parameters are the ones it takes, each of the any type where it declares none, and whose return is the type its body publishes (1.6.2). If value is a class constructor, that signature's return is the class.
  11. Return the object Type Record whose [[Properties]] describes the own properties of value and their declared types, where a property with no declared type has the any type.
Note

This step once read "and has declared signatures", so a callable that declared none reached the last step and reported an object Type Record. The reasoning was that an unannotated declaration stays unannotated, and that reporting a signature of all-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 constructors into one entry. The distinction the qualifier protected is real and survives where it belongs: declaration reflection (7.11.2) reports what was WRITTEN, and gives an unannotated member no signatures; this operation reports what a value IS. The two are allowed to differ, and this is where.

The callable description exposes each signature's effective result: its declared result when present, otherwise its published inferred result under the function value's captured declaration context. A runtime description that places that effective result in [[Return]] must compare it just as a declaration description compares [[InferredReturn]]. Retaining a syntactic [[Untyped]] marker must not bypass an established result, receiver, generic or reference contract. This does not change the declared-only identity and overload-selection rules for declaration signatures, and it does not infer a result for a function that publishes none.

A Type Object is excluded because it is callable and is not a function: calling it is a conversion, uint8(_v_), and typeof reports "object" for it for the same reason. An ENUM's binding is a Type Object, and an enum reports its enumerator names as an object Type Record, which is what keyof Reflect.typeOf(_C_) reads; a function Type Record has no properties to read. The exclusion is written into this step rather than left to the step ORDER, because an implementation that derives a signature needs an evaluator and may therefore perform this step outside RuntimeTypeOf: where it does, it must reach this step only where no earlier step answered, and every earlier step - a Type Object reporting type, a value reporting its [[RuntimeType]], an instance reporting its class, an Array reporting an array type - must win. Hoisting this step above them makes an enum report a function type and lose its names.

A class constructor's signature reports the CLASS as its return, rather than the undefined its body evaluates to. 1.11.2 makes that true rather than assumed: a typed class's constructor may return nothing but this, so a construction yields the class and the return is derived rather than guessed. Without it two classes with the same constructor parameters reported one type, which is the collapse described above surviving for classes after it was fixed for functions. The signature is a CONSTRUCT signature reported in the shape this specification gives a call signature, which is imprecise about 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 object Type Record could not be ranked against a function type. 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 object step below reads each property through it: { 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 captured where it was created inside a generic body - so an annotation naming a type local to the declaring scope resolves there and not where the signature is asked for. Deriving it performs no evaluation: a parameter whose declared type is a computed type (1.4.2.4) is read as any in the derived signature, and a signature that cannot be derived at all is the one signature of all-any parameters with [[Untyped]] true. An implementation may derive a signature once per function and keep it, since nothing it depends on changes after the function is created.

Every value has exactly one runtime type, because the values of distinct value types are distinct (1.5.1). The uint8 representing 0 and the uint16 representing 0 are different values, so a instanceof uint16 is false for a uint8 binding a even though typeof a reports "number" for both. typeof is unchanged by this proposal: it reports the underlying language type, and RuntimeTypeOf is what reports the type of this proposal. 1.12.1 exposes the latter as Reflect.typeOf.

An Array reports an array type rather than the object type describing its indices, so that the type of a value agrees with the types it is a member of: membership for an array type walks a value's length and elements (1.14.7), and the union of the elements' types is the tightest [[Element]] for which that walk succeeds. Without the agreement an operation that RANKS types rather than walking values - overload resolution over Table 30, and the inference of a type parameter from an argument - compared an object type against an array type and found no relation, so no argument could select a parameter of an array type.

The [[RuntimeType]] slot is what a Proxy constructed with a type argument carries, so that it reports the type its trap checks enforce rather than the shape of its target. A Proxy constructed without one has no slot and is an ordinary Object.

The types named "undefined" and "null" are the undefined and null types of 1.5.17, which the type universe names and which PredefinedType (1.4.2) admits in type position. The design writes undefined as a type, since the reject type of a typed promise is undefined by convention when the promise never rejects, and a value that may be absent has a type such as uint32 | undefined.

1.7.2 Selected Construction Admission

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 captured arguments. This association requests a check; it is not evidence that v satisfies m. Obtain it without evaluating an additional initializer, getter, iterator, decorator or body.

Preserve the association when the same producing evaluation supplies a surrounding expression or completion value, including a normal result carried through UpdateEmpty, a consumed targeted break or a normally completing finally. A replacement completion supplies its own producer. Fresh object and array construction can carry a child's association through the data property to which that child's result contributed. At the receiving check, every intervening data-property contribution must still supply that same value. Replacement or deletion invalidates that contribution's association, even when a replacement stores the identical value. This does not cancel the enclosing literal's own obligation after decorators or other ordinary construction effects.

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 contribution even if it stores the identical value; a failed store does not erase a still-current contribution. Ordinary reads of that parameter do not carry its pending association. The ordinary initialization of a separate body variable from the parameter's unchanged initial value preserves the contribution for that admission; a replacement declaration or other store does not. These rules do not give an ordinary iterator or property read a fresh source association and do not move admission into argument or default evaluation.

When the actual named-property branch of call or construction spreading reads a fresh source result's own enumerable String-keyed data contribution, carry its eligible child associations with the captured argument occurrence. At that read require the original producing data descriptor and value at every intervening contribution. Replacement or deletion before capture, including replacement by the identical value, ends the affected association. After capture the consumed outer source property is no longer an intervening contribution: replacing it does not revoke the captured argument's association. Retain every still-intervening contribution inside the captured value until admission. An accessor read, ordinary stored-source read, unrelated result or iterator yield does not acquire an association merely by returning the identical value. Preserve the ordinary iterator lookup, captured own-key order, current descriptor/enumerability checks and once-only property reads, including abrupt completions and last-name-wins binding. Capture performs no admission and does not extend reference liveness.

When actual call or construction spreading resumes the built-in Array value iterator over that fresh source result, its own live indexed data contribution can supply a captured argument's eligible construction association. Require the original built-in next operation, including transparent built-in bound-function forwarding to its actual receiver; a user-defined or Proxy next operation does not acquire this association by returning an equal value or forwarding an iterator result. The iterator-producing hook itself may return the built-in iterator. Match the actual Array and index read by that resumption, not an assumed index counted by the consumer. At the successful data read validate every contributing descriptor/value and retire exactly the consumed outer source edge, retaining nested edges as for named capture. Accessors, keys or entry-pair results, a different backing Array, and ordinary stored sources supply no such association. Keep the association local to that one step and its captured argument; independent reads and reentrant invocations do not inherit it. Preserve iterator lookup, next lookup, iteration, closing and abrupt-completion behaviour. Perform no early admission and end the pending step state on every exit.

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 data property if collection stores the unchanged value. Decay of a Reference Value contributes no fresh construction association. Supply the collected Array's live child associations to its ordinary collected-type admission, including nested tuple and structural checks. Preserve source occurrence correspondence, argument evaluation order, parameter initialization order, and the existing position of rest admission; do not admit an argument early to retain its choice. A selected-member failure does not fall back to another union member, and the declared storage contract remains the original union. End the pending collection association on every normal or abrupt completion. Ordinary iterator/property reads and reentrant invocations gain no association from collection.

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. Replacement or deletion of a still-intervening source contribution invalidates that contribution as usual. A failed store does not publish its replacement value, but this rule rolls back no effects already performed by ordinary evaluation or admission. Completion or failure of the store ends its pending association. The assignment expression returns its captured right-operand value under the existing evaluation rules, without rereading the target, exporting a pending choice to another boundary, or substituting a value converted only for storage. Independent successful-admission facts and valid check elision remain applicable.

For a declared class field, carry its initializer's producing association through the synthetic initializer function's return and call to the field's declared-type admission. This internal transport performs no admission. Use the field's established declaration context and preserve initializer evaluation, anonymous-function naming, captured arguments, and class-element order. Apply admission before publishing the field, including a private field or auto-accessor backing field, and store the admitted value. A field's visibility does not exempt its initializer from the declared check or conversion. Require an eligible selected member without fallback and retain the declared union as the storage contract. A replacement value supplied by a field decorator, or a type default used instead of a written initializer, does not inherit an association from an initializer that did not run. Validate still-intervening data contributions as usual; independent constructor invocations, callbacks and later reads receive no pending association from this field. Initializer or admission failure prevents this field's publication and subsequent element initialization without undoing earlier fields or ordinary effects. End the pending association on every exit and preserve existing private-name, extensibility, placement and reference-liveness rules.

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 specialization and storage acquisition. The declared contract of an enclosing slot remains its original union; the selected member is the admission and materialization contract, not a replacement backing type. A read-only IsOfType query has its ordinary behaviour. This rule does not admit earlier, repeat successful observable admission, roll back ordinary effects of a failed check, or extend a reference lifetime. It adds no wrapper, public property or value identity.

A structural admission retaining an observation of a receiver's own ordinary data property must validate that evidence before using it for materialization and before publishing its new storage contracts. Require that the location still be a data property holding the same observed value. A deletion, replacement by an accessor, or different value throws a TypeError exception, even if the replacement value could independently be admitted. Do not replay a getter, trap, successful membership judgment or selected union search to repair that observation. A same-value data definition or attribute change alone does not invalidate this data-value observation; ordinary permissions and the separate producing-descriptor requirements above remain applicable.

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 verified stored result; an independently invoked operation does not advance the suspended admission's evidence. Before publishing new contracts, validate all retained own-data observations without invoking user code. A prior getter, setter or materialization exception propagates without being replaced by a later stability failure. Refusal publishes none of this admission's new contracts or receiving field, but does not undo earlier ordinary effects, materialization writes or independently completed boundaries. Preserve reference permissions and liveness. Getter results and nonordinary storage retain their applicable separate observation rules; do not turn an accessor or Proxy result into data evidence merely because its value compares equal.

For an admitted structural property whose observed value was supplied by an inherited ordinary data property, retain the supplying Object and the ordinary lookup path from the original receiver to that Object. Every intervening Object must have lacked an own property at that key and supplied the next Object through its ordinary internal prototype link. Establish this provenance from the original read or equivalent internal facts without an additional observable property or prototype lookup. An accessor, Proxy operation or representation-specific read does not supply this ordinary-data evidence, even when it returns an equal value.

At the same validation points as for retained own-data observations, require the same supplying Object and path, absence of shadowing properties on each intervening Object, and a data property holding the observed value under SameValue at the supplier. A changed value, missing data property, accessor replacement, shadowing property or changed intervening prototype link throws a TypeError exception. An equal-valued replacement supplier or a different path to the same supplier does not preserve this evidence. An unrelated property, the supplier's own prototype link, or a same-value data definition or attribute change at that supplier alone does not invalidate it. These are current-state checks; restoring the required path and data before validation satisfies them, subject to independent contribution and reference-liveness requirements.

An ordinary materialization write owned by this admission may create a shadowing own data property on the original receiver. After the write succeeds, require that own property to hold the converted value and require the previously observed inherited path and supplier still to satisfy their retained evidence, ignoring only the new receiver property. Then advance this admission's observation to that verified own location. A subsequent change solely to the former supplier or path is no longer a change to that observation. Do not advance another suspended admission's evidence. A failed write or stability check retains the existing exception, effect and delayed-publication rules. These checks neither install a contract on a prototype merely because it supplied data nor grant a reference authority over inherited storage. Pure membership remains non-acquiring, and missing own Array element initialization retains its separate rules.

1.7.3 Inherited Structural Storage Contracts

After a successful structural admission publishes a property contract using ordinary inherited-data evidence, retain the receiving Object, property key, established backing type, admitted value and ordinary lookup dependencies. This obligation is receiver-specific; it does not assign the receiving declaration to the supplying prototype. Pure membership and failed admission acquire no such obligation. Preserve existing backing rather than replacing it with a later view.

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 property keys are not dependencies of its completed data read.

Apply the mutation's existing destination admission and conversions, but do not select a dependent receiver's converter or perform additional observable membership, specialization, default evaluation or construction merely to validate an indirect mutation. SameValue to that receiver's previously admitted value preserves its retained observation, subject to independent backing, role and reference-liveness requirements. Otherwise require membership and storage evidence already established without further materialization: primitive and literal membership, a successful alternative of a union, every conjunct of an intersection, and the existing declared backing and runtime-type evidence for structured values. Preserve the existing storage-representability requirements: in particular, nominal subtyping does not admit a value-class subclass that adds storage at a base-class contract. A current untyped shape alone does not establish future storage obligations. A value or lookup for which these facts are not established is refused by this indirect operation; an ordinary receiving store retains its separate required admission. An own definition whose value has just completed admission at that same receiver's unchanged backing type may use that operation-local evidence; it is not evidence for an independently invoked operation.

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 TypeError exception before changing the descriptor. For deletion, preserve the ordinary absent-property success and nonconfigurable refusal, then validate dependencies before removal or removal-identity changes; a conflict throws a TypeError exception. Deletion may reveal compatible ordinary data at a further supplier. A reflected descriptor carrying a type must require SameType with an existing property's backing type; it does not replace that backing. Ordinary permissions still apply when the types agree.

For OrdinarySetPrototypeOf, retain same-prototype success, nonextensibility and cycle refusals. After those checks and before publishing the link, a conflicting inherited-storage obligation returns false. Consequently Object.setPrototypeOf retains its throwing wrapper and Reflect.setPrototypeOf its Boolean result. Run no user code between final dependency validation and publication. Earlier argument or descriptor effects, earlier successful definitions and independently completed boundaries are not rolled back.

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 verified own data, its existing own backing takes responsibility for later stores and deletion; retire that inherited dependency. Obligations of other receivers remain conjunctive. Do not retain an otherwise unreachable receiver, value or type graph merely through reverse dependencies on a live prototype.

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 ordered deletion and partial-failure rules. Before acquiring Array backing, refuse with a TypeError exception if the resulting length or derived byteLength representation would invalidate retained ordinary inherited-data evidence; publish none of that failed admission's new backing or pending initialization. An ordinary indexed data read or explicit data byteLength is not a derived representation merely because the supplier is an Array. Preserve the separate backing, reference-permission and reference-liveness rules; a refused mutation does not itself remove or relocate a location.

1.7.4 Successful Admission Within a Typed Store

When a receiving check for an ordinary property store succeeds, retain its receiving Object, property key, required backing Type Record and resulting converted value for the remainder of that store's ordinary internal continuation. Ordinary prototype delegation changes the lookup Object, not this receiver or key. If a subsequent internal step, including the own-property definition, requires the same value at that receiver and key under a SameType backing requirement, use the successful result without repeating membership observations, conversion, construction or callable materialization. A construction selection alone is not successful admission. A separately invoked store or definition performs its own receiving check even when its receiver, key and value are unchanged.

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 abrupt completion, retain earlier effects and independent nested stores without publishing the failed outer write.

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 execution context.

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.

1.7.5 Structural Inputs Across Membership and Conversion

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 property key. Subsequent internal structural checks or conversions of that location within the same operation use the retained value, subject to the validation and write rules below. This includes alternate union members, nested structural members and repeated aliases to the same source location. An abrupt Get propagates its abrupt completion and does not record a successfully observed value.

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 Get to obtain a different candidate merely because the earlier membership failed. Retained input alone proves neither membership nor successful conversion, storage backing, a selected construction, or reference authority. Continue every required type, representation and reference-liveness check.

Keep HasProperty, own-key enumeration and property-descriptor domain checks at their existing phases. Input retention is not a snapshot of presence or of an Object's key domain. A required property which is now absent is still absent, and a newly present optional or indexed property is checked when that phase reaches it. Concrete sequence length and indexed observations retain the separate rules of 1.14.6; structural members reached inside their element conversions follow this clause without combining independent sequence operations.

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 path. An independently changed value or path throws a TypeError exception; do not recover by rereading the replacement. This remains a current-state validation with the existing same-value, attribute, restoration and reference-liveness rules.

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 path and value while allowing only the receiver's newly created shadow. This also applies to operation-owned callable specialization, value copying, tuple-default construction and missing-own-element initialization during admission completion. When a write instead passes through an accessor or Proxy and direct ordinary data is not proved, remove that location's retained input so that required post-write membership observes its result afresh. A successful Set alone does not prove that such a write stored its argument. Preserve existing write refusals, abrupt completions and the existing locations of post-write validation.

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 typed-store forwarding remains governed by 1.7.4, whose callback barriers cannot be bypassed with raw input observations.

1.7.6 Type Membership

1.7.6.1 IsOfType ( value, t )

The abstract operation IsOfType takes arguments value (an ECMAScript language value) and t (a Type Record) and returns either a normal completion containing a Boolean or a throw completion. It determines whether value is a value of the type t. It may call user code, since deciding a structural type reads the value's properties. A structural check reads each member of the type once and decides on what it read.

Note 1

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 composite types already follow when reading a source's properties in a single pass (25.2), generalized to every structural check now that 1.4.2.9 lets any interface be satisfied structurally.

It performs the following steps when called:

  1. If t.[[Kind]] is any, return true.
  2. If t.[[Kind]] is void, return false.
  3. If t.[[Kind]] is union, then
    1. Let selected be the eligible selected construction member for value and t under 1.7.2, or none if this check does not participate in admission with such a choice.
    2. If selected is not none, return ? IsOfType(value, selected).
    3. For each element m of t.[[Members]], do
      1. If ? IsOfType(value, m) is true, return true.
    4. Return false.
  4. If t.[[Kind]] is intersection, then
    1. For each element m of t.[[Members]], do
      1. If ? IsOfType(value, m) is false, return false.
    2. Return true.
  5. If t.[[Kind]] is parameterized, then
    1. If IsSubtype(RuntimeTypeOf(value), t, « ») is true, return true.
    2. If ? IsOfType(value, t.[[Base]]) is false, return false.
    3. If the validation judgment holds of value and t.[[Metadata]], return true.
    4. Return false.
  6. If t.[[Kind]] is literal, return SameValue(value, t.[[Value]]).
  7. If t.[[Kind]] is object, then
    1. If value is not an Object, return false.
    2. For each Property Type Record p of t.[[Properties]], do
      1. Let present be ? HasProperty(value, p's key).
      2. If present is false, then
        1. If p is not optional, return false.
      3. Else,
        1. Let pv be ? Get(value, p's key).
        2. If ? IsOfType(pv, p's type) is false, return false.
    3. Return true.
  8. If t.[[Kind]] is array or t.[[Kind]] is tuple, then
    1. If value is not an Object, return false.
    2. Return ? the result of checking value's length and elements against t (1.14.7).
  9. If t.[[Kind]] is function, then
    1. If IsCallable(value) is false, return false.
    2. Return IsAssignable(RuntimeTypeOf(value), t).
  10. Return IsSubtype(RuntimeTypeOf(value), t, « »).
Note 2

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 data properties, is settled as reading them: a property's type is a claim about what the property yields, and an interface implemented by accessors, which is how interfaces are routinely implemented, would otherwise be unverifiable at exactly the boundary that exists to verify it.

The final step is the general case: the value's runtime type is compared against the target with the subtype relation of 1.2.3.1, so the runtime test and the static one agree by construction. The earlier steps exist because a structural type cannot be decided by comparing two Type Records: an ordinary Object's runtime type is a description of the properties it happens to have, and the question is whether it has the ones the target requires.

Note 3

The function case is checked shallowly and is unsound, and this is the one place where the specification knowingly permits a type violation to go unreported. A function's signature cannot be verified without calling it, so the check confirms only that the value is callable and that the signature it declares is assignable to the target, which admits 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 catch-all step of 1.10.2.3, subject to its reference-parameter and narrowing exclusions. A signature that declares no parameter type and no return type has [[Untyped]] true, but is a catch-all only when it also publishes no inferred result. A return annotation alone makes [[Untyped]] false. A signature that writes 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 (8.1): a method member of an interface carries 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 design does not address it. The available answers are the known ones. The value can be wrapped so that each call checks its arguments and its result, which is sound and is what a contract system does, at the cost of an allocation per boundary crossing, a change to object identity, and a blame mechanism to say which side broke the contract. The check can stay shallow and the unsoundness be accepted, as an optional type system does, at the cost of the guarantee. Or a check can be inserted at each call through a binding whose runtime signature has an 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 proposal's central claim, since a type the engine cannot rely on is a type the engine cannot optimize against. This specification accepts the third: the boundary checks the shape, and the hole through a higher-order crossing is documented rather than papered over. Wrapping breaks the identity interning makes load-bearing, 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 proposal promises elsewhere is withdrawn, since the layout premise never extended inside a function value.

The object case is checked at the boundary but not afterwards, and has the same shape of problem. A plain Object's properties are untyped unless declared with 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. 1.11.1 settles whether a boundary imposes the target's property types on the object it accepts: it does not.

1.7.7 The Boundary Check

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 RequireType. A value of a numeric type added by this proposal retains its type through any; a different numeric target requires an explicit conversion, even when it can represent the value. A type mismatch raises a TypeError, while an unrepresentable Number or BigInt raises a RangeError.

Note 1

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 typed code.

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 shape invites the conclusion that one of them is.

1.7.7.1 RequireType ( value, t )

The abstract operation RequireType takes arguments value (an ECMAScript language value) and t (a Type Record) and returns either a normal completion containing an ECMAScript language value or a throw completion. It enforces the type t on value at a boundary, returning the value of the type t to be used. It performs the following steps when called:

  1. Let selected be the eligible selected construction member for value and t under 1.7.2, or none if this admission has no such choice.
  2. If selected is not none, return ? RequireType(value, selected).
  3. If ? IsOfType(value, t) is true, then
    1. Retain the successful admission, including the selected member of a union, without repeating observable evaluation.
    2. Perform any generic-callable specialization required by 4.6 at the admitted contract, including its admitted object properties and array or tuple elements as specified in 1.7.10, propagating an abrupt completion, and let converted be the resulting value.
    3. Establish any storage contracts acquired by this boundary as specified in 1.7.10.
    4. Return converted.
  4. If t is a union type, return ? SelectUnionMember(value, t).
  5. If t is a numeric type and value is a Number or a BigInt, then
    1. Let r be the result of converting value to t by Table 19, except that a conversion that would wrap, truncate toward zero, round a finite value to an infinity, or map NaN or an infinity to an integer instead yields unrepresentable.
    2. If r is unrepresentable, throw a RangeError exception.
    3. Return r.
  6. If t is string, return ? PrimitiveConvert(value, t).
  7. Throw a TypeError exception.
Note

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 RangeError. A value of any other type has no numeric conversion to attempt, which is a question of type: hence the TypeError, and it is raised before any coercion is tried. Coercing first and then range-checking the result would make an annotation a coercion rather than a check: it would accept a string, a Boolean, null, and any object with a valueOf, and at a float type, where every value is in range, it could not fail at all, so a missing field would become a NaN that surfaces far from the annotation that admitted it. A value of a numeric type this proposal adds is not converted at a boundary: it carries its type, and a boundary checks a value rather than coercing it, so a uint8 at a uint16 is a TypeError however it arrived, as the checker refuses it statically. Only a Number or a BigInt, which carries no numeric type of its own, is converted, as a literal is; a program that means to change a value's numeric type converts it explicitly.

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 1.13.5 states, and which distinguishes a string that is not a literal of the type from one that is a literal out of range.

1.7.7.2 Ordinary Nominal Prototype Obligations

This clause specifies a bounded ordinary prototype specialization for an exact receiving operation whose target is a non-generic source class with a resolved constructor and a nonwritable, nonconfigurable own data property "prototype" whose value is an Object. It applies to checked and explicit receiving conversions and their elided annotation equivalents. It does not make a selected union member or rest partition into an independently acquired exact class contract, and does not define general composite descendant, metadata, generic-specialization or exotic prototype backing.

The operation's normal result must be an Object; otherwise throw a TypeError exception. Before publishing that Object, establish that its ordinary prototype prefix reaches the target prototype. A prefix consists of the result and each intermediate Object whose outgoing ordinary prototype link is followed before reaching the target; the target's own outgoing link is not part of this dependency. If an ordinary chain ends without reaching the target, throw a TypeError exception. This applies also to a normal result supplied by a converting constructor. Propagate an earlier abrupt completion without this later check and preserve ordinary earlier effects. This clause supplies no new proof for an initially exotic prefix or replaceable target prototype.

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 IsOfType acquires no obligation. Whole membership success at a union or heterogeneous tuple does not by itself publish separate exact obligations for its successful alternatives. A separately completed exact receiving conversion retains its obligation even when a later enclosing operation fails.

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. Evaluate its proposed prefix using ordinary internal links with the requested replacement substituted at the changed Object. Do not invoke a getter, a Proxy trap or other user code. If any prefix fails to reach its required target or would require an exotic prototype observation, return false without changing the link or any live dependency. The ordinary caller determines whether that Boolean result is returned or causes a TypeError exception.

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 false. An obligation must not be discarded while its instance can still be observed again. Cleanup invokes no ECMAScript code. These checks do not remove an element, relocate storage or change a reference's invariant target; the independent reference-liveness rules still apply.

Note

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.

1.7.7.3 Selecting a Union Member at a Boundary

Where the target of a boundary is a union, a member supplies the receiving contract. A compatible generic callable may still require specialization at that contract. Which member is selected is decided by what the VALUE is rather than by where a member was written. A union is canonical (1.2.2.8): its members are held in a fixed order and Type Objects are interned on that record, so uint8 | uint32 and uint32 | uint8 are one type, and a boundary may meet it as a Type Object that no source text spelled. A rule that read the order of the members would therefore give one type two behaviours.

Note 1

A union WRITTEN in an annotation has its members canonicalized like any other: ordered by 1.8.5.2, and a member that is itself a union contributing ITS members rather than itself.

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.

1.7.7.3.1 SelectUnionMember ( value, t )

The abstract operation SelectUnionMember takes arguments value (an ECMAScript language value) and t (a Type Record whose [[Kind]] is union) and returns either a normal completion containing an ECMAScript language value or a throw completion. It returns the value of some member of t that value crosses into, or throws where no member admits it. It performs the following steps when called:

  1. Let selected be the eligible selected construction member for value and t under 1.7.2, or none if this admission has no such choice.
  2. If selected is not none, return ? RequireType(value, selected).
  3. For each member m of t.[[Members]], do
    1. If ? IsOfType(value, m) is true, then
      1. Retain this successful admission without repeating observable evaluation.
      2. Perform any generic-callable specialization required by 4.6 at the selected contract, including its admitted object properties and array or tuple elements as specified in 1.7.10, propagating an abrupt completion, and let converted be the resulting value.
      3. Return converted.
  4. If value is a Number, a BigInt, or a value of a numeric type, then
    1. Let exact be a List of the members of t.[[Members]] that are numeric types whose values include the mathematical value of value exactly.
    2. Order exact by width, narrowest first; where two members have the same width, an unsigned integer type precedes a signed one; and where value is an integer, every integer type precedes every floating-point and decimal type. bigint has no width limit, so it follows every sized integer type.
    3. For each member m of exact, do
      1. Let r be Completion(RequireType(value, m)).
      2. If r is a normal completion, return r.
  5. For each member m of t.[[Members]], do
    1. Let r be Completion(RequireType(value, m)).
    2. If r is a normal completion, return r.
  6. Throw a TypeError exception.
Note 2

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 1.6.7.4 refuses 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 integer source prefers an integer member for the same reason. A member's choice is observable, since it decides what Reflect.typeOf reports and which arithmetic the value then participates in, so a program that means one member in particular writes the conversion rather than relying on this operation.

The three ways a numeric value may fail to fit a type are distinguished, and each is treated according to what the program said.

Table 22: Treatment of a Value That Does Not Fit
The program writesExampleTreatmentBecause
A literal in a typed positionlet 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 conversionuint8(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 typed boundarylet u: uint8 = anyValue;A RangeError at run time.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 typed boundarylet u: uint16 = anyValue;, anyValue a uint8A TypeError at run time.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.
Note 2

The boundary converts rather than merely checking, which is what makes untyped data usable: a Number parsed from JSON reaching a uint8 field is a Number, not a 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 RangeError otherwise, keeps the loss from being silent. This is the same distinction the checked arithmetic of 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.

Note 2

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 any-typed operand, a getter, a computed key - because a shape that omitted what could not be read would report every declared member as missing.

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-typed where _t_'s a is not, and { a: 1, ..._t_ } is not.

1.7.8 Where Checks Are Inserted

A check is inserted where a value whose static type is any reaches a position whose type is not, and nowhere else. Table 23 lists the positions.

Table 23: Boundaries
PositionCheck
The initializer or assigned value of a binding with a declared type t, INCLUDING a binding that is an element of a destructuring patternRequireType(value, t)
An argument passed to a parameter of declared type tRequireType(value, t)
The operand of a return in a function with a declared return type t, or with a published inferred return type t (1.6.2)RequireType(value, t)
A value stored to a property or field of declared type t, including the initializer of an explicitly typed own propertyRequireType(value, t)
A value stored to an element of an array of element type tRequireType(value, t)
A value read from a position of the any type into an operator whose operands are typedRequireType(value, t) for the operand type t the operator requires
A value crossing into a typed position through reflection, including Reflect.set and Reflect.definePropertyRequireType(value, t)
Note 1

The list is the same list of boundaries at which a user-defined conversion is consulted (1.6.8) and at which the metadata protocol of the primitive metadata extension applies, and at which a where clause that cannot be decided statically is evaluated. A where clause the argument decides, because every parameter it reads is bound to a compile-time evaluable argument (a literal, a const with a known value, or a binding narrowed to one), is a type error at the call when it does not hold, as a specialization predicate is at its application, whatever the call's own source text, since the declaration's contract applies wherever it is called. This is not a coincidence: a boundary is where a value acquires a type it did not have, and every mechanism that has something to say about that acquisition says it there.

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 any is not rejected by this static assignability judgment; its value is converted and checked by RequireType at the runtime boundary.

The fresh-literal path and the runtime any boundary may require member conversion. That conversion is IN PLACE: the callee receives the same object, so a member the type must convert is refused where the property is not writable. Writability is the test rather than frozen-ness — a sealed object's properties are writable and cross, a frozen object's are not — and refusing rather than copying is what keeps the identity the boundary promises.

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 path above. Only a value reaching the boundary through a binding with no annotation escapes both, and such a declaration in a branch that never runs is therefore not checked. Deciding it earlier would mean tracking frozen-ness through a binding, which is a property of a VALUE rather than of its type and which nothing else in this specification tracks.

Note 2

RequireType at an object type CONVERTS the members the type declares, and does not merely test them. A plain object reaching { 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.

Note 3

"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 ToNumber instead - a String as its parsed value or a NaN, null as zero, undefined as a NaN, an object as whatever its primitive is - would produce at a checked boundary exactly the silent failures the 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.

1.7.9 Elision of Checks

Completion analysis distinguishes normal completion, return, throw, and breaks and continues with their targets. A switch clause that completes normally falls through to its successor; an empty final clause reaches the end of the switch. A break or continue makes subsequent statements in that path unreachable. Loops and labelled statements consume only their own transfers, and an abrupt finally completion replaces the incoming completion. The same normal-completion fact is used when inferring an effective result type; a reachable implicit exit contributes 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.

  • The static type of the value is not any, is not a literal type, is assignable to the target, is not a published inferred return type (1.6.2), and is STABLE (1.7.9.1). The result is established statically, and any violation was an Early Error. The literal exclusion is not a detail: a numeric literal is assignable to uint8 by 1.6.6 and the boundary is still what CONSTRUCTS the value of that type, so 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.
  • The target is the any type. Every value is a value of it.
  • The value's static type and the target are the same type, subject to the stability, materialization and effect requirements of this clause. Type equality alone does not establish that a dynamically obtained value already satisfies that type.
  • At a RETURN, where every 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 undefined. That is a requirement on the PROGRAM and not only on the elision: it is a Syntax Error for a function whose body can complete normally to declare a return type that does not admit undefined. For this rule and for an explicit bare 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 undefined and is checked at that type as an expression return is. In an async function or async generator, a returned Promise contributes its recursively resolved type, and an async generator applies the same resolution to yielded values before comparing them with Y. Runtime conversion and checking of dynamic contributions uses the same effective type; an async function checks after resolution and a generator checks its completed result. The rule applies where a return type is WRITTEN — an inferred one is not published (1.6.2) and so promises the caller nothing — and 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.
  • The index of a read or write of a fixed-length [N].<T> is known to be below N, because N is a compile-time constant and the index is a value generic that 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.

1.7.9.1 Stability of a Static Type

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 live path, and tagged template calls obey the same rule. Existing restrictions on published inferred returns continue to apply.

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 replacement's result contract. This judgment does not alter callable equality or install a wrapper around an admitted function.

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 contribution is also subject to stability: 21.6.1, 21.6.2, 21.6.3, 21.6.4 and 21.6.6 supply static views, not proof that a public getter or method has not been replaced. Retain the required boundary when that proof is unavailable. Successful admission at an inferred initialization establishes the destination binding's own contract; later uses need not inherit instability from the initializer's property access.

An array or tuple static view alone does not establish stability of a property read. In particular, an inferred initialization can preserve an already conforming plain aggregate without installing typed storage. A later mutation through another alias can invalidate that view. Retain a required receiving boundary unless an independent proof establishes the actual storage or property-read contract. Neither immutability of the binding nor a successful membership check at initialization supplies that proof. This rule changes neither the backing value nor the effects of a failed receiving check, and requires no additional property read or reference-liveness assumption.

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 captured write, an assignment-pattern target, an iteration target, or a VariableDeclaration initializer that writes that same binding participates even when its occurrence precedes the declaration text or lies in another body. A declaration without an initializer does not itself write a value.

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 Static Type query reads a callee signature or a contribution follows an unchanged initializer alias. It neither makes an untyped contribution participate nor permits elision on a published inferred return. Dynamic name lookup, direct eval, reference aliases and global-object access must retain their existing replacement and liveness rules. In particular, a Script-global object binding can be changed through the global object; absence of a lexical assignment alone does not prove otherwise. A runtime identity guard cannot justify an irrevocable Early Error.

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 global object affects its properties, not a same-named declarative binding. A named property footprint is restricted to that name; an unknown property key widens to global-object properties, not to every lexical binding. Indirect dynamic source can additionally reach mutable bindings in its global lexical environment. Neither kind of footprint replaces a function-local or module-local binding solely because its name matches.

The footprint includes writes through tracked global-object aliases and their supported binding projections, reflective property writes on those aliases, and exposure of the global object as an argument or receiver of an opaque invocation. Aliased, optional and bound invocation forms participate. Invoking indirect eval or a dynamically constructed function supplies a possible global dynamic-source footprint without interpreting its source string during checking. Constructing the function does not itself execute that body; binding arguments does not itself invoke the bound target. Effects of evaluating those operations and arguments still participate normally.

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 global object merely because another property of that container holds the global object. Track possible values by allocation and property identity: aliases of one allocation share its slots, distinct allocations do not share a slot merely because its name matches, and unknown-key stores contribute to every potentially selected slot. Object and array literals, supported projections, stores, copies and reflective stores participate without constructing or accessing a user object during checking. A later overwrite does not erase an earlier possible origin from a may-flow summary.

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, abrupt completion and reference-liveness rules remain unchanged.

A finite may-flow graph joins candidates across supported stores and source callable contexts. It does not claim exact per-invocation heap contents. Preserve the contributing-read and capture rules below when applying its mutation footprint. Cyclic storage requires a finite graph summary, not recursive execution or copying of the stored objects. An incomplete or exhausted analysis cannot certify absence of an affecting global effect, including for a read queried before the incompleteness was discovered. This rule supplies no new result boundary and does not erase an independently established local contract.

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 global object as an explicit argument. An opaque returned value can carry such capabilities onward. Merely copying an ordinary own-data callable value does not invoke it. A callable's independently established result contract is distinct from its effect summary.

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 ordinary object. Its effect footprint includes the enumeration of own keys, descriptor inspection and reads of the remaining enumerable own properties. Statically known excluded keys do not contribute a rest read, and an inherited or non-enumerable property does not contribute merely because it exists on the source. Copy possible property values into the result without identifying the result with the source or with a capability stored inside it. An unavailable operand or an exotic enumeration supplies a possible effect; an empty analysis summary does not prove there is none.

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 iterator method and iterator, obtaining and invoking next, reading result properties, and closing an unfinished iterator. Even an empty array pattern acquires an iterator and can close it. Async iteration additionally includes the applicable async-method lookup, fallback and awaiting behaviour. A numeric element projection alone is not a summary of these operations. Rest arrays and rest-parameter arrays are fresh containers; argument spreading does not restrict a spread to one positional argument.

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 capture of the call's callee. Preserve established earlier captures and independently enforced local contracts. A positive summary for dense fresh arrays requires evidence of the applicable intrinsic iterator and next method, absence of an affecting close hook, and absence of intervening effects that defeat that evidence. Array holes, unknown iterables, async suspension and delegation are not justified by that array summary. An independently proven local resumed delegation retains its applicable input boundary (37); an opaque effect classification must not discard that proof. These static observations neither acquire an iterator nor read a user property during checking.

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 field does not prove the other fields effect-free. A positively established absent optional field contributes no read; unavailable evidence is not such a proof. Include inherited or exotic descriptor lookup where applicable, without executing a lookup during checking.

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 property key, contributing installation and access positions, receiver and setter argument. A plain assignment target need not read its getter; compound and update operations have their applicable read and write effects. Preserve an earlier capture and an independently established contract. Direct, bound and supported 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 abrupt completion.

Ordinary property-key coercion has its own effect footprint. Evaluating the key expression and converting its resulting value are distinct operations. Computed references, computed property names, the ordinary key path of the 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 field names participate during definition, separately from a deferred body or instance initializer. A proven short circuit contributes no skipped conversion effect; a private-name brand check does not use ordinary property-key conversion. Positive primitive and intrinsic evidence must account for its contributing sources and stability dependencies without executing a user conversion during checking. No such proof changes reference authority, liveness, nominal prototype obligations or abrupt-completion propagation.

Absence of a candidate in a finite value-flow inventory is not evidence that an operation has no effects. A positive summary must cover every contributing alternative, including an unmodeled alternative joined with a known value. Retain a possible effect for an invocation, property operation or implicit protocol whose required operand or operation evidence is unavailable. This includes implicit coercion, primitive boxing accessors, tagged invocation, enumeration, membership hooks, decorator application, disposal, suspension and resumed execution. Preserve each operation's ordinary evaluation point and independently established contracts; in particular, an already captured callee is not replaced by later argument effects. A deferred body does not precede an earlier straight-line capture merely because its source occurs earlier. Relate its effects to applicable invocation points and preserve capture order within an activation. Callee and argument evaluation, including argument access performed by an invocation adapter, precedes reads inside the entered body. A later operation in that activation cannot erase an earlier capture merely because both are deferred. Effects can precede a capture in a subsequent invocation or loop iteration, including iteration in a caller. Unknown invocation and later activations retain possible effects. Apply this rule to keyed writes as well as unknown effects. Implicit admission at a binding, typed member, parameter or result boundary also retains possible conversion effects. An admission that may convert a value or install storage obligations cannot preserve unsupported untyped-backing evidence from its input. A successfully enforced primitive result is distinct from the effects of admitting that input; ordinary primitive facts and established parameter contracts remain available independently. A primitive-operand proof excludes applicable coercion hooks, not user-defined operator dispatch on a different operand domain. Preserve the ordinary intrinsic operation only when its operand-domain and stability conditions are established. A builtin type spelling supplies intrinsic admission or operand facts only while its denotation is established. Replacement through a global property or reflective alias withdraws unsupported intrinsic evidence subject to the existing capture and activation rules. A known absent accessor is distinct from an unknown accessor. Syntactic type names are not ordinary value reads under this rule.

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 realm does not become a replacement merely because the original namespace property changes. Conversely, a replacement, accessor result or proxy wrapper does not acquire the original intrinsic's summary from its name, compatible signature or native callability. Include the effects of obtaining the method before its arguments and the effects of invoking the captured callable after its arguments; an argument-side replacement does not retarget that capture. An ordinary data read of a callable is not its invocation. Where the applicable invocation summary is unavailable, retain possible effects rather than assuming the call inert. Supported summaries retain all operand, property-access and stability conditions; they neither execute a lookup during checking nor change receiving checks, prototype obligations or reference liveness.

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 captured, renamed or borrowed. For the original call and apply, retain the actual target receiver and forwarded arguments. For the original bind, retain the captured target, receiver and argument prefix for later invocation; binding does not itself invoke the target. Include possible effects of the target prototype operation and the applicable length-presence, length and name operations during binding. Preserve the bound function's applicable prototype for subsequent property lookup.

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 iterator acquisition. Borrowed and composed adapters preserve these operand flows and operation positions; unknown arity or incomplete flow is not evidence that an argument or its effects are absent. Obtain the adapter before evaluating its arguments, then perform its internal operations and any target invocation in the specified order. An abrupt completion prevents the subsequent runtime steps. These summaries add no checking-time invocation or repeated runtime access and change neither receiving boundaries nor reference authority or liveness.

Possible deletion of an own property participates in the stability of dependent later lookups. Retain the aggregate identity, possibly matching property key and contributing deletion position. An ordinary deletion does not obtain the deleted property's value or invoke an inherited getter. A later lookup may instead reach an inherited property, including through removal on an intermediate prototype; include its applicable access and invocation effects at that later operation. An exotic deletion retains its own possible effects at the deletion. A positively established non-configurable own descriptor excludes exposure through that property. Unavailable prototype or key evidence is not proof that no inherited effect can occur.

Do not discard an already captured value merely because its former property can later be deleted. An argument-side deletion does not retarget the callee already obtained. Apply supported aliased, computed, optional and reflective deletion routes to the same allocation and property dependencies, preserving the original receiver of inherited lookup. Parentheses do not turn the final deletion reference into a property read; intermediate accesses needed to obtain its base still participate. Deleting a non-reference expression does not remove a property, and a proven short circuit contributes no skipped property operation. These observations neither execute deletion or lookup during checking nor change runtime deletion restrictions, receiving boundaries, prototype obligations or reference authority and liveness.

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-field, array-like, iterator, coercion, membership, construction and other prototype-sensitive operations at their consumption positions. A use that does not inspect the chain does not acquire such an effect solely because relinking occurred.

An already captured value is not retargeted by a later prototype change. In particular, an argument-side change does not replace a callee obtained before the arguments. Internal operations after argument or operand evaluation include relevant changes within those arguments or operands. The original bind captures the applicable target prototype during binding, after its arguments; later relinking of the target does not relink that existing bound function, although mutation of the captured prototype object can affect subsequent lookup. Retain conservative ordering where stronger occurrence evidence is unavailable. These summaries execute no user operation during checking, add no runtime access or replay, and change neither receiving boundaries nor prototype obligations or reference authority and liveness.

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 global object. Descriptor-table access and conversion retain their own effects at creation. Later inherited access includes effects of the established or unavailable initial chain at that use, preserving the original receiver and positively established own-property hiding conditions.

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 proposal's type field, including applicable inherited field access, and the effects of any subsequent type-directed admission, conversion or defaulting. A result contract alone does not establish that those operations cannot run user code. Retain their effects unless the applicable operations have positive effect evidence. Apply this rule consistently to creation and supported Object and Reflect property-definition routes. A getter installed by a descriptor is distinct from a getter invoked while converting that descriptor; installation does not itself invoke the installed getter.

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 property name nor native callability establishes effect-freedom. An unknown property key retains all applicable accessor alternatives. In particular, a summary for the original prototype getter on an ordinary receiver cannot be transferred to a replacement getter or an exotic receiver.

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 data property or a replacement setter with the same name does not receive that summary. Preserve the original setter's argument-validation order, possible exotic receiver operation and ordinary rejection obligations. Installing a new prototype is distinct from invoking a property through it later. The setter's undefined result does not replace the right-hand value of an assignment expression.

Obtaining the Reference for a property assignment does not capture its eventual accessor implementation. Setter selection during the store includes relevant changes from right-hand-side evaluation, including property definition, deletion or relinking. An explicit call through a setter value already obtained preserves that callee across later argument effects. Preserve the original receiver through inherited lookup and supported reflective stores. A known explicit receiver of 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 wildcard write cannot retarget a value already captured for an earlier base expression.

The ordinary store method's identity alone does not establish absence of user code. Include applicable proposal storage admission and effects of the receiver's descriptor and definition operations, including typed properties, indexed storage or an unavailable backing representation. Internal storage metadata can carry a declared type even when the raw property descriptor does not. Preserve effects at the applicable admission or store operation unless positive operation-specific evidence excludes them. Initial array and rest-allocation prototype dependencies participate in inherited setter lookup. None of this evidence grants reference authority, extends liveness or bypasses receiving, representation or prototype obligations.

Where every contributing callee alternative has established bound invocation flow, apply the captured target, receiver and arguments through that flow without additionally treating the same invocation as an unmodeled argument escape. This establishes neither purity nor a result contract; actual target effects remain. A possible bound candidate does not cancel an unknown contributing alternative. These store and invocation summaries execute no user operation during checking, add no runtime access or replay, and retain abrupt completion and the distinction between possible historical values and guaranteed descriptor presence.

Descriptor observation and ordinary property Get are distinct operations. For an established original 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 covers that operation. Neither a method spelling nor native callability proves the original implementation or effect-freedom.

A reflected descriptor is a fresh ordinary object with shallow field values, rather than a live alias of the source property. Retain the observed callable and value identities at the observation's operation position; a later target store or definition does not retarget an earlier obtained field. A getter or setter extracted from a descriptor is not implicitly bound to the observed object. Its later invocation retains its actual receiver, arguments, abrupt completion and receiving checks. The initial prototypes of descriptor objects and bulk descriptor tables follow the actual built-in's realm, including a borrowed cross-realm original. These observations do not establish purity, a public result contract, reference authority or liveness.

Follow a transparent unannotated immutable callable copy to the read in its initializer. A copy made after an affecting replacement does not recover the former declaration's result assumption. A copy made before a later replacement can preserve the captured value's evidence. In a simple top-level sequential region, compare an effect with the contributing read, not with the end of the containing call: an argument-side write does not change the callee already obtained for that invocation. Loops and deferred callable or instance-field regions require a conservative matching footprint when execution order is not established. A may-alias inventory cannot discard a possible global origin merely because another assignment supplies an ordinary object.

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 finite footprint analysis is not a general proof of absence of effects through arbitrary heap aliases, callbacks or dynamic lookup, and its failure to recognize an effect does not establish implementation identity for check elision.

Example 1 (Informative)

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();
Example 2 (Informative)

Storing the global object in a container does not prevent the later property write from replacing f. The Number receiving boundary admits the replacement result. Replacing 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();
Example 3 (Informative)

Evaluate these Scripts successively in the same realm. The earlier accessor is not read during checking. Its eventual result can supply the global object, so the later call cannot use the former string result to reject the Number receiver. A Map containing the global object, or a previously generated function returning it, requires the same separation between unavailable effect evidence and an independently enforced contract.

// 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 contribution inference requires that unavailable assumption, the contribution is unknown by the existing inference rules; a former declaration's result cannot be retained solely to create a new inferred boundary. A declared result contract of the enclosing function still applies, and another independently established published contract still applies. A shallow function storage check does not install a wrapper checking every invocation of the stored value.

Example 4 (Informative)

The final call returns "s". Replacement makes the call through typed unknown, so neither produce nor id obtains a result contract from that call. Adding : T to either function's result requires a runtime TypeError at that declared boundary, including for this untyped caller. Merely annotating the mutable callback storage does not add that return boundary.

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);
Note

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 function check of 1.7.6.1, which is the one place this specification knowingly permits a violation to go unreported. That admission is bounded: it lets a wrong function be stored. What the elision would add is that every later boundary trusts the signature anyway, which turns a bounded admission into an unbounded one.

A lexical assignment inventory proves only the bindings and effects it models. A Script-global object binding can change through an alias of the global object, reflection, indirect eval, a dynamically constructed function or a later Script. Absence of a lexical assignment in one source is therefore insufficient implementation evidence for elision. Parentheses do not make an otherwise direct eval indirect. Sloppy eval declaration injection and dynamic lookup can also hide an outer immutable binding without writing to its storage.

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 captured callee, receiver, arguments, value and location. It does not repeat a lookup, argument, spread, invocation or constructor, retry another overload, roll back prior effects, or revive an invalid reference. A guard cannot justify an irrevocable Early Error. Static contribution and result-contract formation remain distinct from this runtime elision proof; withdrawing an optimization is not permission to erase an independently established contract.

Note 1

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 floor and a closed end raises the ceiling, independently of each other, which is why the rule above is stated over members rather than over endpoints — the members are already what 21 fixes, and restating it here would be a second place for the two to disagree.

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 specialization once its parameters are bound (5.1), so a body may assume it without being re-checked per application.

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 site, and an implementation compiles a source text rather than a program: a function is reachable through 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 10.1.9.1 as well as through an assignment.

Note 2

In a fully typed program every check above is elided, and what remains at the boundaries of a partly typed one reduces to a tag test on a sealed typed class. This is the property the proposal's premise rests on: a check the engine can discharge statically is not a cost, and the type is available to the engine as a fact rather than a hint. A program that is entirely untyped inserts no checks either, since it has no typed positions. The cost falls where the boundary is, in proportion to how mixed the program is.

1.7.10 Typed Storage

A class in which at least one public or private field is typed is automatically sealed, as if PreventExtensions had been performed on each of its instances, and its prototype is frozen. A field may be written, since a field's type is what constrains it, but a property may not be added or removed, and the prototype may not be modified.

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 constructor body has run, so an instance sealed at allocation could not receive them. Adding a property in a constructor is therefore refused by the type system rather than by the seal: an assignment this.x = v in the body of a typed class, where x names no field the class declares or inherits, is a type error (1.2.8). A computed this[k] = v with a statically established key or finite set of keys is checked against every selected member. An unknown key is left to the seal, which is in force for every write after construction.

Note 1

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 undefined, as it does for any non-extensible object, and a program may legitimately be testing for that - 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 specialization in an admitted property (4.6). Retain the value already read and its admitted contract, including its selected union member, with the property's identity and key. After membership succeeds, complete required specialization in admission order and use ordinary Set with the original Object as receiver only when the value changes. Propagate an abrupt specialization or write; if Set returns false, throw a TypeError exception as for in-place object conversion. Do not reread the property, copy its container or replace an accessor with a data property merely to retain the specialization. An unchanged value needs no write. Readonly view permissions do not grant permission to change readonly backing storage.

Where several admitted paths reach the same Object and property key, their contracts apply together: use their canonical intersection for materialization and for a newly acquired backing contract. Preserve the order of retained observations. Equal consecutive observations of that location share one required materialization and write, even when other locations intervene; a different observed value at that location makes a later observation distinct again. A later path does not override an earlier contract, and an existing backing contract is not replaced. This rule does not combine alternative union members into an overload group. It also does not remove a specialization required by a combined callable contract.

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, specialization, ordinary Set, overlapping-location and delayed-publication rules above also to elements admitted against an array Type Record. Retain each observed element and its selected union contract, together with the admitted extent, including for an empty Array. Carry this evidence through nested boundaries and retain only the evidence of selected alternatives. Acquire homogeneous element storage rather than a separate property contract for each occupied index. Preserve existing array or tuple backing contracts; a receiving view does not replace them. An unchanged element requires no write. These rules do not grant a reference new permissions or establish its liveness after intervening effects.

When an Array acquires owned homogeneous element storage, apply the value-copy requirements of 1.11.4 to its retained elements. Perform required replacements by ordinary Set, without rereading elements or replacing accessors with data properties. Passing an already typed array through another compatible view does not by itself store its elements again. Publish newly acquired element storage only after this admission's required writes succeed; earlier ordinary effects and independently completed nested boundaries survive a later failure. Preserve a backing contract established by such an independent boundary during a setter. This in-place admission is distinct from elementwise conversion that constructs a new Array.

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 undefined. Its ordinary Get may produce undefined or an inherited value; that value must satisfy membership before initialization. A supplied hole does not request DefaultValueOf or an omitted trailing tuple default. Preserve the Array's identity and custom prototype. Independent membership and viability queries do not initialize positions. An elementwise conversion that requires a new Array retains its separate copying rules.

For such new owned storage, retain the observed length and, immediately before each indexed Get, whether the position is an own property. Retain that presence separately from the value and the selected admission evidence. After membership and combined length-domain validation, but before required materialization writes, compare the current length and each retained own presence with these observations. At each currently own position, require a data descriptor and compare its value with the last retained observation there using SameValue. An own accessor descriptor throws a TypeError exception at this phase, even when its observed value satisfied the element type. A discrepancy throws a TypeError exception. A missing own position on a non-extensible Array also throws a TypeError exception at this phase. These inspections use internal own descriptors and do not invoke getters.

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 captured immediately before that write at its own definition, after lookup and conversion effects. A discrepancy throws a TypeError exception. Scope this check to the receiver, key, and executing outer write; callback and nested operations remain independent. Remove the scope on normal or abrupt completion. Earlier abrupt completions retain precedence.

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 SameValue to its prepared value. A discrepancy throws a TypeError exception. Preserve independently established backing, including its completed effects, and validate its length and own presence without replacing its contract. These checks do not reread elements or replace own accessors with data. The judgment concerns current descriptors: independently replacing an accessor with matching data can satisfy it; installing an accessor after preflight cannot. A representation refusal after a union alternative was selected does not retry another alternative.

After all plans validate, create each still-missing own element as a writable, enumerable, configurable data property holding its prepared value, and publish the admission's new backing contracts. There is no user-code execution between this validation, initialization, and publication. These definitions neither call inherited setters nor repeat getters, conversions, or defaults. Existing own descriptors, including a matching data property created by an ordinary materialization write, retain their attributes. A failure publishes none of the admission's still-pending initialization or new contracts; earlier ordinary writes, independent definitions, and nested boundaries survive. Filling positions within the admitted extent does not itself grow, remove, or relocate storage or establish a new reference invalidation event. Retained observations are current-state checks, not a lease against user-code mutation.

The retained-admission requirements also apply to tuple elements. Retain the element values and admitted union contracts of the assignment selected by SequenceAssignment, not evidence belonging only to an unsuccessful candidate. For required fixed positions and at most one unbounded homogeneous array rest, retain the complete prefix, rest and suffix domains. At an admitted length n, a suffix of s required positions starts at index n - s; positions between the prefix and suffix have the rest element type. The minimum length is the sum of the required prefix and suffix widths. This assignment follows from the length, including when the rest is empty, and is not selected from current element values. Acquire these complete domains, after required materializations, value copies and ordinary writes succeed. Preserve existing backing contracts and use the same failure and publication rules as for homogeneous-array admission. A later compatible view does not store the elements again.

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 integer length from its required prefix width through its total position count, with each existing index governed by that declared position's type. Acquiring this domain or constructing a fully default-filled tuple does not freeze it at the observed or maximum length. Existing backing remains authoritative. Membership and viability do not fill omitted positions; conversion that fills an omitted trailing default retains the construction rules of 1.4.2.7. A supplied hole or supplied undefined is not an omitted position.

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 undefined.

For each selected construction, create a separate Array using the receiving realm's Array prototype, its retained supplied values, and the declared values of its omitted defaults. Do not reread source properties, rerun a default initializer, grow the source, acquire replacement backing on the source, or copy an enclosing container merely to fill this tuple. A source accessor may supply a retained value for this genuine copy, and a non-extensible source does not itself prevent the copy. Newly owned output elements remain own data locations. Apply required callable materialization, nested construction, and value copying to the output, and acquire its complete tuple storage domain before the result can escape through an enclosing ordinary Set. The source's existing backing and reference locations remain authoritative.

Where retained paths reach the same receiving Object, property key and observed value, and any of them requires this construction, carry all of their retained subvalue evidence into that construction, including a path that does not itself fill defaults. Their observed supplied lengths and required completed lengths must agree, and the declared defaults at every omitted position must be SameValue. The completed length must satisfy every retained tuple and homogeneous-array length domain. Reject an incompatible plan with a TypeError exception during the surrounding admission's combined-plan validation, before its materialization writes. Do not choose one path's defaults by traversal order or memoize a result solely by source identity across different receiving locations. The ordinary equal-consecutive-observation rule continues to govern shared materializations and writes.

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 TypeError exception; an absent optional property contributes no acquisition; a newly present declared property or a newly produced key governed by an index signature must satisfy its contract. Reuse retained evidence for unchanged supplied values, and check changed or newly produced values without rereading the source. Do not acquire contracts solely for optional source properties absent from the output.

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 TypeError exception and an abrupt result propagates; neither resumes selection at another union member. A later enclosing failure publishes none of that enclosing admission's pending contracts, but does not roll back an earlier write or a nested output whose boundary completed before it was passed to a setter. Separate nested boundaries do not form a transaction over the reachable alias graph. Allocating a separate tuple does not remove or relocate the source's locations. A reference to an enclosing receiving property observes its ordinary replacement under the existing reference rules.

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 interval alone does not establish current occupancy, including for a previously typed enclosing object whose nested tuple was shortened. Such a proof may use internal facts without introducing observable reads; otherwise preserve the required boundary. These rules do not change default evaluation at type formation, its evaluable-fragment limits, later storage growth through DefaultValueOf, or the separate assignment requirements for tuples containing rests.

Intersect a defaulted tuple's complete length interval and positional types with all other contracts acquired in the same admission. A finite maximum is not by itself a fixed extent; flatten the combined domain to one length only if that is its sole permitted length. Later shortening, growth and intrinsic mutation use the existing tuple-domain and surviving-location checks. An unsupplied newly exposed position uses DefaultValueOf of its actual backing type under the initialization rules, not the tuple's construction initializer. Removal invalidates the removed location's references, and later regrowth does not revive them. A defaulted position does not authorize a hole.

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 TypeError exception before element materialization. Do not select only the first or last path's contract or record only currently occupied indices. A contract independently established during an ordinary write is preserved.

For these required prefix/rest/suffix contracts, a length change must preserve SameType between the old and proposed backing types at every index below both lengths, as well as remaining in the permitted length domain. This judgment does not depend on whether a reference is currently live. An indexed store or definition that grows the Array applies this judgment before element conversion and obtains its destination contract at the proposed length. A length assignment applies it before removing elements. A failure throws a TypeError exception. It neither removes a surviving location nor establishes a new reference invalidation event. Existing value initialization, descriptor, write and liveness obligations still apply; preserving contracts alone does not establish initialized values in newly exposed positions.

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 min(i - p, s - 1), where s is the suffix width. This read union is not a writable backing contract and does not widen an invariant reference. A statically expanded fixed spread can establish a literal length; an unresolved spread cannot.

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 2.10. These admission requirements do not change the restrictions on moving values between differently typed tuple positions in 1.14.10.

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 typed Array elements are own data locations, including elements whose declared type is any and tuple positions. An indexed accessor descriptor cannot be installed in that backing. After ordinary property-key and descriptor conversion, retain the existing fixed-extent and tuple-domain judgments and the earlier ordinary nonwritable-length refusal for growth. At the reached indexed storage check, an accessor descriptor throws a TypeError exception, whether the element exists, whether its current descriptor is configurable, and whether the operation was called through Object.defineProperty or Reflect.defineProperty. This refusal leaves the current descriptor, value, length, capacity and location identity unchanged. Ordinary generic/data descriptor updates and non-index properties retain their existing rules; Object.defineProperties retains earlier completed definitions before a later refusal. Independently acquired object-property contracts still constrain definitions, including on otherwise untyped Arrays, and their abrupt completions are propagated.

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 accessor properties and non-index Array properties remain borrowable through ordinary Get and Set. A failed accessor replacement does not itself invalidate a reference; independently completed removal or relocation retains its own liveness effects.

A store to a typed field or a typed element checks the value against the field's or element's declared type, by Table 23. Deleting a typed field, a typed element, or a member required by an implemented interface is a type error. Where the receiver type and property key statically establish that the deletion targets such storage, the source is rejected before evaluation under 1.2.8. Otherwise the attempted deletion throws a TypeError exception.

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 finite set of property keys, using String values and actual Symbol identities. It is a type error when every executing receiver/key alternative establishes protected storage. An unknown key or receiver, or a legal fixed-container nonposition, defeats this all-invalid proof. Do not apply the bounds judgment for a read to the operand of 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 typed positions. A receiver or key whose type does not settle the judgment retains the runtime check. The prohibition on deleting typed storage does not change the rules for resizing an array or for the liveness of references into it.

The statically protected index domain includes each canonical array index of a growable homogeneous typed array, every written fixed tuple position including one with a default, and the tail of a tuple with a proved trailing unbounded array rest. Current occupancy is not queried: deletion at such an index is a type error even when a growable array or its trailing rest is currently empty. A fixed, nested or non-final rest does not by itself prove an unbounded domain; unresolved domains and keys retain runtime checking. Noncanonical string properties and fixed tuple nonpositions remain outside this domain. Union and optional-deletion rules continue to combine the executing alternatives, and neither resizing nor reference liveness changes.

A class whose fields are typed may opt out with the dynamic modifier, which leaves it unsealed. Such a class cannot be used for sequential data and cannot be a value type in a typed array.

Note 2

Sealing is what makes a typed field's type a fact about the layout rather than a claim about the initializer. Without it a typed class would be a class whose fields happen to have been assigned values of the right type so far, which is not something an engine can lay out contiguously or an offset can be computed from. The memory layout extension depends on this, and it is the reason sealing is automatic rather than requested: a field's type and a reallocatable object are not compatible, so the type implies the seal.

1.7.11 The instanceof Operator

A Type Object has a %Symbol.hasInstance% method. When called with argument v, it returns ? IsOfType(v, the Type Object's [[TypeRecord]]).

Note

instanceof therefore extends to every type of this proposal through the protocol the language already has, and needs no new operator. a instanceof uint8 is true for a uint8 binding and a instanceof uint16 is false, a fixed-length array is NOT an instance of the corresponding dynamic-length array type — the extents must agree (1.4.2.7), and Span.<T> is the type that accepts either — and a class is unaffected, since a class's type object is its constructor and the prototype chain semantics apply as they do today. A GENERIC class's specializations are distinct class objects whose prototype chains do not pass through the declaration's prototype, so for x instanceof C where C is the constructor of a generic class declaration, OrdinaryHasInstance is extended: where the ordinary walk returns false, the result is true if some object on x's prototype chain is the prototype of a specialization of that declaration. The declaration is the family: 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 true, while instanceof Box.<uint8> is the ordinary walk against that specialization's prototype. A successful check narrows the static type in the branch it guards, which 1.8.5 specifies.

1.7.12 Typed catch Clauses

A catch clause may name the type it handles, and a try may then carry more than one. The productions below restate their ECMAScript definitions (1.1).

TryStatement[Yield, Await, Return] : try Block[?Yield, ?Await, ?Return] Catch[?Yield, ?Await, ?Return] try Block[?Yield, ?Await, ?Return] CatchClauses[?Yield, ?Await, ?Return] try Block[?Yield, ?Await, ?Return] Finally[?Yield, ?Await, ?Return] try Block[?Yield, ?Await, ?Return] Catch[?Yield, ?Await, ?Return] Finally[?Yield, ?Await, ?Return] try Block[?Yield, ?Await, ?Return] CatchClauses[?Yield, ?Await, ?Return] Finally[?Yield, ?Await, ?Return] CatchClauses[Yield, Await, Return] : Catch[?Yield, ?Await, ?Return] CatchClauses[?Yield, ?Await, ?Return] Catch[?Yield, ?Await, ?Return] Catch[Yield, Await, Return] : catch ( CatchParameter[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt ) Block[?Yield, ?Await, ?Return] catch Block[?Yield, ?Await, ?Return]

It is a type error if a Catch other than the last of its CatchClauses has no TypeAnnotation. When a value v is thrown, the clauses are tried in order, and the first runs for which IsOfType(v, its declared type) is true; a Catch with no annotation satisfies every value. The test is membership and not RequireType (1.7.7.1), since a clause binds the thrown value itself and does not convert it: accepting a Number at a numeric type or any value at string by conversion would bind a value that is not of the declared type. It is a type error if a Catch can never run: where it follows a Catch whose annotation denotes any, or where its declared type is a subtype of the union of the declared types of the Catch clauses before it, since IsOfType holds of a value at every supertype of a type it holds at, so an earlier clause accepts every value this one would. This is the rule that an untyped Catch must be last, extended to the typed clauses it has the same reason for. The annotation remains the catch binding's declared store type. On entry, narrow its incoming value by removing the stable members excluded by the failure of all preceding filters, using NarrowFrom and the membership-stability rules of 1.8.5. For example, after 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 set of values thrown by the try body. A value no clause accepts propagates as if the try had no catch: typed clauses filter, they do not swallow, so the completion is rethrown after Finally, when present, runs as it does today.

A destructuring CatchParameter is checked as a binding pattern in the clause's own lexical scope. Its declared filter, where known, supplies the source Static Type for the pattern's contributions and typed bindings. Without a known filter the source is unknown. Independently written member annotations, defaults, rest annotations and pattern source-eligibility judgments apply even when the handler or default is never selected. This judgment does not infer the set of values thrown by the try body and does not convert the filtered value. Ordered dispatch, property and iterator effects, reference decay and liveness are unchanged.

Note

This is the design's error handling document made normative: ordered dispatch, first match, an untyped tail for the rest. A rejected promise reaches a typed clause through 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 proposal itself raises, RangeError at a checked numeric conversion and at an out-of-bounds access, TypeError at a dynamic boundary, are ordinary values here, so catch (e: RangeError) handles every range failure in one clause (design, errorhandling.md).

1.8 Declarations

This clause gives meaning to the annotations the grammar attached to bindings in 1.4.4. It specifies what a typed binding holds before it is assigned, what a typed initializer infers, what an assignment to a typed binding does, the declaration that names a type, and the narrowing by which a test refines the type of a binding in the branch it guards.

1.8.1 Type Alias Declarations

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 denotes: an assignment to it throws a TypeError. An alias a previously evaluated Script published is known before a later Script is parsed, and it is judged in that later Script's static phase as an alias of the same Script is, a generic alias applied to arguments included.

Declaration[Yield, Await] : HoistableDeclaration[?Yield, ?Await, ~Default] ClassDeclaration[?Yield, ?Await, ~Default] LexicalDeclaration[+In, ?Yield, ?Await] TypeAliasDeclaration[?Yield, ?Await] TypeAliasDeclaration[Yield, Await] : type [no LineTerminator here] BindingIdentifier[?Yield, ?Await] TypeParameters[?Yield, ?Await]opt = Type[?Yield, ?Await] WhereClauses[?Yield, ?Await]opt ;

A type alias declaration with no TypeParameters creates an immutable binding for its BindingIdentifier, initialized to GetTypeObject of the Type Record its Type denotes. The name is therefore usable in expression position, where it evaluates to that Type Object, and in type position, where it denotes the type. A declaration with TypeParameters creates no binding and is usable only in type position, since a generic alias denotes a type for each list of arguments rather than one type.

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 Realm as well as within a Script or Module.

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 1.6.2.10; neither publication nor successful type resolution proves reference liveness or permits an otherwise invalid read.

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 captured generic bindings on which its expansion depends. Resolve written type arguments at their use site. Bind omitted defaults and check parameter constraints in the alias's declaration environment, with preceding parameter bindings available in declaration order, and evaluate the selected alias definition in that environment with the resulting bindings. A same-named binding or generic parameter of the consumer does not replace a capture. An available closed generic alias application is not left unresolved solely because the dependency is generic; apply its result to the retained checking obligation. An application requiring an unavailable runtime input remains subject to the existing deferral rule.

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 captured binding. This does not create an ordinary binding for the generic alias and does not add nominal identity to the expanded type. Reuse of an expansion remains subject to 1.6.2.10. Restore temporary declaration environments and generic parameter bindings after expansion, including abrupt completion, deferral and budget exhaustion; successful expansion supplies no initialization or reference-liveness permission.

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 1.3.8. Preserve these requirements for ordinary deferred annotations, computed result obligations, trial specialization, successive Scripts and dynamically constructed function sources. A temporary checking environment and its completed results supply no initialization or reference-liveness permission.

Note 1

A Type Object may escape through an ordinary return, stored value or export. Its value and ordinary type identity remain available through that value; the alias's local name does not thereby enter another scope. Implementations may retain declaration provenance beside an environment binding or recover equivalent evidence from a binding's Type Object. No realm-wide name registry, particular cache representation, additional property on a user object or retention of a dead environment is required.

Note 2

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 true, because Type Objects are interned (1.2.2.9). What the declaration adds over the 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 enum.

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 field of a sealed class. It is a type error if a cycle never does, since the type would demand an infinite inline layout, which is the same rule 1.11.2 applies to a value type class containing itself. The fixpoint of 1.2.7.3 validates a computed cycle by this rule.

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, denotes a recursive edge to the type being formed rather than requiring another expansion. Preserve captured binding instances, selected definitions and the ordinary argument-binding rules. Identifier spelling or declaration syntax alone does not establish this equality. An internal formation identity does not make an expanded alias nominal.

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 Type Object. Complete the required graph and obligations before publication, then apply the recursive-validity judgment to every cycle. A chain consisting only of alias forwarding denotes no type; temporary nodes do not give it an empty object type. Growing applications and required computations remain subject to 1.2.7.4. Resource exhaustion is distinct from a proved invalid cycle and does not supply a completed result.

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 host interruption. A failed formation, another lexical activation or another Agent supplies no completed graph for this application. These requirements extend 1.3.5 and do not require a particular graph or continuation representation.

Note 3

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 (1.4.2.9) even though a read of either yields the union with undefined. A recursive type therefore writes { next: L | null } rather than { next?: L }, and the second is a type error naming the field it reaches itself through.

The [no LineTerminator here] restriction is what keeps type a contextual keyword. 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 design relies on throughout, and it is why 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.

1.8.2 Typed Bindings

var, let, and const accept the same annotations. A binding declared with a TypeAnnotation has the annotated type; a binding declared without one has the 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 Initializer is initialized to the value of the initializer, checked against the declared type by the rules of 1.6: the check is static where the initializer's type is known, and is RequireType where the initializer has the any type. A binding declared with a type and no Initializer is initialized to the default value of that type (1.8.3) rather than to undefined.

A const declaration without an Initializer is a Syntax Error where the binding carries no TypeAnnotation, as it is in ECMA-262: the Early Errors of LexicalDeclaration are amended so that the rule "it is a Syntax Error if Initializer is not present and IsConstantDeclaration of the LexicalDeclaration containing this production is true" does not apply to a LexicalBinding whose BindingIdentifier carries one. Such a binding is initialized to the default value of its type, exactly as a let binding of that type is, and it is a type error where that type has no default (1.8.3.1).

Note 1

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 typed binding takes its type's default; a 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 TypeError. It has never been a promise that the value is frozen — 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 ReferenceError.

A ref binding is a separate rule and keeps its Initializer requirement (2.5): a 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 ReferenceError. The default value applies at the declaration, not before it.

Note 2

That a typed binding takes its type's default rather than undefined is the point at which a type stops being a claim about a program and becomes a fact about storage. 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 undefined until assigned, its type would be uint32 | undefined in all but name, and the layout would have to carry a bit saying which. This is the same reason a typed field takes its type's default, and the two rules are one rule.

1.8.3 Default Values

1.8.3.1 DefaultValueOf ( t )

The abstract operation DefaultValueOf takes argument t (a Type Record) and returns either a value of the type t or none. It returns the value a binding or field of the type t holds before it is assigned, or none if t has no default. It performs the following steps when called:

  1. If t.[[Kind]] is nominal and t.[[Declaration]] has a declared zero, then
    1. Let z be the declared zero of t.[[Declaration]].
    2. Return a copy of z.
  2. If t.[[Kind]] is any, return undefined.
  3. If t is a numeric type, return the value of t representing 0.
  4. If t is the String type, return the empty String.
  5. If t is the Boolean type, return false.
  6. If t is the type named "undefined", return undefined.
  7. If t is the type named "null", return null.
  8. If t.[[Kind]] is union, then
    1. If t.[[Members]] contains the type named "null", return null.
    2. If t.[[Members]] contains the type named "undefined", return undefined.
    3. Return none.
  9. If t.[[Kind]] is array, then
    1. If t.[[Extent]] is dynamic, return a new empty array of the type t.
    2. If t.[[Extent]] is 0, return a new empty array of the type t.
    3. Let values be a new empty List.
    4. Repeat t.[[Extent]] times:
      1. Let d be DefaultValueOf(t.[[Element]]).
      2. If d is none, return none.
      3. Append d to values.
    5. Return a new array of the type t whose elements, in order, are values, with value-type copying as required by 1.11.4.
  10. If t.[[Kind]] is tuple, then
    1. If t has an established tuple sequence domain involving finite or nested rest expansion (1.14.1), then
      1. Let values be a new empty List.
      2. For each fixed position of that domain, with any unbounded run empty, in order, do
        1. Let d be the position's formed declared default if present, and DefaultValueOf(the position's type) otherwise.
        2. If d is none, return none.
        3. Append d to values.
      3. Return a new tuple of the type t whose elements, in order, are values, with value-type copying as required by 1.11.4 and its complete positional storage domain.
    2. Let values be a new empty List.
    3. For each element e of t.[[Elements]] whose [[Rest]] is false, in order, do
      1. Let d be none.
      2. If e.[[Initial]] is none, set d to DefaultValueOf(e.[[Type]]).
      3. Otherwise, set d to its [[Initial]] converted at the position's type by Table 23.
      4. If d is none, return none.
      5. Append d to values.
    4. Return a new tuple of the type t whose elements, in order, are values, with value-type copying as required by 1.11.4.
  11. If t.[[Kind]] is parameterized, then
    1. Let d be DefaultValueOf(t.[[Base]]).
    2. If d is none, return none.
    3. Let e be a new empty Object, which is the metadata of a value of t.[[Base]] that carries none.
    4. Let crossed be Completion(ConvertParameterization(d, e, t.[[Metadata]])).
    5. If crossed is an abrupt completion, return none.
    6. Return crossed.[[Value]].
  12. If t.[[Kind]] is literal, return t.[[Value]].
  13. If t.[[Kind]] is nominal and t denotes a value type class, return the instance of t each of whose fields, public and private, holds the default of the field's type as that type stands for t, or none if any field's type has no default.
  14. Return none.

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 element type is reached through a type parameter rather than written, since a generic body is checked once for every instantiation and a declaration of [0].<_T_> must not become unwritable at the instantiations whose argument has no default.

It is a type error to declare a binding or a field with a type t and no initializer when DefaultValueOf(t) is none.

A PRIVATE field is filled like a public one. Its participation is the same as its participation in the layout — "private fields participate in the memory layout exactly as public fields do, which is why the value type rule counts both" — so a class whose only field is private has a default, and only the absence of a TYPE disqualifies a field, never the kind of its name.

For a GENERIC class the field types are those of the APPLICATION, not of the declaration. G<4> whose field is declared [N].<uint8> has a field of type [4].<uint8>, so its default is a four-element zero-filled array; the declaration's own field type, whose extent is still the parameter, has no default and would otherwise leave every application of every such class without one.

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 (6.3), so that 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 meta type whose validate refuses the base's zero leaves the parameterization with no default, since the crossing throws and the step reports none: 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 unit type zero-fillable: Velocity is a float64 whose dimensions meta type defines no validate, so the cast admits the zero and let _v_: Velocity; holds 0 m/s.

The meta types and casts declared in the surrounding realm are those declared as of the operation's application, as they are for the metadata subtype judgment and for the same reason: a parameterization may be written above the declarations that govern it, so the checking pass processes a source text's meta declarations and its primitive blocks' implicit cast operators before applying this operation (1.2.8), and whether a declaration needs an initializer is answered against those. Two call sites fix the realm rather than taking the current one, because in each the value is produced for a program that is not the one asking. An unwritten ThreadLocal read (43) takes the default in the realm in which the ThreadLocal was created, so that two agents reading one storage read one value. Reflect.defineProperty with a type and no value (1.12.7) takes it in the realm of the Type Object it was given, which is the only call site at which a program chooses the moment.

Every default above is a zero, or, for a type with exactly one value, that value: the numeric zero, the empty String, false, null, the one value of a literal type, and an array or an aggregate whose storage is zero-filled. A literal type is the one case where a default is not chosen but forced — there is nothing to prefer a zero over, since no other value is of the type — and it is the reason the phrase is not simply "a zero". It takes nothing away from the fill below: a class holding a field of literal type has no layout at all, so no storage is described by it, and where a layout exists every default in it is a zero. Zero-filling is part of the semantics and not an optimization a program may opt out of, and no form of this proposal allocates without it. What is filled for an aggregate is its layout (9), padding included, so two instances of one value type class that are === field by field are also equal byte for byte.

An aggregate has a default exactly where its parts do: a value type class where every field's type does, a fixed-extent array where its element's type does, and a tuple where every position it fills does. A parameterized field is the case to watch, since the step above gives a parameterization a default only where its base's zero can cross into it. A Vector3 of three Meter fields is zero-fillable exactly where float64 declares the cast into the dimensions meta type, and let _d_: [10].<Vector3>; is a type error where it does not — not because a unit has no zero, but because nothing in that program says how a bare zero becomes one. Writing that cast is what gives a design's unit types their zeros, and with it the aggregates holding them.

Read the other way, the same rule is how a type says that no zero of it is meaningful. A class that needs its constructor to run states that by holding a field whose type has no default (1.11.2), and a brand into which no cast is declared is such a type: its values arrive only by construction, through the operators its base declares (6.5), so an aggregate holding one has no zero-filled form and says so at the declaration rather than at the first use.

Note

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 design states the defaults for the numeric, SIMD, String, Boolean, nullable, and array types and does not reach the rest, so the boundaries above are supplied here and need review. Three deserve attention. A 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 1.14.10 reconciles. A value type class defaults field-wise, which is what makes 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 constructor running, which 1.11.2 endorses.

1.8.4 Typed Initializers

A binding declared with a TypedInitializer takes the type of its initializer rather than the any type. Its declared type is the Static Type of the AssignmentExpression, and it is initialized to that expression's value.

Note

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 type error if the Static Type of the initializer is the 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.

1.8.5 Narrowing

A test that establishes something about a value's type refines the Static Type of that value in the branch the test guards. The Static Type of a reference to a binding at a given point is its declared type refined by the narrowing facts that hold there.

The narrowing facts that hold at a point are the join of the facts on every path of control reaching it. A path through a statement that cannot complete normally (27) contributes none, and where several paths reach a point the Static Type of a path there is the union of its Static Types on those paths, a path no fact narrows contributing its type before the test. So the statements after an 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 path the 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 participates in typed operand judgments. Use the original declaration's contract for participation and the current narrowed Static Type for the judgment itself. Thus a typed nullable binding narrowed to null cannot support an ordinary property operation, and a typed union narrowed to a Symbol cannot support numeric conversion. A narrowing fact introduces no lexical declaration or annotation: genuine shadowing, explicit any and mutation invalidation retain their ordinary effects.

Table 24: Narrowing Forms
FormWhere trueWhere false
v instanceof TNarrowTo(s, T)NarrowFrom(s, T)
#f in v, a brand checkNarrowTo(s, the class declaring #f)NarrowFrom(s, the class declaring #f)
v.length === k or v.length !== k, with k a compile-time constant, where every member of s is a fixed-shape sequence: a fixed-extent array, a tuple whose positions are all required with no rest, or a literal String type, whose exact length is its UTF-16 length (1.4.2.7)The members whose exact length is kThe other members
k in v, with k a String or Number literal. Classify each member independently: always present for established typed storage (1.7.10), such as a typed class field or an index within a fixed shape; an index past a fixed shape is unknown, not absent, since in follows the prototype chain, which a program may extend; unknown otherwise. A receiver still under construction supplies no typed-field presence proofMembers not proved always to lack kMembers 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 topic (41)Every row of this table, in the positions the test governs. The topic is a binding, so a test on it narrows it as a test on any reference does — which is a second reason it is a binding and not a substitution, since a substitution has no identity to narrow.
v is T (5.2), and generally v is P for a pattern P (26.4)MatchNarrow(s, P)MissNarrow(s, P)
v === e or v !== e, where e names a literal type L (below)NarrowTo(s, L)NarrowFrom(s, L)
v === w, for established built-in strict equality over known primitive value domainsFor each operand, the portion of its captured domain compatible with an equal value in the other operand's captured domain; publish only to a still-preserved placeNo additional domain exclusion in general, except that the edge is empty where both captured domains are the same singleton; an object, array, function, arrow, class or regular-expression literal, and a new of a class binding the program never assigns whose constructor returns no value, has a fresh identity no other operand's domain contains, so its equal edge is empty; !== exchanges these edges
v.p === e or v.p !== e, where e names a literal type L, including a computed access naming the same established constant String key, where s is a unionNarrowTo(s, the union of the members of s whose type at 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 Static Type of v is a parameterized type, and e is a compile-time constant cNarrowTo(s, s with its metadata replaced by NarrowMetadata(s's metadata, op, c))NarrowTo(s, s with its metadata replaced by NarrowMetadata(s's metadata, the negation of op, c))
A call of a signature whose [[Narrows]] holds a Narrowing Record for the position v is passed in, where the signature's [[Return]] is booleanNarrowTo(s, that Record's [[Type]])NarrowFrom(s, that Record's [[Type]])
The same, where the signature's [[Return]] is voidNarrowTo(s, that Record's [[Type]]) in every position the call dominates, rather than in a branch (8.2). Where that is empty the call can never return normally, and every position it dominates is dead.
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 rowNarrowTo(s, the union of the types whose typeof is k)NarrowFrom(s, that union)
typeof v === typeof w, and the corresponding nonliteral switch labelEach preserved source's members whose tags occur in the other captured source domainExclude 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 1.8.5.1, preserving SameValue distinctions
v == null or v != null, and the nullish operators ??, ??=, and ?.The nullish branch: NarrowTo(s, null | undefined)The present branch: NarrowFrom(s, null | undefined)
v as the test itselfThe truthy part of s (1.9.5)The falsy part of s
A switch on a value of an enum type or a sealed class hierarchyIn each case, NarrowTo(s', the case's type), where s' is s with the types of the cases before it removedIn the default, s with every case's type removed
A case e of any other switch (v), where e names a literal type L: the v === e row, applied to the labelIn the statements the label reaches, NarrowTo(s, L), joined over every label that falls through to themIn 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 domainsThe equal-edge domains of the v === w row, projected only onto preserved discriminant and label origins, then joined with incoming fallthroughNo whole-domain exclusion merely from a failed comparison; retain effects of evaluating the label
A match clause when P (26)In the arm, MatchNarrow(s, P)In later clauses, MissNarrow(s, P) where the clause is unguarded

v above is a reference to a binding or a chain of property accesses over one, a path, and s is its Static Type where the test occurs. A path names the SAME PLACE however often it is evaluated, which bounds what may be one: a chain of plain names from a binding or 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 path, since two evaluations of 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 path narrows nothing rather than narrowing unsoundly; its test is still a narrowing form, and where it decides a branch 1.8.5.4 judges it with s the Static Type of the expression. A narrowing fact holds in any position the test governs, not only in the body of an 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 Expression of a MatchGuard, which decides whether the arm runs once the pattern has matched and whose facts hold in the arm, in a case label of a switch whose captured discriminant has an established singleton Boolean value (below), and in the positions nested in any of these that decide the same branch: the right operand of && 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 path holds until the path or a prefix of it is assigned a value whose type is not within the narrowed type. == 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 design's 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 typed-storage presence judgment. A broad or multiple-key domain and a structural property annotation alone establish no guaranteed presence. Parentheses around an instanceof constructor preserve its identity; an immutable constructor alias may supply the same nominal implication when its captured constructor and the applicable instance-check semantics are established. A callable signature, unknown constructor selection, or custom or replaced 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 NoSubstitutionTemplate, which is the String literal it spells — and L is its literal type; where it is a reference to 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 enum, so that two enumerators of one enum are disjoint exactly where their values differ and names sharing one value are one type (1.11.8). No other operand names one: an expression whose value merely folds to a constant does not, so which comparisons narrow is visible in the source.

Where a switch's captured discriminant has an established singleton Boolean value, from a Boolean literal, an operand naming that literal type, or its current narrowed Static Type, each case label is a test deciding whether its clause is entered, read as the label's own test for the captured value true and as its negation for false. A discriminant whose type admits both Boolean values supplies neither proof. Evaluate and capture the discriminant once; label effects do not change the captured value. Its facts hold in the statements it reaches unless an earlier clause can fall into them, and the labels after it, which are tested only where it failed, see its negation, as the default does where no clause falls into it.

The types whose typeof is "object" include every array and tuple type, the type of a typed class, and every vector, complex, rational and composite type, besides null; the types whose typeof is "function" include every function type. An object type is not among the types whose typeof is either, since a function with the required properties is a value of it; nor is an untyped class, whose constructor may return a function, nor the type of a global constructor, whose values are its instances by prototype chain, which a function can be given. A typeof test of a value of one of those can go either way.

Note 1

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.

Note 2

Facts reach paths because the types this proposal narrows are about fields as much as bindings, and if (addr.country == 'US') is the design's own example. Assignment to a prefix discards a fact about everything under it, which is the design's account of a 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 (Table 23).

1.8.5.1 Propagation of Narrowing Facts

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 checked code (1.2.5). Outside checked code the same facts are established without error. An ordered comparison (<, <=, >, >=) of a place whose type is an integer value type against a constant narrows the place, on each branch, to the interval of values of its type that satisfy the comparison or its negation; a later ordered comparison of the place whose outcome that interval settles is a test that can never succeed, or never fail, as an equality whose exclusion leaves nothing is. Each 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 path rooted in that binding; equal spellings in different scopes do not identify the same binding. A fact does not change the declared type of a store, the declaration's participation in typed operand judgments, or the validity of a reference under 2.10.

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 2.5. Preserve a fact only where the applicable origin and effect judgments establish that it is unaffected; an unrelated lexical cell or a same-spelled shadowing declaration is not itself a write to that location. The constness of a reference binding does not make its referent immutable. An ordinary copied value has its own binding contract.

A value read through a reference has the ordinary effects of accessing its referent, including possible accessor or proxy execution and captured writes. Apply those effects at the read's source position under 1.6.2.15; forming or forwarding a reference does not by itself read the referent. An earlier getter result does not prove the value of a later getter read. Computing these judgments executes no getter and proves neither initialization nor reference liveness.

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 captured-write effects, including writes through lexical aliases and captured rebinding. An observation of such a read supplies no current-value fact for a subsequent read, including through a stored predicate or an ordinary copy; the copy retains its own contract. Preserve unaffected facts and declared read contracts.

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 2.3. Reference return admission retains the specified membership check. Those operations can read the referent even when the callee's source body does not. Neither successful flow checking nor location formation extends a reference's lifetime.

An instance field initializer is a deferred callable body for these judgments, including when the class has no explicit constructor. Include its possible captured writes and reference effects wherever construction effects are transferred. Checking the initializer uses captured declaration contracts, not declaration-time facts about mutable captured values or reference locations. An established fact about an immutable ordinary binding may survive this boundary; constness alone proves nothing about object members, reference referents or mutable exported bindings. Restore the enclosing flow after checking the initializer. Declaring a class, including inside a loop or finalizer, does not execute its instance initializer. Computed names, decorators, static initializers and static blocks retain their specified evaluation contexts and effects.

Note 1

Effect timing is separate from the classification in 1.2.5. Proposal syntax inside a nested class does not by itself make the enclosing Module checked. A later annotation in that Module's own syntax does make the entire Module checked, including preceding guards. Importing a typed value alone does not change this classification. These rules change neither runtime initialization order nor the effects retained after an initializer throws.

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 captured reference's location when a callable is declared does not establish its location in a later invocation. An operation that may execute a captured rebinding revokes the affected location proof. These requirements mandate neither whole-program points-to analysis nor a particular graph representation; uncertain locations retain the existing conservative effects and runtime obligations.

Example 1 (Informative)

In checked code, the right comparison below cannot succeed: its predecessor has established that the unchanged direct referent is undefined. The required diagnostic is independent of the fact that the local was initially unannotated. Removing the left comparison leaves no such established fact. Replacing the direct location with an accessor whose successive reads can differ removes the preservation proof.

let x;
let ref r = x;
if (r !== undefined || typeof r !== "undefined") {}
Example 2 (Informative)

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 continue completions. A break or continue completion identifies its target statement. Sequential evaluation passes only normal completions to the next position. At an alternative join, retain a fact only if every incoming completion supplies it, joining its possible types with CanonicalizeType of their union. A predecessor with no fact is unknown, not unreachable. An established impossible true or false result contributes no predecessor to that result join; keep result feasibility separate from the facts on a reachable result. Established truthiness may exclude an opposite result without making a literal idiom an error. Unreachable source retains its ordinary syntax and type checks but supplies no outgoing completion. Leaving a lexical scope removes its local bindings and retains facts about surviving bindings. Checking a nested function does not execute it or export its completion facts to the enclosing evaluation.

The following transfers apply to the narrowing forms in Table 24, including declared predicates and resolved metadata narrowings. They also apply when computing a conditional arm's Static Type, rather than only when diagnosing a test.

  • Parentheses preserve both result environments and built-in logical negation exchanges them. A declared logical-not operator uses its selected result contract instead; it need not negate the receiver's truthiness. For 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.
  • A comma expression evaluates its operands in order and takes both result environments from its last operand. A conditional expression evaluates its condition, evaluates each arm under the corresponding condition environment, and separately joins the arms' true results and their false results. An if statement joins only the normal completions of its arms; an omitted arm supplies its incoming environment.
  • The right operand of ?? 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 captured callee value does not establish that its binding still holds that value. A logical assignment checks its store against the declared destination type and invalidates affected facts when that store occurs.
  • A while or for test's true environment enters its body. A for update joins the body's normal completions and the continues targeting that loop. A do-while test has the same incoming completions from its body. Returns, throws and breaks do not enter those positions. The normal successor of a loop joins the false test completion, where present, with the breaks targeting that loop. An inner loop or switch's break is not an exit from an outer loop. A for-in, for-of or for-await-of loop also admits the path on which iteration ends without entering its body.
  • Apply loop widening before reasoning about the repeated positions: discard incoming facts about bindings the loop can write and, where it contains a call, bindings subject to captured writes or other call effects under 1.6.2.15. The loop's own tests can establish new facts. This rule requires neither a fixed point over iterations nor a proof of termination. An omitted for test or a literal true loop test supplies no false exit.
  • Switch label selection and clause execution are separate flows. Evaluate labels in their selection order, passing failed comparisons to the next label. The default's selection environment follows every failed label, including labels textually after the default. A clause body joins its selected entry with normal fallthrough from the preceding body. Case-block lexical declarations retain their ordinary shared scope and temporal dead zone; an unrelated declaration does not disable a discriminant fact. A typeof switch refines the original operand, retaining every type with the selected tag. A singleton Boolean switch uses the label's corresponding result environment. A singleton Boolean label can likewise select the corresponding result environment of a captured Boolean discriminant, including an original numeric predicate, an admitted Object.is test, or a preserved predicate alias. Capture that result once and apply subsequent label effects to its source relations before selection. A label that changes the discriminant binding cannot refine its new value from a comparison with the captured discriminant. Switch exits join normal fallthrough, breaks targeting the switch, and any unmatched path admitted by its existing exhaustiveness rules.
  • A labelled statement consumes only breaks naming that label. A try statement joins normal completion of its body with normal completion of its handlers. Handler entry conservatively admits exceptions from expressions and cleanup, widening facts that the try body may invalidate; a typed catch binding keeps its declared filter as its store type and receives the residual entry fact specified in 1.7.12. A finally block is checked under the join of the incoming completion environments, including possible implicit exceptions. Its normal completion preserves each incoming completion's kind and target, carrying forward facts it did not invalidate and applying the facts its normal completion establishes. An abrupt finalizer completion replaces the incoming completion. A finalizer write or call can therefore invalidate a fact on a pending break, continue or return.
  • A match subject that is a stable place retains its lexical binding identity through parentheses and property paths. A selected clause receives MatchNarrow of the remaining captured subject type and its pattern, including a scalar literal subset of a broader primitive type. Apply the existing numeric literal adoption rules before constructing that subset; do not choose an arbitrary numeric domain. An unrepresentable Type Record complement remains conservative; retain the internal scalar exclusions specified below. Publish a captured-subject fact about the current place only while the place and its prefixes still denote that captured subject. For ordinary match, the next clause joins the pattern-miss environment with the matched-pattern, guard-false environment. An irrefutable pattern supplies no miss predecessor. The arm receives the guard-true environment. Project away clause-local bindings at each exit, and process pattern and guard effects before joining. Alternative arm bodies do not execute one another; join their normal completions at the result. The existing match-all selection and exhaustiveness rules remain separate.
  • When an ordinary do expression supplies a test result, associate the true and false environments with each normal completion value producer identified by 39.2, including values carried by UpdateEmpty and breaks consumed within the expression. Its producers are deciding positions to the extent the enclosing result decides a branch. Separately join environments for truthy and falsy normal results; a completion with no value contributes the undefined result. Apply effects occurring after a producer to the facts attached to its captured result, including effects of a normally completing finally block. An abrupt completion escaping the expression contributes no result environment. Remove local bindings when leaving their scopes. Unknown completion sources remain conservative. The generator forms of do expressions do not use this transfer.
  • For a tested coalescing expression, distinguish operand selection from result truthiness. Partition the captured present-left value by its truthy and falsy parts, publishing those parts to its preserved origin; a Boolean present value therefore separates into true and false. Join the truthy results of the present-left path and the nullish-right path for its true environment, and the falsy results of those paths for its false environment. An alternative with established opposite truthiness contributes no predecessor to that result. Apply right-operand effects before propagating its result facts. Bare-place tests use the truthy and falsy parts in Table 24.
  • A built-in strict equality or inequality comparison between a Boolean-valued expression and an established singleton Boolean value composes the expression's result environments, exchanging them for equality with false or inequality with true. Either operand order is admitted. Evaluate both operands once in source order, applying later effects before exporting facts about an earlier captured result. Unknown comparison values, coercing comparisons, and user-defined operations do not establish this correspondence.
  • When an assignment expression supplies a test result, propagate the established result producer's true and false environments through destination evaluation, conversions, and store effects in their specified order. Partition the actual normal result by ToBoolean, including a non-Boolean result. An established identity-preserving assignment result carries the captured right operand's truth environments through the store. A fact about the current source survives only if destination evaluation, conversion and store effects preserve that source identity and the established result correspondence. A setter can replace the source while the assignment still returns the captured right operand; do not reread the destination or source to obtain that result. For logical assignment, separately join skipped and stored alternatives by the truth of the resulting value; a skipped alternative supplies no fact established only by its right operand. Check the declared destination type as usual. This transfer concerns the tested expression's result, not an unconditional fact about its destination at later reads. An unresolved store, accessor, conversion, or selected operation supplies no unsupported preservation proof.
  • When an optional chain supplies a test result, each skipped nullish alternative contributes its undefined result only to the false environment. Separately join the true and false results of evaluated alternatives, including a selected predicate's argument facts under the declared-call rules. This transfer does not infer a predicate contract for an untyped callee binding. The false environment may include a present receiver producing a falsy value. Refine a stable receiver or argument place only while it still denotes its captured value after key, argument, getter, and call effects; the optional expression itself does not become a path.
  • Strict comparison and switch selection can also consume an optional result's alternatives. A skipped link contributes 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 captured places survive all subsequent operand and label effects. A repeated accessor read need not reproduce its captured value; an open structural property contract alone supplies no such preservation proof. o?.tag === "a" can therefore establish presence, but o?.tag !== "a" and o?.tag === undefined do not generally establish it. Evaluate each receiver, key, getter, argument and label once. Switch body entry still joins selection with fallthrough. These alternatives may be transformed by the established 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.
  • For switch selection from an optional result, carry its remaining evaluated and skipped alternatives from each failed comparison to the next label. If a label has an established singleton value, remove from the failed-selection alternatives every established singleton result that compares equal to it by the admitted built-in strict equality, retaining every other possible result. Strict equality identifies the two signs of zero within the same numeric type and never matches NaN; mathematical equality across unrelated numeric types is not a proof of selection. Apply the existing literal-adoption rules when establishing the operands' value domains. A broad primitive domain is not exhausted by removing one of its values. A non-singleton label supplies no whole-domain exclusion merely because its type admits the discriminant's type. When no evaluated result remains, remove the evaluated alternative; when the skipped result is excluded, remove the skipped alternative. Later labels and the default receive only the remaining alternatives, after their reached label effects. Join selected entry with normal fallthrough before checking a clause body. A present result of 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.
  • For built-in strict equality between two known primitive domains, retain on the equal edge each operand's members that admit an equal value from the other's captured domain. Numeric compatibility follows strict value identity and the existing literal-adoption rules, not mathematical equality across unrelated numeric types or a nominal type intersection. Capture each operand at its evaluation point; later writes, getters and calls can prevent projecting a captured domain onto its current place. Projection requires an established data value; a binding or proved data slot can supply this identity, but an open structural property contract alone cannot rule out a varying accessor result. Apply the same equal-edge transfer to an ordinary non-singleton switch label, using the captured discriminant and the value produced by evaluating that label. Selection is joined with incoming fallthrough before checking the body. The unequal edge supplies no whole-domain exclusion in general. Unknown domains, unresolved metadata and coercing operations supply no such proof. Existing operator-admission rules remain in force.
  • When an ordinary match supplies a test result, separately join its selected arms' normal true and false results after pattern and guard effects. Block arms use the completion-value producer rules of an ordinary do expression, including UpdateEmpty and effects after a captured producer. Escaping abrupt completions contribute no result, and arm-local bindings are removed at the exit. The collection returned by match all does not use this scalar transfer.
  • When a pipeline supplies a test result, its body supplies the result environments under its own topic binding. Remove that binding at the pipeline exit and retain facts about surviving outer bindings. A topic may additionally carry the identity of its input stable place: publish a topic fact about that place only where the place and its prefixes still denote the captured input value and the existing membership-stability rules admit the fact. Calls and other non-path inputs supply no source-place identity. Nested topics have distinct identities. The topic remains a value binding; this transfer permits neither a reference to it nor an extension of reference liveness.
  • Entry to a for-in iteration establishes that its captured enumeration source is neither null nor undefined. Publish this fact about the current source place only while its identity survives loop widening, enumeration effects, iteration-target assignment, and lexical shadowing. A source that can change between iterations cannot be narrowed anew from its old captured value. In particular, enumeration callbacks may replace an aliased property or reference source; mere syntactic absence of an assignment to its spelling is not an identity proof. The successor still joins the zero-iteration path. The for-of and for-await-of protocols establish no such fact by this rule.

Parentheses around a switch discriminant preserve its selection rules and facts. A constant range label that admits no value of the captured discriminant's established numeric domain is an impossible test under 1.8.5.4. Range selection uses the member-wise MatchNarrow and MissNarrow rules of 26.3 for the current place while that place still denotes the captured subject. Keep the union of preceding proved constant intervals in selection order: a later range label is an impossible selection where none of its values in the captured numeric domain remain outside that union. Use exact established endpoints and domains, respecting open and closed endpoints and integer adjacency; unknown bounds contribute no coverage proof. Partial overlap alone is not impossibility. Clause execution still joins selected entry with fallthrough, so redundant selection does not make all statements beneath that label unreachable. These rules add neither range-based default rejection nor range coverage to switch exhaustiveness.

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 topic scope. They do not by themselves extend a pattern binding's governed scope. A producer used only as a value remains subject to the existing non-deciding-query rules.

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 captured destination. An unresolved result or conversion supplies no preservation proof. A successful participating initialization or simple store also establishes a new current-value fact where its post-conversion domain is proved. Complete dependent store facts under 1.6.2.15. Capture that result at its production point and apply destination, conversion and store effects before publishing it. For logical assignment, join the skipped and stored alternatives. The declared destination remains the store contract, including its callable, predicate and contextual-void contracts; do not replace it with a raw initializer signature. Ordinary untyped bindings do not acquire a contract by this rule. Unknown conversion results, accessor stores, or changed destination origins supply no new fact. Reference liveness is unchanged.

An immutable Boolean binding initialized from a recognized test retains that producer's established true and false implications under the following source-identity rules. Capture each implication's lexical subject and value identity at initialization; a later test of the Boolean replays an implication only while its subject still denotes that captured value. The Boolean's value is not reevaluated, and it grants no reference or lifetime. A mutable Boolean, a shadowing binding, a relevant write, an uncertain alias effect, or an invalid reference origin supplies no such replay. Preserve these identities through branch joins and loop widening; no fact may survive an unconstrained incoming predecessor.

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 captured value; a matching name in another lexical environment supplies no implication. Apply all initializer effects before retaining a relation, and do not recover a source relation already invalidated before the copy. Retain only the source implications established by the selected result: unrelated facts in the initializer's incoming environment do not become properties of the Boolean. The binding must retain a Boolean value under its ordinary participation and conversion rules; an explicit any view, mutable binding or reference alias establishes no immutable Boolean-copy relation by this rule. No implementation-selected alias-depth cutoff changes these required judgments for an established finite chain.

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 replacement type argument or store contract. Join proof domains over incoming paths and invalidate them on replacement or uncertain alias effects. An unknown initial bound alone supplies no verdict.

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 Type Record. Subsequent literal tests whose entire success set is excluded are impossible under the existing deciding-position rules; a 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 static iteration contribution guarantees no position or a range with constant endpoints has no interior. Retain an exclusion at a join only when every incoming path guarantees it, and discard affected exclusions on writes, unknown alias effects or loss of the subject's identity. Literal adoption and the actual equality operation determine the excluded values: strict equality identifies the signs of zero, while signed-zero patterns retain their distinct semantics; NaN and distinct numeric identities are not conflated. Coercion without an established equal-value mapping supplies no exclusion. These facts introduce no public complement Type Record, reflected type identity, arbitrary arithmetic relation or interval subtraction.

A call proved to select the original %Boolean% intrinsic carries the true and false result environments of its captured first argument. Evaluate the callee and arguments once in source order, applying the effects of later arguments before exporting facts about an earlier captured value. A participating argument whose truthiness is established supplies the corresponding Boolean result type; a value query is permitted, and the existing literal-idiom exceptions remain. Construction with 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 captured source place. Existing narrowing forms on either value propagate their evidence to the other only while both origins remain current. Replacing the source ends the relation to its current value, but does not invalidate the immutable snapshot's own evidence. Equal spellings, unknown incoming origins, reference alias effects, and unresolved conversions establish no relation. Joins and loop widening apply to the origin identities as to other facts. This is not general equality solving or a heap-alias analysis, and it preserves declared storage contracts and reference liveness.

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 denotes the captured value. A tag is a many-to-one classification: "number" retains all admitted numeric families, and "object" retains all admitted Object-tag alternatives, including null. Replacing the source or losing its identity ends the relation; the saved tag is never reevaluated. This rule infers no relationship for arbitrary String-returning functions.

For a preserved participating numeric value, established original non-coercing Number.isNaN, Number.isFinite, Number.isInteger and Number.isSafeInteger tests refine a finite internal partition: NaN, infinity, finite fractional values, unsafe integral values and safe integral values. Omit cells impossible for the established family. This partition applies to Number, binary float16/32/64 and sized integer domains; a numeric literal contributes its exact applicable cell. Finiteness selects the last three cells, integer selects the last two, and safe integer selects the last; a negative answer retains every other possible cell. For these supported floating domains, proved self-comparison of the same preserved data value produces and consumes the NaN category under its built-in equality semantics. Other domains retain established whole-member classification without assuming this partition: BigInt and complex predicate-admission rules, unresolved metadata and unknown intrinsic identities are unchanged. A join unions possible categories, and replacement or uncertain alias effects discard the affected evidence. Retain numeric-family identity and the public Type Record; these categories introduce neither new public scalar types nor arithmetic propagation. An equality with a numeric constant, and an equality or relational test of a value whose partition is NaN alone, read the partition as well, so such a test whose whole success set the partition excludes can never succeed and one whose whole failure set it excludes can never fail.

On the equal edge of a proved built-in strict primitive comparison, retain a relation between the two captured value origins as well as their initial compatible domains. Later narrowing forms may transfer only evidence invariant under that equality operation. For String and Boolean values this includes their literal domains and exclusions; for floating values it includes compatible numeric categories and truthiness, but not a particular zero sign. Success excludes NaN. A sign-specific zero exclusion or identity must not cross a strict-equality relation as if it were SameValue. Distinct numeric families, decimal cohorts and unresolved metadata retain their established equality semantics; an unproved mapping contributes no relation. Keep relations only on incoming paths that establish them and while both origins survive replacement, alias effects and suspension. Propagation is bounded by the source's captured relations and finite fact vocabulary; it infers no arbitrary arithmetic relation or heap equivalence.

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 proposal types: numeric-family collisions and the Object/null collision remain.

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. SameValue does not contextually adopt a Number argument into a sized numeric family. A broader representable domain is retained where finer identity cannot be expressed; decimal cohort distinctions are not discarded. Both this intrinsic and a later intrinsic consuming its evidence require independent origin proofs. Unknown calls, getters, replacement, argument mapping or conversion supply no unsupported relation.

For an immutable object binding pattern, apply the primitive-copy rule separately at each established data extraction. Capture the current member domain and evidence at its read point, check the binding conversion and retain a relation only where the copy preserves identity. Renaming does not change that identity. A later computed key, getter, default, rest collection or other effect can break the relation to the current source while leaving the earlier primitive snapshot's own evidence intact. Unknown accessors, alternative origins and unresolved conversions supply no relation. Nested object patterns apply the same rule to each admitted data path. Array binding patterns consume their selected iterator and acquire no indexed-source relation from this object-field rule. Participation, declared binding types and reference liveness are unchanged.

A successful structural type test or structural member pattern may project its established member evidence onto a corresponding stable data path. Admit only proved data storage, including typed data fields and immutable Composite members, whose identity survives every remaining observation in the test or pattern. Accessors, unknown extractors, refinements that may run code, and unknown iteration supply no such projection. Conjunction sequences evidence, while alternatives retain only evidence common to successful alternatives. Failure of a multi-property test does not establish which member failed. This projection changes neither the declared member store type nor the scope of any pattern binding.

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 captured left value. Reject a right operand only when the established answer skips it: a truthy left side of && 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 27; rejection and finalization remain separate paths.

Metadata comparison requests may identify a stable binding or property path through parentheses, with a constant on either side. Mirror the comparison operator when the constant is on the left. Use the current type of that place and carry the request's lexical identity and dependencies through the same joins and invalidations as other facts. Arbitrary expressions and unstable paths establish no place fact, and unresolved hooks retain their existing conservative behaviour.

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 captured value supplies no fact about the changed place. A void assertion establishes its facts at the call's normal completion, including when the call is parenthesized or an operand of another expression. A position reached both through that completion and through a path that skipped the call receives their join. The spelling of a statement body with or without braces does not change this rule.

Note 2

This analysis composes the proposal's existing type tests; it is not general constant folding, satisfiability solving, or proof of a predicate's implementation. The deciding-position restrictions, stable-membership conditions, literal-idiom exceptions and exhaustiveness limits of 1.8.5.4 continue to apply. A constant-answer membership query in a value position remains permitted. Propagating a type fact does not keep a borrowed location alive, permit a relocation, or remove a runtime boundary check otherwise required by this proposal.

1.8.5.2 The Canonical Total Order

The total order over canonical Type Records that CanonicalizeType invokes is fixed as follows, so that the member order a program observes through reflection is the same on every host. Records of different [[Kind]] order by the row order of Table 1. Records of one kind order by their fields in that table's order, compared as: a Type Record recursively by this order; a List element-wise and then shorter first; a String by code units; a Number by numeric value with -0𝔽 before +0𝔽 and NaN greatest; a BigInt by value; a Boolean with false before true; a Symbol by the order in which each is first interned into a Type Record within the surrounding Agent; a function, as a deferred record's [[Operator]], by the source position of its declaration, module before offset; and a metadata value by the structural comparison of 1.2.7.2 with its leaves ordered by these same rules. Where one field holds values of two different ECMAScript language types, as a literal's [[Value]] does, they order by type, in the order String, Number, BigInt, Boolean, Symbol.

Note

The order is arbitrary and load-bearing at once: nothing about it matters except that every host computes the same one, because canonical order is observable, a union's arms reflect in it and a builder that turns a union into a tuple inherits it, and the determinism invariant of 1.2.7.4 therefore reaches it. A builder wanting a different order sorts. The order among Symbols is Agent-local, because a Symbol is, and it is stable within a program because the same source interns the same symbols in the same sequence; a Symbol never reaches a comparison in another Agent, since a type carrying one cannot be shared.

1.8.5.3 NarrowTo ( s, t )

The abstract operation NarrowTo takes arguments s (a Type Record) and t (a Type Record) and returns either a Type Record or empty. It returns the type of a value known to be of the type s and also of the type t, or empty if no value can be of both. It performs the following steps when called:

  1. If s.[[Kind]] is any, return t.
  2. If s.[[Kind]] is union, then
    1. Let kept be a new empty List.
    2. For each element m of s.[[Members]], do
      1. If IsSubtype(m, t, « ») is true, append m to kept.
      2. Else if IsSubtype(t, m, « ») is true, append t to kept.
    3. If kept is empty, return empty.
    4. Return CanonicalizeType(the Type Record { [[Kind]]: union, [[Members]]: kept }).
  3. If IsSubtype(s, t, « ») is true, return s.
  4. If IsSubtype(t, s, « ») is true, return t.
  5. If t.[[Kind]] is union, then
    1. Let parts be a new empty List.
    2. For each element m of t.[[Members]], do
      1. Let part be NarrowTo(s, m).
      2. If part is not empty, append part to parts.
    3. If parts is empty, return empty.
    4. Return CanonicalizeType(the Type Record { [[Kind]]: union, [[Members]]: parts }).
  6. If s is a class type and t is a class type, return empty.
  7. If AreDisjoint(s, t) is true, return empty.
  8. Return CanonicalizeType(the Type Record { [[Kind]]: intersection, [[Members]]: « s, t » }).

A class type is a nominal Type Record whose [[Declaration]] is a class declaration, or the type of a global constructor usable as a type name other than 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 CanonicalizeType does not distribute an intersection over a union (1.2.2.8): without the step, NarrowTo of a class type against a union of class types it shares no value with would be the intersection C & (A | B), which stands for a type with no values.

Note

The disjointness judgment is 1.2.2.6, which this clause once stated in prose and which canonicalization and the Early Error of 1.4.2.1 now share, so a type is empty in one place exactly when it is empty in the others. Two distinct non-composite primitive types have no common values, since each value belongs to exactly one of them (1.5.1), and neither does a non-composite primitive type share values with an object or function type. Two object types, or two nominal types that are interfaces, may have common values, since a class may satisfy both, so an intersection of those is inhabited and is returned. This is the step that yields empty for d ?? 5 where d is a uint8: the narrowed type would be the intersection of uint8 and null | undefined, which has no values.

empty remains distinct from the 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.

1.8.5.4 NarrowFrom ( s: a Type Record, t: a Type Record, ): either a Type Record or empty

The type errors of this clause apply only in checked code (1.2.5). Outside checked code a test is never refused by them, though their results still narrow.

description
It returns the type of a value known to be of the type s and not of the type t, or empty if there is no such value.
  1. If s.[[Kind]] is union, then
    1. Let kept be a new empty List.
    2. For each element m of s.[[Members]], do
      1. If IsSubtype(m, t, « ») is false, append m to kept.
    3. If kept is empty, return empty.
    4. Return CanonicalizeType(the Type Record { [[Kind]]: union, [[Members]]: kept }).
  2. If IsSubtype(s, t, « ») is true, return empty.
  3. Return s.

This operation returns Type Records only. The internal scalar exclusions and generic proof domains of 1.8.5.1 may supply additional flow evidence without changing its result or the identity of a declared type.

Both NarrowTo and NarrowFrom read a closed set by its members (1.11.8, 1.11.9). Where s, or a member of s, is an enum all of whose enumerators' values are known, it is read as the union of the literal types over the enum of those values, one per value; where it is the type of a 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 1.5.19 untouched. A sealed class that is not abstract is a member of its own set and is read as written. Against that reading a literal t whose base is not that enum keeps the enumerators whose value it is, and NarrowFrom removes them, since an enumerator is equal to its value (1.9.2). A result that removed no member of the reading is s as written.

Both operations return empty where the result they would otherwise return admits no value: a union of no members, a parameterized type whose metadata holds an empty range (6.2), or an intersection of two parameterizations over one base whose governing meta type's meet answers null (Table 42), the last decided by the checking pass, which runs the hook. This is the narrowing counterpart of the Early Error a written intersection of the same pair receives, so the two receive one answer.

It is a type error to apply a narrowing form where the test can never succeed or can never fail, since the branch it guards is then dead code the program did not intend. These are the branches for which NarrowTo or NarrowFrom returns empty. The sentinel is deliberately not the never type (1.5.18): a test that cannot succeed is reported as a mistake rather than narrowed to the type with no values, because a branch the program wrote and can never take is dead code and not a computation.

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. NarrowTo(s, t) being empty shows a test that can never succeed only where s and t are disjoint across such a boundary: where every value of one is an ECMAScript primitive value - it is a numeric type whose typeof is "number", bigint, string, boolean, symbol, null or undefined, a literal type of one of those, an enum whose underlying type is one of those, a parameterized type over one of those, or a union of such types - and the other is an object, array, tuple, function or nominal type that is not an enum, or a union of those, since a value that is not an Object never becomes one nor the reverse (a vector, complex, rational or composite value is an Object, and type values are Type Objects); where both are primitive, literal or enum types, or parameterized types over one, whose underlying primitive types (an enum's being the type its enumerators have) share no value; or where both are class types (class type), or unions of them, which share no instance; or where s is read as a closed set and every member of that reading meets one of these conditions with t, an enumerator being an immutable value and a subclass a class; or where the result admits no value and both are parameterized types over types whose every value is an ECMAScript primitive value, or unions of those, whose values cannot change. The length and in rows of Table 24 meet it as well, since a fixed shape's length never changes and typed storage cannot be deleted (1.7.10). Disjointness that rests on the members of an object type is not such a boundary, since the object case of 1.7.6.1 is checked at the boundary and not afterwards, and two enums are not disjoint by declaration alone, since their enumerators may share values. NarrowFrom(s, t) being empty shows a test that can never fail only where every member of s and of t is a primitive, literal, enum or class type, or t is any: a value of an object type can stop satisfying it through mutation, and a where refinement is evaluated again by every test. For a pattern test the condition applies to each NarrowTo and NarrowFrom that MatchNarrow and MissNarrow perform (26.3), and an empty produced by one that does not meet it is not a verdict.

Where a member of s is a parameter with an established upper bound (1.4.3), this judgment reads that member as its bound, since a verdict for the bound holds for every specialization the bound admits. The facts the test establishes keep the parameter, and a parameter with no established bound gives no verdict.

A relational comparison, <, <=, > or >=, of an operand whose Static Type is a numeric type, a closed set read by its members, or a union of them, with another operand, each contributing limits, a compile-time constant its value and any other operand those of its Static Type, an enum's limits being its least and greatest enumerator values,, in either order, is a test wherever it decides a branch. It can never succeed where no pair of the operands' values satisfies it, and can never fail where every pair does; since NaN satisfies no comparison, a type with a member that can hold NaN is judged only in the first direction. A parameterized member is judged by the metadata row of Table 24 instead. For a proved built-in comparison, apply its judgment separately to existing known source members and remove a member from an edge only where its entire domain proves the opposite outcome. Canonicalize the surviving union. A member that partially overlaps the comparison retains its existing type, and an unresolved member contributes to both possibilities. Respect selected operator dispatch, literal adoption, exact constant representation, NaN and numeric-family admission; no new interval or cross-variable relation is inferred. In a value position a settled comparison remains a question with a constant answer. A comparison of an operand whose Static Type is string, a union of literal String types, or an enum over string, with a String constant, is judged the same way in code-unit order, the empty String being the least String and string having no greatest. A fixed-shape sequence's length (1.4.2.7), a literal String type's among them, is read as its exact length wherever a test reads it, and an index within a literal String's length reads as its own character (29), nothing being claimed past the end, where a read reaches 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 (1.9.7), no error results. A proved built-in strict or loose equality of the same preserved data value with itself can never fail, and its negation can never succeed, where its current domain and internal categories exclude NaN. In the supported floating domains, a NaN-only category makes self-equality always false. Self-inequality selects NaN and self-equality excludes it, by 1.8.5.1. Repeated getter reads, unresolved operations and selected user operators establish no such value identity or reflexivity proof.

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 set being read by its members. Two typeof results whose operands' tags (1.12.1) share none are such a pair; two whose operands each have one tag, the same tag, can never differ.

The disjoint strict-comparison judgment is an operator obligation in checked code: it applies when the result is stored, returned, discarded or used to decide a branch. Determine whether an operand names a literal type from names a literal type, using its resolved source declaration, independently of its current narrowed Static Type. In particular, narrowing an annotated mutable or immutable binding to a literal Type Record does not grant that operand the literal-name exception. This obligation does not make an ordinary pure membership query a deciding test.

Note 3

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 1.8.5.1; a broad union parameter without that fact supplies a different comparison domain. The declared union still permits a later String store. An established current String-key fact is also an input to the ordinary computed-property read judgment: let k: string = "n" can select the uint8 member of { n: uint8 }, whereas a general String parameter without a finite-key fact leaves that selection unknown. These examples do not change the separate rules for type-position indexed access, effect invalidation or reference liveness.

A test whose value's type settles its truthiness can never succeed or can never fail wherever it decides a branch, including a non-path expression that supplies no place fact. Where the truthy part (1.9.5) of the Static Type of such a test is never, the branch its truth leads to can never be taken, and where the falsy part is never, the other one can never be; it is a type error in either case. A test that is 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 Static Type of its value, whatever its form: a conditional expression by the Static Type of the whole and never by the truthiness of an arm, whose value is judged where it is used (1.5.16), though a narrowing form within an arm is still judged where the arm decides the branch, an assignment by its actual returned value after applying store effects, and the producers the tested-expression transfers name by their parts. An operator with a rule of its own keeps it, including the selected-result rule below. A built-in comparison whose result type remains the broad 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 Static Type is void is refused by 1.5.16 rather than judged here, since it has no value whose truthiness could be settled. An array literal, including one 1.14.8 leaves without a Static Type, has a truthy part only and is never nullish, since it is an Array whatever its elements. So does an untagged template literal any of whose cooked strings is non-empty, since it is a non-empty String.

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 ToBoolean and negation, rather than the raw method result or receiver type. A declared compound operator supplies its method result without rebinding the target. Apply ordinary call effects to captured facts; a declared result contract does not prove an operator pure. Unknown dispatch or a result admitting both truth values stays conservative. Do not inspect or execute an operator body to infer a constant answer.

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 abrupt completions. An unknown transformed result retains conservative checking.

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 Table 24. A label whose value an earlier label of the same 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 enum holds the discriminant with every case's type removed (1.11.8), and is a branch that can never be taken where that is empty. So is the 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.

Note 4

This is why d ?? 5 is rejected where d is a uint8: the left operand can never be nullish, so NarrowTo of 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 "number" for every numeric type, so typeof v === "number" narrows uint8 | string to uint8 but cannot separate uint8 from uint16, and reports "object" for the SIMD, rational, and complex types. 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 stable path relies on the invalidation rules in 1.8.5.1. Assignment to the path or a prefix can end the fact. This does not license narrowing arbitrary repeated property expressions: the path restrictions above and the structural-membership stability conditions remain necessary (1.11.1).

The switch row covers the exhaustiveness the design reserves to enums and sealed classes. A union of literal types deliberately does not drive exhaustiveness, so it narrows in each case but its default is not proven unreachable.

1.9 Operators

For binary arithmetic, bitwise and ordered operators, it is a type error when participating operand contracts establish that every possible selected path fails a required primitive conversion or produces numeric categories incompatible with that operation. Consider declared and derived operator dispatch and contextual literal adoption first. A known Symbol on a selected numeric or ordered-conversion path fails; multiplying BigInt by Boolean fails because the latter converts to Number. Arithmetic's numeric-category exclusions do not apply to ordered comparison or equality merely because the same types occur there. This negative proof does not prohibit otherwise successful coercions. An unknown or viable alternative, or unresolved operator dispatch, prevents the proof. Participation and preservation of ordinary JavaScript exception timing follow 1.9.7.

Follow established primitive conversion results into the selected operation's own numeric-family rules. On the built-in numeric path, Boolean, null and undefined produce Number and therefore cannot serve as a sized numeric-family operand where that operation requires matching families. This applies to such ordered operations as well as arithmetic and bitwise operations; it does not import BigInt arithmetic restrictions into comparison. Ordinary Number operations, successful String coercions, contextual literal adoption and applicable declared operators are unaffected. Reject only when every selected path fails; an explicit any or unresolved dispatch retains runtime checking.

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 type error when this domain exclusion or the conversion exclusions establish failure on every selected path. Declared dispatch and literal adoption precede this judgment. It does not prohibit BigInt << or >>, Number unsigned right shift, or unsigned right shift in a sized integer value family. An implementation representation using BigInt does not change that family. Unknown alternatives and ordinary untyped expressions retain runtime checking.

It is a type error for an untagged template substitution, or an operand on a built-in + concatenation path, to require implicit string conversion when its participating Static Type establishes only Symbol values. Participation is the operand-contract judgment of 1.9.7. Applicable declared operators are considered first. A union supplies this negative proof only when all alternatives require the failing conversion; any and unknown coercions retain runtime checking. A tagged template passes its substitution values to the tag without this implicit conversion, but expressions inside its substitutions are checked normally. Explicit 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 typed. Little new machinery is required, because the specification already has the mechanism: when BigInt was introduced, the operators were generalized from one numeric type to two, and an operator now reduces its operands to numeric values, rejects a pair whose types differ, and dispatches to an abstract operation belonging to the type. This proposal adds numeric types to that mechanism. It does not add a second one.

Note

The principle the design states is that typed code changes what a program checks and what its values can represent, and does not change what an operator means. Every rule below follows from it. Where an operator's result is forced by the type rather than chosen, as an integer quotient is, the type decides; everywhere else the operator means what it means today.

1.9.1 Dispatch

ToNumeric is revised so that a value of a numeric type of this proposal reaches an operator as itself rather than as a Number.

1.9.1.1 ToNumeric ( value )

The abstract operation ToNumeric takes argument value (an ECMAScript language value) and returns either a normal completion containing either a Number, a BigInt, or a value of a numeric type of this proposal, or a throw completion. It returns value converted to a numeric value, preserving the numeric type it already has. It performs the following steps when called:

  1. Let primValue be ? ToPrimitive(value, number).
  2. If primValue is a value of a numeric type of 1.5, return primValue.
  3. If primValue is a BigInt, return primValue.
  4. Return ? ToNumber(primValue).

The remaining machinery is unchanged and generalizes as written. In 13.15.3, the step that throws a TypeError when the two operands are not of the same numeric type applies to every numeric type, and the table associating an operator with an abstract operation is read as associating it with T::op for the numeric type T of the operands. The operations are those of 1.5.23.

Note

This is the whole of the dispatch. 1n + 1 already throws a TypeError today because the two operands are of different numeric types and neither converts to the other, and uint8(1) + uint16(1) throws for exactly the same reason, by the same step, with no rule specific to it. The shape the committee arrived at for BigInt, an operator that dispatches on the numeric type of its operands and refuses to mix them, is the shape this proposal needs, which is why adding sixteen numeric types costs one inserted step.

ToNumber is not reached for a typed operand and so is never the vehicle for an implicit conversion, which is what 1.6.10 anticipated. ToNumber keeps its meaning for a value of 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 is a String after ToPrimitive, the result is the concatenation. uint8(1) + "x" is therefore "1x". This is not an implicit conversion between value types in the sense 1.6.1 forbids, since no uint8 becomes a value of another numeric type; it is the meaning + has always had, and changing it for typed operands would violate the principle above.

1.9.2 Arithmetic Never Promotes

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. Integer exponent and shift-count rules retain their own contexts. No unit-polymorphic zero rule is introduced: arithmetic may reject a scalar/quantity pair where strict equality answers false; loose equality retains its mathematical-value rule.

Two operands of the same value type produce a result of that type. Two operands of different value types are a type error, since neither is assignable to the other. Where one operand is a literal it takes the type of the other (1.6.6), so a literal never forces a conversion.

The LITERAL rule of the preceding paragraph also reaches ==, !=, ===, !==, and a case label, though the type error of the preceding paragraph does not. A comparison answers rather than computing, so two operands of different numeric types compare unequal instead of being an error; but a literal in one of those positions still takes the other operand's type, so uint8(5) === 5 is true. A binding adopts nothing, so uint8(5) === n for an n of the number type is false, and the values of distinct value types remain distinct. A 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 false rather than an error - unlike an arithmetic position, where the same literal is an Early Error, because there the literal must become a value and here it need not.

Note 1

Reaching equality is what makes a typed value usable with the comparisons programs actually write, including inside a callback: 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 proposal have none, so a literal is the ONLY way to write one of their values in a comparison. Declining would have made every such comparison require an explicit conversion.

It is a type error if the Static Types of the operands of an arithmetic, bitwise, shift, or relational operator are numeric types that are not the same type, except that ordered comparison between the existing unbranded Number and BigInt types retains the base language's mathematical comparison. This exception applies to scalar and union alternatives and does not relax the identity rules for the numeric value types added by this proposal or their brands. A shift's right operand is a DISTANCE rather than a quantity combined with the left one, and is exempt as well: where both operands are of integer types, the distance may be of any of them, the operator takes it as written (1.9.6), and the result has the left operand's type. Converting the distance into the left operand's type could change it, since 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 TypeError of 1.9.1 is thrown at run time if the selected operation forbids the differing types.

Note 2

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 proposal offers across engines depends on refusing that.

1.9.3 The Operations Each Family Defines

A numeric type defines the operations of Table 25. An operator whose operation the operand's type does not define is a type error.

Table 25: Operations by Family
FamilyDefinesDoes not define
integerEvery operation of 1.5.23.None.
binary floating-point, and the Number typeunaryMinus, exponentiate, multiply, divide, remainder, add, subtract, lessThan, equal, sameValue, sameValueZero, toString.For a binary floating-point type of this proposal: bitwiseNOT, the shifts, and the bitwise operations, since each would require converting the operand to an integer type.
decimal floating-pointunaryMinus, exponentiate, multiply, divide, remainder, add, subtract, lessThan, equal, sameValue, sameValueZero, toString.bitwiseNOT, the shifts, and the bitwise operations.
rationalunaryMinus, multiply, divide, add, subtract, lessThan, equal, sameValue, sameValueZero, toString.bitwiseNOT, the shifts, the bitwise operations, and remainder. exponentiate is defined only for an integer exponent.
complexunaryMinus, exponentiate, multiply, divide, add, subtract, equal, sameValue, sameValueZero, toString.lessThan, since the complex numbers are not ordered; remainder; and the bitwise and shift operations.
vectorunaryMinus, multiply, divide, add, subtract, equal, sameValue, sameValueZero, toString, applied lane-wise; and the bitwise and shift operations where the lane type is an integer type.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 1.9.9 rather than through the protocol.
Note

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 integer, a binary floating-point, a decimal floating-point and a rational number have that unit, the last two stepping in their own type since neither has a Number value. A complex number has none: the complex numbers are the family the table leaves unordered, and an update of one is a type error, as an operation its type does not define.

The Number type continues to define the bitwise and shift operations, which convert their operand with ToInt32 as they do today, and no existing program changes. A 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 type error rather than a silent trip through int32. This is the one place where an operator that works on a Number does not work on the corresponding typed value, and it follows from the conversion rule rather than from a decision about bitwise operators.

The table is the answer to the three cases 1.5.23 left open. Two of them are absences, complex having no order and a rational having no remainder. The third, a vector comparison returning a vector, is not an absence but a signature the protocol cannot express, since lessThan is required to return a Boolean. Rather than widening the protocol for one family, the vector comparisons are declared operators, which is a path the specification already provides and which admits the return-type overloading they need (1.10.5).

1.9.4 The Result of an Operator

1.9.3 states which operations a family has. This clause states what an application of one yields, which Table 2 does not reach: that table gives the Static Type of a literal, a binding, and a type name, and an operator application is none of those.

Where a declared operator applies (1.9.9), its overload set is resolved using the operand types and the application's contextual return type, as for a call (1.10.3). Its selected signature supplies the result type; that type need not be the receiver's type. Apply any specified Boolean conversion or negation before judging the actual normal result in a deciding position under 1.8.5.4. The declaration does not establish that invocation preserves captured facts. The following rules apply to the built-in operation when no declared operator applies.

For the built-in in and instanceof operators, including the private-name form of in, it is a type error if the right operand's Static Type establishes that its value is not an Object. This judgment includes non-object primitive types and their literal and qualified forms; a union is rejected by this rule only when every alternative is known to be non-object. Unknown types, open type parameters and unions with a possible Object retain the runtime check. Type Objects and composite objects are Objects for this judgment, irrespective of their Type Record's [[Kind]]. This is an object-eligibility check, not a requirement that an instanceof operand be a constructor: an object with a custom 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 typed class constructor contributes its constructor-side static contract, including inherited and specialized members and established immutable lexical aliases. Use actual declaration and well-known Symbol identity rather than the expression's spelling; decorated, replaced or unknown origins retain their existing deferral. It is a type error if every possible selected hook call is known to be invalid: a present non-nullish hook cannot be called, or the actual left operand cannot satisfy its applicable argument signatures. The hook is selected before any ordinary instance-test shortcut for a primitive left operand. Its result is passed to ToBoolean, so an Object result is valid. Nullish hooks retain the ordinary callable-target fallback. An optional hook prevents this hook-call proof unless its fallback also fails under the rule below. Open callable possibilities, unknown contracts and viable alternatives prevent a closed negative proof; missing declarations on open objects do not prove absence. The judgment uses the actual well-known Symbol and inherited and specialized contracts, invokes no getter or hook, and preserves reference decay and liveness. This protocol proof does not replace the separate per-alternative requirements on explicit function calls.

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 constructor object. The left operand's nominal annotation alone is insufficient. Account for the producer and other dependencies' initialization, left-operand evaluation, intervening effects, receiving operations that can invoke conversions, and suspension. A constructor retained from an earlier evaluation also requires evidence about its current protocol; its original declaration alone does not establish that the hook or constructor prototype chain remains unchanged.

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 field initialization. Unknown dependencies and exhausted proofs retain runtime behaviour. Checking performs no property getter, Proxy operation, constructor, conversion or hook invocation to settle these questions. Independently proved stable tests retain their existing diagnostics. These requirements also apply to the nominal branch implications in 1.8.5 and supply no reference-liveness or storage authority.

For the nullish-hook fallback, it is a type error when both the effective Symbol.hasInstance contract is proved nullish and a stable creation origin proves that the participating target is an ordinary non-callable Object. An ordinary object literal and its stable aliases can supply the latter fact. An open structural type or a nominal class name without such an origin does not prove the absence of [[Call]], and an annotation whose conversion may replace the creation result withdraws the origin proof. Every selected alternative must fail; a viable custom hook, unknown origin or undeclared hook prevents the negative proof. A primitive left operand does not skip the target's fallback callability requirement. No getter, hook or reference is evaluated, and runtime reference checks remain required.

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 type error only when every possible combination is known to have no applicable operation. Consider declared and derived operator dispatch before built-in availability, retain brands when comparing numeric identity, and consider a literal in each applicable numeric context. An unknown alternative or unresolved dispatch prevents this proof. Failed alternatives contribute no diagnostics, literal contexts or result types. This judgment does not require a Cartesian product of result types and does not change the per-alternative requirements on function calls. Where an alternative remains viable, retain the runtime check for the actual operands; do not apply a scalar built-in exclusion to bypass a possible declared operator in another alternative.

The Static Type of that operator application is determined as follows.

  1. If the operator is &&, ||, or ??, the Static Type is the union of the short-circuiting part of the left operand's Static Type and the right operand's Static Type, where the short-circuiting part is the falsy part (1.9.5) for &&, the truthy part for ||, and, for ??, the result of NarrowFrom (1.8.5) applied to that Static Type and null | undefined; where that part has no members, the Static Type is the right operand's Static Type alone, and where the complementary part has no members, so that the left operand always short-circuits and the right is never evaluated, the Static Type is the short-circuiting part alone.
  2. Else if the operator is an equality or relational operator other than one whose operands are vectors, the Static Type is the primitive type named "boolean".
  3. Else if the operator is +, the built-in operation is established, and either operand is known to contain only String values, the Static Type is the primitive type named "string".
  4. Else if either operand's Static Type is a literal type and the other's is not, the literal adopts the other operand's numeric base, excluding metadata (1.6.6), or beside a vector type the numeric base of that type's lane type, and this rule is applied again.
  5. Else if one operand's Static Type is a vector type and the other operand has the numeric base of the vector type's lane type, the other operand broadcasts: it is converted by the lane cast (1.5.8), and this rule is applied again.
  6. Else if the operands' Static Types are not the same type, it is a type error: an operator does not combine two numeric types (1.9.2).
  7. Else if the operands' Static Type is a vector type, the Static Type is that vector type, the operator having been applied at each lane; except that a relational or equality operator between vectors is overloaded on its result type (19).
  8. Else if the family of the operands' Static Type does not define the operation, it is a type error.
  9. Else, the Static Type is the operands' Static Type.

The concatenation result judgment reads String category evidence through literal bases, parameterized, shared and reference wrappers, enum underlying types and established type-parameter bounds. Every member of a non-empty union must supply the evidence; one member of an intersection suffices where that member restricts every inhabitant to String. An unresolved member or recursive revisit supplies no evidence. Existing lexical constant evidence may supply a String literal; this does not assign a permanent contract to an unannotated mutable binding. Parentheses are transparent. The resulting string is the type of a normal completion, not a proof that primitive conversion succeeds. Apply the existing conversion-failure judgments and ordinary destination checks; a contextual numeric destination neither changes + 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, abrupt completions or reference liveness.

The two exceptions in that step are duals, and between them they keep the Static Type to the values the expression can produce: 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 boolean rather than an error. A contextual type applies to the operands rather than to the application, since it is an operand that is produced: 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 1.7.7.3.1 applies to a value: narrower before wider, an integer member before a floating one, and a member whose range excludes the value not at all. 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 ordered and flattened first, so which member is chosen is a property of the TYPE and not of how the program spelled it. Where no member accepts the value the literal is refused, as it is at a single type.

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> }.

Note 1

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 - 1.14.9 withholds one so the boundary is not elided, since the boundary is where an Array carrying the element type is built - and it is nonetheless adapted, on the same evidence as any other literal.

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.

Table 26: Result of an Operator by Family
FamilyArithmetic + - * / % **Shifts and bitwiseRelational and equalityUnary -
integerthe operand type, wrapping (1.9.6); / truncates toward zerothe operand typeBooleanthe operand type
binary floating-pointthe operand typenot definedBooleanthe operand type
decimal floating-pointthe operand typenot definedBooleanthe operand type
rationalthe operand type; % not defined, ** for an integer exponent onlynot definedBooleanthe operand type
complexthe operand type; % not definednot definedequality only; the complex numbers are not orderedthe operand type
vectorthe operand type, at each lanethe operand type where the lane type is an integer typethe three forms of 19the operand type
bigintbigint; does not combine with a NumberbigintBooleanbigint
string+ concatenates; the others are the existing Number coercionthe existing Number coercionBooleanthe existing Number coercion
enumthe underlying type of the enumthe underlying typeBooleanthe underlying type
nominalthe return type of the declared operator, or the existing rules where none is declaredas declaredBoolean unless declared otherwiseas declared
referenceas its referent's type: an operand is read through the referenceas its referent's typeas its referent's typeas its referent's type
Note 2

Three results in the table are worth stating in words, being the ones a type's name does not suggest.

An integer type divides by truncating toward zero and wraps on overflow, so 7 / 2 at uint8 is 3 and 1 - 3 at uint8 is 254. 1.13.2 gives the forms that trap or clamp instead.

A one-bit integer is an integer, so 1 + 1 at boolean1 is 0. The name suggests a Boolean, but the family is integer and the arithmetic wraps at one bit as it wraps at eight.

A relational or equality operator yields a Boolean and not a value of a sized type, because it answers a question rather than computing a quantity and so has no result type to preserve. A comparison between vectors is the exception the fourth step names, and is overloaded on its result type because a lane-wise comparison has more than one useful shape.

1.9.5 The Falsy and Truthy Parts of a Type

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.

Table 27: Contribution of a Member to Each Part
MemberFalsy partTruthy part
A literal typeThe member, where its value is falsy.The member, where its value is truthy.
undefined, nullThe member.Nothing.
booleanThe literal type of false.The literal type of true.
A numeric type, string, or bigintThe member.The member.
symbol, type, an object, array, tuple, function, or nominal type other than an enumNothing.The member.
An enum all of whose enumerators' values are knownThe literal types over the enum of its enumerators whose values are falsy.The literal types over the enum of its enumerators whose values are truthy.
voidNothing.Nothing.
Note

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 Static Type is sound where a narrower one would be a claim the type system cannot support. An implementation that can name the value more precisely, as it can for 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 truthy part of 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).

1.9.6 Integer Operations

For an integer type T of width N, each operation computes the exact mathematical result and then reduces it to a value of T as Table 19 specifies for an integer target: the result is taken modulo 2**_N_ and interpreted as a value of _T_. Overflow therefore wraps, in two's complement for a signed type, and discards the bits that do not fit.

T::divide returns the exact quotient truncated toward zero. T::remainder returns the remainder whose sign follows the dividend, unchanged in meaning from the Number type. If the divisor is zero, both throw a RangeError exception.

It is a type error if the divisor of / or % is a known integer operand whose value is zero and the operands are of an integer type of this proposal. The same judgment applies where the operands are of the bigint type, and a known negative exponent of a bigint left operand of ** is likewise a type error, since the operation has no result; a bigint shift distance is not judged, as a negative distance is a shift the other way. A known integer operand is a literal, a const bound to a constant expression over literals and such constants (1.8.2), an explicit conversion of either to an integer type in either spelling (1.6.7), its value being the conversion's result, or a const of an integer value type whose initializer is one of these. 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*`k` reduced as above, and `x` `>>` `k` the exact quotient of `x` by 2`k` rounded toward negative infinity, so a distance of _N_ or more shifts every bit out and leaves 0, or -1 where `>>` is applied to a negative value of a signed type. The distance is NOT reduced modulo _N_. If the distance is negative, the operators throw a *RangeError exception, and it is a type error if it is a known integer operand whose value is negative or at least N, the divisor rule one operator along.

Note 1

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*`k` are the same computation, so they must not part company at any distance; reducing the distance modulo _N_ would make `x` `<<` _N_ answer `x` while `x` × 2*_N_ answers 0. The masking rule is what Java, C# and WebAssembly adopt and what the shift instructions of common hardware do, and it is available to an implementation wherever it can show the distance is below the width, which is nearly everywhere. What it cannot be is the meaning of the operator: a mask is only free where _N_ is a power of two, and `uint.<24>` and `uint.<40>` are ordinary types here.

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*-1 times an even value is an integer and times an odd one is not, and the reduction step has no meaning for a non-integer. The literal rule is the divisor rule one operator along, and both reference designs do the same: a constant shift at or above the width is a compile error in Rust under its default overflow lint, and a negative constant shift count is illegal in Go, whose specification otherwise states this clause's rule exactly — "there is no upper limit on the shift count", and "`x` `<<` 1 is the same as `x`2".

Note 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 1.13.3.

That an integer quotient truncates is not a choice. 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 integer type has no infinity and no NaN, so there is nothing to return. A float is unaffected and keeps the answer it has today: 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 (Table 22): it is a mistake visible in the source text, so it does not compile, while a divisor whose value is not known until run time raises a RangeError there. It deliberately does not extend to bigint, where 1n / 0n continues to throw a RangeError exception at run time: that expression is legal today, and rejecting it earlier would break an existing program. The asymmetry is a compatibility constraint rather than a design view, and it is worth stating in the design document, which does not currently note that the rule stops at the new types.

1.9.7 Unary Operators

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 path remains admitted.

Unary - applies T::unaryMinus for the numeric type T of its operand. For an unsigned integer type of width N, the result is 2*_N_ minus the operand, modulo 2*_N_, so unary minus on an unsigned value wraps rather than raising an error.

For a built-in prefix or postfix increment or decrement, it is a type error when the participating operand contract proves that every possible path fails either numeric conversion or storing the updated value in the operand's location. The judgment uses the actual write contract, including a setter parameter or reference referent, rather than assuming it equals the read type. It computes the updated value where needed for a literal destination, respecting the numeric type's rounding and wrapping; the value stored is distinct from a postfix expression's result. An unknown conversion, destination or applicable operator prevents this proof. A union with a successful path retains its runtime check.

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 proposal's numeric updates, including wrapping, remain valid, as does numeric-to-String conversion on the existing update/store path. This rule does not replace that boundary with ordinary assignment checking. Applicable declared updates precede the built-in judgment and retain their result/store requirements. No location is evaluated, borrowed or pinned to perform the proof; write permission and reference liveness remain independently required.

Unary + returns its operand unchanged when the operand is a value of a numeric type of this proposal. It continues to throw a TypeError for a BigInt, and continues to apply ToNumber otherwise.

After applicable user-defined operator dispatch, it is a type error to apply unary + to an operand whose participating Static Type establishes only BigInt or Symbol values, or unary - or ~ to one establishing only Symbol values. Participation here is an operand contract: an annotation, a declared member, a declared or published return, or an inferred contribution carrying such a contract through a constant, pattern, iteration binding or enclosing value expression. An unrelated typed condition or annotation in the surrounding body contributes no operand contract. A union proves this error only when every alternative is forbidden by the operator; any and unresolved alternatives retain the runtime check.

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 typed unary-plus expression does not publish a BigInt result. This rule changes no conversion semantics: BigInt still admits unary minus and bitwise NOT, the proposal's numeric types keep their own operations, and declared operators are resolved before the built-in domain is tested. No reference permission or liveness rule changes.

Note

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 proposal adds. Against it: operator+() is an overloadable unary operator on a class in the design, so unary + already means more than ToNumber, and an earlier clause of this specification states that +a on a uint8 is a uint8. This clause follows the design. If the committee prefers consistency with BigInt, the change is to this step alone, and it would make +x a TypeError for every numeric type of this proposal.

1.9.8 Equality and Comparison

=== and !== are not overloadable and retain their identity semantics. What identity means follows from the type. The identity of a reference type is the reference, so two distinct objects are never ===. The identity of a value type is its value, as it already is for a uint8 and a String, so two instances of a value type class are === exactly when their fields are, compared field by field.

== between two values of the same numeric type applies T::equal. Between two values of different numeric types it is false rather than a type error, as it is today between a Number and a BigInt of unequal value, and IsLooselyEqual continues to compare their mathematical values where both are numeric. A decimal compares by numerical value, so a value with trailing zeros equals one without them (1.5.4).

The relational operators apply T::lessThan through IsLessThan, unchanged.

Note

That === compares a value type class field by field rather than by reference is what makes a value type a value: two 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 false across numeric types rather than throwing is the existing treatment of a Number against a BigInt, and it is deliberately not aligned with the arithmetic operators, which do throw. This specification states the rule the design left open: == and != between two typed values compare by the loose-equality algorithm they already use, so uint8(1) == uint16(1) is true by comparison of mathematical values, while uint8(1) + uint16(1) remains a compile-time type error. The two are deliberately not aligned: an arithmetic operator produces a value and so must fix the type it produces, which is why a cross-type use is rejected rather than silently widened; equality asks a question and produces a Boolean whatever its operands' types, so it has no result type to fix and no information to lose by answering. This is a stated position rather than an inherited one; a committee that prefers equality to throw across typed values, for symmetry with arithmetic, has the alternative recorded here.

1.9.9 User-Defined Operators

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 1.6.8.

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, field or element named on the left is left as it was. Such an operator therefore applies to a 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.

Note 1

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 value type, whose bindings are usually 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 this is what allows chaining, and this rule is what allows the receiver to have been mutated by the time it is returned.

It is a Syntax Error if an OperatorDefinition of a binary operator, a relational operator, or a compound assignment operator declares other than one parameter, if one of a unary operator or of ++ 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 1.5.23 may not be redeclared for that type, so a program cannot redefine 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 ToBoolean before being negated, since it is a user function and may return any value. A DECLARED operator always takes precedence over the derivation, so a class that declares both < 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 false and <= and >= always true for any class declaring only <. Half of those answers are correct by coincidence, which is why only the reversed operand order reveals it: _M_(2) > _M_(1) answered false.

Note 2

Deriving <= as the negation of _b_ < _a_ assumes a TOTAL order. Where neither _a_ < _b_ nor _b_ < _a_ holds - as for a NaN-like value under a partial order - the derived <= answers true where false is meant. A type whose order is partial should declare <= 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 design writes a multidimensional or jagged index. Where the base of a computed access is an instance of a class that declares an index accessor, and every index supplied is a value of a numeric type, the access applies the accessor whose parameter count matches the number of indices, with those indices as its argument list, in place of the ordinary property read or write: the read direction for a value use and the write direction for the target of an assignment, the write direction taking the indices followed by the assigned value. An index that is not of a numeric type makes the access an ordinary property access, so an index accessor does not shadow the string-keyed and symbol-keyed properties, including the methods, of an instance it applies to. Resolution among several index accessors of one class is overload resolution on the number of indices and then on their types (1.10.3), as for any overloaded operator, so a class may declare [i] and [x, y] and each is reached by the access that matches it.

Where a class declares a read direction whose Static Type is a reference type and declares no write direction for that number of indices, an assignment whose target is such an access stores through the reference the read direction returns. This is the assignment-target rule of 2.8 applied to an index access: the accessor is a call whose result is a reference, and a reference denotes the place a write goes, which is why the design writes such an accessor without a setter. A read direction whose result is not a reference has no location for a write to reach, and an assignment to it is a TypeError exception. It is a type error for a class to declare both a reference-returning read direction and a write direction for the same number of indices, since the write would have two meanings.

Note 3

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 contextual type under 1.6.5, retaining the expression's source and numeric adaptation eligibility. Thus 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.

Note 4

Resolution among several declarations of one operator is overload resolution on the operand types, specified in 1.10.3. The operator overloading extension works the rules through a case where the receiver is not the left operand, a scalar multiplying a vector, and states what an engine must guarantee for the result to compile as well as the equivalent C++ would.

1.10 Functions and Overloading

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.

1.10.1 Signature Records

A Signature Record describes one callable signature. It has the fields of Table 28. A function Type Record's [[Signatures]] is a List of them, with more than one element when the function is overloaded.

Table 28: Signature Record Fields
FieldValueMeaning
[[Parameters]]a List of Parameter RecordsThe parameters, in order.
[[Return]]a Type Record or noneThe declared return type, and none where none is declared. It is none rather than the 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 1.6.2's "the published one otherwise" with no case in which to apply.
[[InferredReturn]]a Type Record or noneThe published inferred return type (1.6.2), or none where the signature declares a return type or the function does not participate in inference. It is a separate field because identity, overload-set formation, ranking, and viability read [[Return]] alone.
[[ThisType]]a Type Record or noneThe declared this type; for a method, the self [[ThisType]] of 8.1, which no syntax declares; and none otherwise.
[[Narrows]]a List of Narrowing RecordsThe narrowings a call of this signature declares (8.2). Empty where none is declared.
[[TypeParameters]]a List of Type Parameter RecordsThe type parameters the signature declares (Table 29), empty when it is not generic.
[[Untyped]]a Booleantrue when the signature declares no parameter type and no return type, in which case it is a catch-all. It is a syntactic property: a signature that declares neither is untyped however much is inferred for it.

A Type Parameter Record describes one declared type parameter, of a signature or of a generic declaration. It has the fields of Table 29.

Table 29: Type Parameter Record Fields
FieldValueMeaning
[[Name]]a StringThe 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]]type, value, or constructorParameterKind of the declaration (1.4.3.3): decided by what the domain means, never by how it is spelled.
[[Domain]]a Parse NodeThe Type of the TypeParameterDomain, evaluated per application as the constraint is.
[[Variadic]]a Booleantrue when the parameter is declared with ... (4.4).
[[Variance]]invariant, covariant, or contravariantThe declared variance (4.12); invariant where no VarianceModifier is written. For a variadic parameter it applies per element position.
[[Arity]]a non-negative integerThe count of _ holes of a higher-kinded parameter (4.13); 0 for a parameter that stands for a type or a value.
[[Constraint]]a Parse Node or noneThe Type of the TypeParameterConstraint, the bound written after extends, or none. It is a Parse Node rather than a Type Record because a constraint is evaluated per application, over the bindings of the parameters before it (1.4.3.4); a record that evaluated it once would fix V: T = 0 at whatever T meant first.
[[Default]]a Parse Node or noneThe Type of the TypeParameterDefault, evaluated as the constraint is.
[[Declaration]]a Parse NodeThe TypeParameter itself: the identity a parameter Type Record substitutes by, and what makes two references to one parameter one thing.

The [[Domain]], [[Constraint]] and [[Default]] Parse Nodes retain their declaring environments. Effective constraint and default descriptions may accompany them under 4.2; each description retains its parameter declaration identities and captured substitutions and does not replace per-application evaluation with one declaration-wide concrete result.

Two Lists of Type Parameter Records are the same up to renaming when they have the same length and, pairwise, the same [[Kind]], [[Variadic]], [[Variance]], and [[Arity]], and constraints and defaults that denote the same types once each parameter of one list is identified with the parameter at the same position of the other. This is how generic signatures compare (1.10.2.1): [[Name]] is never read, so <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 Type Record), an [[Optional]] field (a Boolean, true when the parameter is marked ? or has a default), a [[Rest]] field (a Boolean), an [[Initial]] field (an ECMAScript language value or none, the declared default), and a [[Reference]] field (a Boolean). [[Reference]] is true exactly when [[Type]].[[Kind]] is reference: the flag restates the type for convenience, and reflection reads the fact from the type.

Note 1

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 (1.10.5). Those two declarations are how a name is overloaded, in EVERY goal symbol and every scope: the early error ECMA-262 raises for a redeclared lexical name does not apply to a second FunctionDeclaration of a name a FunctionDeclaration already declared, whose signature differs. A name a let, const or class declaration bound is not one, and is redeclared as it always was.

Note 2

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 early error. Whether a function could be overloaded therefore depended on which file it was written in, which no rule said and none intended.

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 set. A name denotes all of its declarations, so an importer resolves against all of them and no declaration is unreachable through the boundary. A parameter's name is carried because the design's named arguments select by it, and is not part of the signature's identity. A parameter's [[Initial]] and a signature's [[ThisType]] are part of the identity, compared by SameValue and as a type; the name is carried from the first occurrence interned, so two structurally identical declarations reflect the first's names. That is an order-of-loading fact, deterministic within a program and tolerated because a parameter has one name and there is nothing else to do with two. It is also the measure of what may be added: no field may be admitted whose value depends on the order modules were reached in, which is a stronger rule than counting fields and is the one 23 satisfies by unioning where a name wins. A decorator's metadata is the case that tests this and does not become an exception to it: two functions of one signature are one type however differently their parameters are decorated, so metadata cannot ride a Signature Record, and 7 reads it from the declaration instead. [[ThisType]] and [[Narrows]] are the design's declarative checker facts: data the checker reads and never code it runs. Both are part of a signature's identity, and both are consumed by 8.

1.10.2 Identity and Subtyping of Function Types

These operations complete the cases 1.2.2.2 and 1.2.3.1 defer for the function kind.

1.10.2.1 SameFunctionType ( s, t, assumptions )

The abstract operation SameFunctionType takes arguments s (a function Type Record), t (a function Type Record), and assumptions (a List of Records that have [[First]] and [[Second]] fields) and returns a Boolean. It determines whether s and t are the same function type. It performs the following steps when called:

  1. If s.[[Signatures]] and t.[[Signatures]] do not have the same number of elements, return false.
  2. For each integer i such that 0 ≤ i and i < the number of elements of s.[[Signatures]], do
    1. Let a be s.[[Signatures]][i].
    2. Let b be t.[[Signatures]][i].
    3. If a.[[TypeParameters]] and b.[[TypeParameters]] do not have the same number of elements, return false.
    4. Let identified be a List whose elements are the elements of assumptions.
    5. For each integer k such that 0 ≤ k and k < the number of elements of a.[[TypeParameters]], do
      1. Let u be a.[[TypeParameters]][k].
      2. Let w be b.[[TypeParameters]][k].
      3. If u.[[Kind]] is not w.[[Kind]], or u.[[Variadic]] is not w.[[Variadic]], or u.[[Variance]] is not w.[[Variance]], or u.[[Arity]] is not w.[[Arity]], return false.
      4. Append the Record { [[First]]: the parameter Type Record whose [[Declaration]] is u.[[Declaration]], [[Second]]: the parameter Type Record whose [[Declaration]] is w.[[Declaration]] } to identified.
    6. For each integer k such that 0 ≤ k and k < the number of elements of a.[[TypeParameters]], do
      1. Let u be a.[[TypeParameters]][k].
      2. Let w be b.[[TypeParameters]][k].
      3. If exactly one of u.[[Constraint]] and w.[[Constraint]] is none, return false.
      4. If u.[[Constraint]] is not none and SameTypeWithAssumptions(the Type Record u.[[Constraint]] denotes, the Type Record w.[[Constraint]] denotes, identified) is false, return false.
      5. If exactly one of u.[[Default]] and w.[[Default]] is none, return false.
      6. If u.[[Default]] is not none and SameTypeWithAssumptions(the Type Record u.[[Default]] denotes, the Type Record w.[[Default]] denotes, identified) is false, return false.
    7. If a.[[Parameters]] and b.[[Parameters]] do not have the same number of elements, return false.
    8. For each integer j such that 0 ≤ j and j < the number of elements of a.[[Parameters]], do
      1. Let p be a.[[Parameters]][j].
      2. Let q be b.[[Parameters]][j].
      3. If p.[[Optional]] is not q.[[Optional]], return false.
      4. If p.[[Rest]] is not q.[[Rest]], return false.
      5. If p.[[Reference]] is not q.[[Reference]], return false.
      6. If p.[[Initial]] is none or q.[[Initial]] is none, then
        1. If p.[[Initial]] is not none or q.[[Initial]] is not none, return false.
      7. Else if SameValue(p.[[Initial]], q.[[Initial]]) is false, then
        1. Return false.
      8. If SameTypeWithAssumptions(p.[[Type]], q.[[Type]], identified) is false, return false.
    9. If a.[[ThisType]] is none or b.[[ThisType]] is none, then
      1. If a.[[ThisType]] is not none or b.[[ThisType]] is not none, return false.
    10. Else if SameTypeWithAssumptions(a.[[ThisType]], b.[[ThisType]], identified) is false, then
      1. Return false.
    11. If the number of elements of a.[[Narrows]] is not the number of elements of b.[[Narrows]], return false.
    12. For each integer k such that 0 ≤ k and k < the number of elements of a.[[Narrows]], do
      1. Let m be a.[[Narrows]][k] and let n be b.[[Narrows]][k].
      2. If m.[[Target]] is not n.[[Target]], return false.
      3. If SameTypeWithAssumptions(m.[[Type]], n.[[Type]], identified) is false, return false.
    13. If a.[[Return]] is none or b.[[Return]] is none, then
      1. If a.[[Return]] is not none or b.[[Return]] is not none, return false.
    14. Else if SameTypeWithAssumptions(a.[[Return]], b.[[Return]], identified) is false, then
      1. Return false.
  3. Return true.
Note

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.

1.10.2.2 EffectiveSignatureReturn ( signature )

The abstract operation EffectiveSignatureReturn takes argument signature (a Signature Record) and returns a Type Record or none. It returns the completed result contract used by callable conformance. It performs the following steps when called:

  1. If signature.[[Return]] is not none, return signature.[[Return]].
  2. Return signature.[[InferredReturn]].

The publication rules of 1.6.2 store none for a completed unknown join; they do not publish the any type. An explicit any result annotation is retained. This operation does not complete an unfinished inference dependency, and an unfinished result must not be replaced by none merely to discharge a conformance obligation. Complete the dependencies required by 1.6.2.3 and 1.6.2.18 before finalizing that obligation.

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 published result still describes what its consumers can receive. Identity, interning, overload-set formation, ranking and declared-only viability continue to read the declared signature fields. Declaration identity alone cannot discharge a result obligation that depends on publication. This distinction applies recursively when a comparison reaches a callable contract; it does not relax writable-storage invariance or reference-liveness requirements.

A published result describes a function value, not an unwritten storage promise on a replaceable function declaration binding. For a store to that binding, including contextual typing of the replacement expression, use its declared signature with [[InferredReturn]] set to none. Preserve its declared parameters, explicit result, receiver and narrowing contracts. This does not erase the published contract of a function value passed to a consumer, or an explicit callable annotation on another binding. The existing binding-stability rules determine whether a later read may use the original function's published result.

1.10.2.3 IsSignatureSubtype ( a, b, assumptions )

The abstract operation IsSignatureSubtype takes arguments a (a Signature Record), b (a Signature Record), and assumptions (a List of Records that have [[First]] and [[Second]] fields) and returns a Boolean. It determines whether a function with the signature a may be used where the signature b is required. It performs the following steps when called:

  1. If a.[[Untyped]] is true, a.[[Return]] is none, a.[[InferredReturn]] is none, neither signature has a reference parameter, and b.[[Narrows]] is empty, return true.
  2. If a.[[TypeParameters]] is not empty and b.[[TypeParameters]] is empty, then
    1. Let bindings be the bindings of a.[[TypeParameters]] inferred from b's parameter types by the structural matching of 22.
    2. Let sourceResult be EffectiveSignatureReturn(a) with bindings substituted.
    3. Let targetResult be EffectiveSignatureReturn(b).
    4. If sourceResult and targetResult are not none, targetResult.[[Kind]] is not void, and sourceResult still contains unbound parameters of a, use structural matching against targetResult to infer those remaining parameters. Bindings already established by parameter matching are retained.
    5. Check every inferred binding by IsAssignable against its parameter's constraint after substituting the inferred bindings, including those on which a dependent constraint depends. If a parameter cannot be bound or a binding fails its constraint, return false. A completed result with no remaining inference variable is checked covariantly below, not required to be identical to targetResult.
    6. Set a to the Signature Record whose fields are those of a with each Type Record they hold replaced by the result of SubstituteType applied to it and bindings, and whose [[TypeParameters]] is empty.
  3. Else if a.[[TypeParameters]] is empty and b.[[TypeParameters]] is not empty, then
    1. Return false.
  4. Else if a.[[TypeParameters]] is not empty, then
    1. If a.[[TypeParameters]] and b.[[TypeParameters]] do not have the same number of elements, return false.
    2. For each integer k such that 0 ≤ k and k < the number of elements of a.[[TypeParameters]], do
      1. Let u be a.[[TypeParameters]][k].
      2. Let w be b.[[TypeParameters]][k].
      3. If u.[[Kind]] is not w.[[Kind]], or u.[[Variadic]] is not w.[[Variadic]], or u.[[Arity]] is not w.[[Arity]], return false.
      4. Append the Record { [[First]]: the parameter Type Record whose [[Declaration]] is u.[[Declaration]], [[Second]]: the parameter Type Record whose [[Declaration]] is w.[[Declaration]] } to assumptions.
      5. Append the Record { [[First]]: the parameter Type Record whose [[Declaration]] is w.[[Declaration]], [[Second]]: the parameter Type Record whose [[Declaration]] is u.[[Declaration]] } to assumptions.
    3. For each integer k such that 0 ≤ k and k < the number of elements of a.[[TypeParameters]], do
      1. Let u be a.[[TypeParameters]][k].
      2. Let w be b.[[TypeParameters]][k].
      3. If u.[[Constraint]] is not none, then
        1. If w.[[Constraint]] is none, return false.
        2. If IsSubtype(the Type Record w.[[Constraint]] denotes, the Type Record u.[[Constraint]] denotes, assumptions) is false, return false.
  5. If an argument sequence admitted by b has a statically established omission that a cannot bind, after the defaults supplied by the crossing have been applied, return false. A rest may supply no arguments; a required parameter whose type admits undefined can bind an omitted argument. Preserve the gradual admission of any. A fixed tuple rest is compared by its positional elements, retaining their defaults.
  6. Let identicalParameters be true.
  7. If a.[[Parameters]] and b.[[Parameters]] have different lengths, then
    1. Set identicalParameters to false.
  8. Else,
    1. For each integer k such that 0 ≤ k and k < the number of elements of a.[[Parameters]], do
      1. Let p be a.[[Parameters]][k].
      2. Let q be b.[[Parameters]][k].
      3. If p.[[Rest]] is not q.[[Rest]], p.[[Optional]] is not q.[[Optional]], or p.[[Reference]] is not q.[[Reference]], set identicalParameters to false.
      4. If one of p and q has a default and the other does not, set identicalParameters to false.
      5. If SameTypeWithAssumptions(p.[[Type]], q.[[Type]], assumptions) is false, set identicalParameters to false.
  9. If identicalParameters is false, then
    1. For each integer j such that 0 ≤ j and j < the number of elements of b.[[Parameters]], do
      1. Let q be b.[[Parameters]][j].
      2. If a.[[Parameters]] has no element at index j and no element of a.[[Parameters]] has [[Rest]] true, return false.
      3. Let p be the element of a.[[Parameters]] that receives the argument at index j, or, where more than one could receive it, the union of the types of those that could (1.10.4.2).
      4. If p.[[Reference]] is not q.[[Reference]], return false.
      5. Let pType be p.[[Type]], or the element type of p.[[Type]] where p.[[Rest]] is true.
      6. Let qType be q.[[Type]], or the element type of q.[[Type]] where q.[[Rest]] is true.
      7. If q.[[Optional]] is true, q.[[Rest]] is false, and q has no default supplied by the crossing, set qType to the union of qType and the undefined type.
      8. If p.[[Rest]] is false and p is optional or has a default, set pType to the union of pType and the undefined type.
      9. If p.[[Reference]] is true, then
        1. If SameTypeWithAssumptions(qType, pType, assumptions) is false, return false.
      10. Else,
        1. If IsAssignable(qType, pType) is false, return false.
  10. If a.[[ThisType]] is not none, then
    1. If b.[[ThisType]] is none, return false.
    2. If IsAssignable(b.[[ThisType]], a.[[ThisType]]) is false, return false.
  11. Else if b.[[ThisType]] is not none, then
    1. Return false.
  12. For each Narrowing Record n of b.[[Narrows]], do
    1. Identify the parameter denoted by n.[[Target]] with the corresponding parameter of a, or identify "this" with "this". Parameter names need not agree.
    2. If a and b do not declare the same narrowing form (Boolean test or void assertion), return false.
    3. If there is no corresponding Narrowing Record m of a.[[Narrows]], return false.
    4. If b declares a Boolean test, the narrowed sets of m.[[Type]] and n.[[Type]] must be equivalent within the parameter domain admitted by b; if that equivalence is not established, return false. SameType under assumptions after substitution is a sufficient conservative judgment.
    5. Otherwise, if IsSubtype(m.[[Type]], n.[[Type]], assumptions) is false, return false.
  13. Let bReturn be EffectiveSignatureReturn(b).
  14. If bReturn is none, return true.
  15. If bReturn.[[Kind]] is void, return true.
  16. Let aReturn be EffectiveSignatureReturn(a).
  17. If aReturn is none, return true.
  18. Return IsSubtype(aReturn, bReturn, assumptions).

Reference modes and narrowing guarantees participate in ordinary callable conformance, including assignments, callbacks, interface satisfaction and overrides. The untyped catch-all does not erase a reference parameter's required location or supply an undeclared narrowing fact. Reference parameters require arguments even when their referent annotation is any; a reference rest may collect an empty sequence. Explicit any at a value boundary retains its gradual meaning. This does not change the decay of a reference passed directly to a by-value parameter, and introduces no adaptation wrapper or additional reference-liveness permission.

Note 1

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 SequenceAssignment.

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.

Note 2

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 (4.6). A concrete a is not usable where a generic b is required, since <T: type>(x: T) => T promises every T and (x: uint8) => uint8 would return a uint8 for a string; the untyped catch-all of the first step is the one exception, as it is everywhere. A generic a is usable where a generic b is required when it is at least as general: the same parameters position by position, each constraint of a admitting everything the corresponding constraint of b admits, so <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 IsAssignable. A where on a's declaration is not consulted, because a function type has none; it is checked at the specialization the crossing performs.

A parameter and a [[ThisType]] are related by 1.2.3.2 and not by 1.2.3.1, because a parameter position is a BOUNDARY and any is bidirectional at a boundary. IsSubtype admits 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-typed position, so (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 8.1 states in each direction: a signature with none is usable nowhere a 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 (1.7.6.1), and an arrow, which adopts nothing, is refused here.

A return type keeps IsSubtype, and the asymmetry is deliberate. A return is covariant and is a PROMISE the function makes rather than a position a caller supplies, so a known specific result may stand where any is required by IsSubtype's ordinary target rule. A source explicitly returning any does not thereby satisfy a specific result promise.

Note 3

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 reference rule in 1.2.3.1.

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 catch-all step asks for [[InferredReturn]] as well as [[Untyped]] because the two are not the same question. [[Untyped]] is syntactic, so it is true of function g() { return f(); }, which declares neither a parameter type nor a return type; but g PARTICIPATES in inference all the same, since a contribution of it is anchored by f's declared return, and it therefore publishes a return type. 1.6.2 says subtyping and assignability read "the published one otherwise", and a catch-all asking only about [[Untyped]] returns before that reading can happen - so g would satisfy () => string while publishing uint32. The catch-all is for a signature there is nothing to judge BY, which is what [[Untyped]] alone does not establish. Where a signature is untyped and publishes nothing there is nothing to judge in any position, which is why the step precedes the arity and parameter steps as well: an untyped function naming parameters its caller does not supply is the ordinary shape of existing ECMAScript.

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 1.6.2 settles what that means: a join of the 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]] true and is admitted by the first step; one that participates and publishes nothing must be admitted here, or the two would be distinguishable after all - and distinguishable by a BODY, which is what [[Untyped]] being a syntactic property exists to prevent. 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 false and the assignment is refused. The unannotated function makes no promise to break. The two are different signatures for the same reason - which is why [[Return]] is none where none is declared rather than the 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 1.10.3.1, and the disagreement is worth stating. A call 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 parameter list widened. The design states the first rule and not the second, and this specification supplies the second in the shape ECMAScript's existing tolerance of extra arguments suggests. This specification settles it as the two rules answering two questions. A direct call is checked against what the author declared, where an extra argument is a mistake worth catching at the site that made it; assignability answers what a value can safely receive, where an extra argument is ignored by the language itself. Both rules stand as written, and a committee preferring one question's answer for both has each rule's consequences recorded above.

Example 1 (Informative)
interface I { m(): number; }
class C implements I {
  text: string = "s";
  m() { return this.text; }
}

The published String result of 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.

Example 2 (Informative)
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 published result becomes String under that binding. The union consumer is compatible by covariance, and the void consumer discards the result. The target result does not rebind T to a union or to void. Replacing the union target with number fails the result obligation.

1.10.2.4 IsFunctionSubtype ( s, t, assumptions )

The abstract operation IsFunctionSubtype takes arguments s (a function Type Record), t (a function Type Record), and assumptions (a List of Records that have [[First]] and [[Second]] fields) and returns a Boolean. It determines whether every value of the function type s is a value of the function type t. It performs the following steps when called:

  1. For each element b of t.[[Signatures]], do
    1. If there is no element a of s.[[Signatures]] such that IsSignatureSubtype(a, b, assumptions) is true, return false.
  2. Return true.
Note

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.

1.10.3 Overload Resolution

An overloaded name evaluates to a single function object that resolves at the call site. Its 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.

1.10.3.1 ResolveOverload ( signatures, args, contextualType )

The abstract operation ResolveOverload takes arguments signatures (a List of Signature Records), args (a List of arguments), and contextualType (a Type Record or none) and returns either a Signature Record, none, or ambiguous. It selects the signature a call invokes. It performs the following steps when called:

  1. Let viable be a new empty List.
  2. For each element declared of signatures, do
    1. Let sig be declared.
    2. If sig.[[TypeParameters]] is not empty, then
      1. Let bindings be the bindings of sig.[[TypeParameters]] from the call's explicit type arguments by BindTypeArguments (4.3) where the call supplies them, and otherwise, or for the parameters they leave unbound, from args by 17.
      2. If some parameter of sig.[[TypeParameters]] is left unbound, or binding it was a throw completion, then
        1. Continue.
      3. Set sig to the Signature Record whose fields are those of sig with each Type Record they hold replaced by the result of SubstituteType applied to it and bindings, and whose [[TypeParameters]] is empty.
    3. Let argumentBindings be BindArguments(sig, args) (1.10.4.4).
    4. If argumentBindings is unmatched, then
      1. Continue.
    5. If some element of args does not satisfy its receiving contract under argumentBindings as defined below, then
      1. Continue.
    6. If contextualType is not none, sig has a declared return type, and IsAssignable(sig.[[Return]], contextualType) is false, then
      1. Continue.
    7. Append sig to viable.
  3. If viable is empty, return none.
  4. For each element sig of viable, do
    1. Let rank be the greatest rank in Table 30 required by any element of args to match its receiving contract under the bindings that made sig viable.
    2. Let sig's worst rank be rank.
  5. Let best be the elements of viable whose worst rank is least.
  6. If best has more than one element, then
    1. NOTE: Specificity breaks the tie the ranks leave. For each argument, a parameter written as a concrete type is more specific than one written as a type parameter, and a parameter of a fixed position is more specific than an element received by an ordinary rest or a rest typed by a generic pack, read from the declared signature each element of best was instantiated from. Compare the positions actually receiving the arguments under BindArguments; merely containing a rest elsewhere does not make a signature less specific.
    2. Remove from best each element that some other element of best is more specific than for some argument, and that is more specific than that other element for no argument.
  7. If best has exactly one element, return that element.
  8. Return ambiguous.

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 ReferenceSequenceAdmits (1.10.4.3), including nested rests. Do not replace the leaf with a union of the tuple's element types. Retain the owning declared parameter as well: a leaf within a rest is still a rest position for specificity. Reuse the assignment that established viability when comparing candidates.

A reference receiving position requires the original reference contribution and the reference-argument judgment of 2.4. At run time, the referent must be live and satisfy the receiving type without conversion, and its available declared location type retains its invariant obligation. The current referent type and the invariant location type are distinct facts; a narrower current value does not narrow the location's contract. Unresolved static facts retain their existing deferred runtime judgment. Rank an admitted reference against its receiving leaf type under the existing ranks. A value receiving position instead judges and ranks the decayed value under the ordinary value rules. An explicit reference contributes no additional preference between otherwise equally ranked candidates; existing specificity and ambiguity rules still apply.

Overload selection does not evaluate value parameter initializers or create replacement borrowed locations. The dispatcher forwards the original arguments and locations to the selected function; it is not itself a value parameter boundary. Final parameter binding and later accesses retain their independent admission and liveness checks, including after an intervening argument effect or initializer. This does not change the value boundaries of call, apply, or bind.

Table 30: Argument Match Ranks
RankThe argument matches its parameter by
1Being of exactly the parameter's type.
2Satisfying a nominal parameter type structurally, where the argument's type is not that type and does not refine it.
3Being an untyped literal that takes the parameter's type, ordered among competing parameter types by Table 21.
4A user-defined conversion (1.6.8).
5Binding to a signature whose [[Untyped]] is true.
Note 1

Rank 2 separates two ways of matching that are otherwise indistinguishable, and without it a pair of signatures a reader would consider plainly ordered is ambiguous for every call:

  1. interface I { a: uint8 } and type O = { a: uint8 } as two signatures of one name, called with { a: 1 }.

The argument has the shape exactly, which is rank 1 against O; it satisfies I by having the members, which is what an interface-typed position accepts and is now rank 2. The exact match wins, which is the answer a reader expects and the one the languages that face this question give.

Rank 2 does not reach a class that DECLARES the interface: such a class refines it (1.2.3.1), so it matches at rank 1 as any subtype does, and a declaring class is not made to lose to an unrelated structural signature. Nor does it order two nominal parameter types against each other: two interfaces of one shape are each satisfied structurally by the same argument, both at rank 2, and the call remains ambiguous - which is correct, since neither is a better answer than the other.

It is a type error if ResolveOverload returns none, and it is a type error if it returns ambiguous. Where an argument has the any type the resolution is performed at run time on the argument's runtime type, and the same errors are thrown as a TypeError there.

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 type error at the second. Declaring nothing is a declaration of nothing, not an unknown. It is a type error to declare a signature that is viable for the same argument list as an existing one at the same rank, after specificity and declared return discrimination leave them tied. Two generic signatures that are the same up to renaming (1.10.1) are one signature written twice, and are the same error: 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.

Note 2

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 typed argument never ranks below 1: a typed argument of the wrong type is not viable at all, so it is never a question of how well it matches.

An untyped function is a catch-all because its signature has [[Untyped]] true, which ranks last. Given function f() {} and function f(a: uint8) {}, the call f(1) selects the typed signature, since the literal ranks 2 against the catch-all's 4, while f(1, 2) selects the untyped one, since the typed signature's arity does not accept two arguments and it is not viable. A function that takes no arguments and is nonetheless typed is written by declaring the return type, as function f(): void {}, which makes [[Untyped]] false and so makes 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.

1.10.4 Named Arguments

An argument at a call site may be written as a name and a value, 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 (1.10.1) are what a named argument selects by. A required parameter that follows an optional or a defaulted one is reached by name: f(b: 1) supplies b and leaves a to its default, so such a declaration is well formed and needs no ordering rule.

The Arguments grammar admits a named argument alongside the positional and spread forms. A named argument is an IdentifierName followed by : and an AssignmentExpression, at the top of an argument. This is new syntax: a call argument that is an identifier followed by : 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.

ArgumentList[Yield, Await] : AssignmentExpression[+In, ?Yield, ?Await] ... AssignmentExpression[+In, ?Yield, ?Await] ArgumentList[?Yield, ?Await] , AssignmentExpression[+In, ?Yield, ?Await] ArgumentList[?Yield, ?Await] , ... AssignmentExpression[+In, ?Yield, ?Await] Argument[?Yield, ?Await] ArgumentList[?Yield, ?Await] , Argument[?Yield, ?Await] Argument[Yield, Await] : AssignmentExpression[+In, ?Yield, ?Await] ... AssignmentExpression[+In, ?Yield, ?Await] NamedArgument[?Yield, ?Await] NamedArgument[Yield, Await] : IdentifierName : AssignmentExpression[+In, ?Yield, ?Await]

This is the first restatement of ArgumentList, factoring the pinned edition's four alternatives through an Argument element exactly as FormalParameters is factored above, so that a new argument form is one added alternative rather than two. The references extension adds one such alternative, the ref argument, by restating Argument (2.1); per 1.1 that later block is the production's complete form.

A call is bound to the parameters of the selected signature by BindArguments (1.10.4.4). It is a type error if a named argument's name is not a parameter of the signature, and it is a type error if BindArguments returns unmatched.

For a named or object-spread call to an overload set, evaluate the argument contributions once in source order, preserving each contribution's name where present, source identity, value or original reference, and forwarding information. BindArguments is applied separately to each candidate; a losing candidate does not replay argument expressions, getters or iteration, run value parameter initializers, or convert an argument to make it viable. Preserve the candidate's successful binding map with its signature. Ranking and specificity compare the same supplied contribution at its receiving contract in each candidate, even where names place it at different parameter indices. Omitted defaults contribute no argument rank. Where repeated names replace an earlier assigned value under BindArguments, the earlier expression is still evaluated but its replaced contribution does not participate in ranking.

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 2.4; a value position uses the referent under its ordinary value-admission rule. Preserve the original reference through mapping so that final parameter binding still performs its required reference or value operation. Name placement does not require a storage read. Evaluating argument expressions, admitting their results, and binding parameters do not grant a reference new escape permissions, and successful admission does not establish liveness after intervening effects, including earlier parameter initializers. These requirements also apply when distributing non-final or multiple rests and forwarding a reference run.

The signature selected is the signature in view. Where the callee expression is a reference whose binding has a declared type that is a function Type Record, or a nominal interface type whose structural form is one (1.4.2.9), the signature in view is selected from that type's [[Signatures]] by ResolveOverload, binding each candidate against the supplied names and positions before judging viability and ranking. A candidate is not selected merely because its parameters include the names; if no candidate is viable or no unique best candidate remains, the call is a type error. The call is bound against it as follows, and the callee receives the resulting positional list. Named arguments pin their parameters first. A positional argument fills the next unpinned position. A named argument fills the parameter bearing its name; it is a type error if no parameter does. A parameter filled by neither takes the signature's [[Initial]] where it has one, is left undefined where it is optional or a rest, and is otherwise a type error. An argument at a position whose parameter type is not any, where the argument is not an Object, is converted to that type as at a declared parameter (1.6). Where the callee expression has no such declared type in view, the signature in view is the callee function's own parameter list, read as before, and a skipped position is left undefined for the callee's own default.

Note 1

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 (1.8.2), which is how a call through a parameter finds the signature in view.

1.10.4.1 Named Arguments of Intrinsic Functions

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 replacement function's declaration. A declared callable signature in view retains the names, defaults and admission rules specified above.

The following declarations apply to the intrinsic Range methods and synchronous Iterator helpers. Parameters are listed in order. Unless marked optional, each must be supplied in a named or object-spread call. These are value parameters, with no reference rest.

Table 31: Intrinsic Named Parameter Declarations
Intrinsic familyMethodParameters
Rangecontainsoptional value
Rangeintersectother
Rangescalefactor
Rangestepoptional by
Range and Iteratormap, filter, flatMap, forEach, some, every, findcallback
Range and Iteratorreducecallback, optional initial
Range and Iteratortake, dropcount

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.

Evaluate argument contributions and apply BindArguments in the ordinary order. When materializing the positional argument List for an intrinsic's own declaration, omit an unfilled optional trailing position; supply undefined for an unfilled optional position preceding a supplied later position. An explicitly supplied undefined remains present. Thus a call to reduce naming only callback has no initial accumulator argument, whereas a call also naming initial with value undefined has one. This rule does not change materialization under a declared callable signature in view.

Argument binding adds no conversion to an inferred receiver-dependent type. After binding, perform the selected intrinsic's ordinary receiver, value and callback admission, iterator operations and abrupt-completion handling. Retain original reference locations through binding and perform their existing value decay and liveness checks at the native boundary. No listed method admits a reference-rest forwarding occurrence, including an empty one. A Range helper delegates its materialized positional List to the normally selected Iterator helper; it does not bind the outer names a second time. Named parameter metadata alone supplies no generic callable capability and does not authorize ignoring explicit type arguments.

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 BindArguments applies to a positional call.

1.10.4.2 SequenceAssignment ( slots, n, admits [ , admitsRun ] )

The abstract operation SequenceAssignment takes arguments slots (a List of Records with a [[Rest]] field (a Boolean) and an [[Optional]] field (a Boolean)), n (a non-negative integer), and admits (an Abstract Closure taking a non-negative integer and a Record of slots and returning a Boolean) and optional argument admitsRun (an Abstract Closure taking two non-negative integers and a Record of slots and returning a Boolean) and returns either a List of non-negative integers or unmatched. It distributes n items among slots in order, returning how many items each slot takes, or unmatched where no distribution admits every item. A slot whose [[Rest]] is true takes zero or more items, one whose [[Optional]] is true takes one or none, and any other takes exactly one. admits(i, slot) reports whether the item at index i may be taken by slot. It performs the following steps when called:

  1. If slots is empty, then
    1. If n is 0, return a new empty List.
    2. Return unmatched.
  2. Let slot be slots[0].
  3. Let tail be a List of the elements of slots from index 1.
  4. Let limit be 0.
  5. Repeat, while limit < n and admits(limit, slot) is true and (slot.[[Rest]] is true or limit < 1),
    1. Set limit to limit + 1.
  6. Let min be 0.
  7. If slot.[[Rest]] is false and slot.[[Optional]] is false, set min to 1.
  8. For each integer m from limit down to min, in descending order, do
    1. If admitsRun is present and admitsRun(0, m, slot) is false, continue.
    2. Let shifted be a new Abstract Closure with parameters (index, which) that captures admits and m and performs the following steps when called:
      1. Return admits(index + m, which).
    3. Let rest be unmatched.
    4. If admitsRun is present, then
      1. Let shiftedRun be a new Abstract Closure with parameters (start, count, which) that captures admitsRun and m and performs the following steps when called:
        1. Return admitsRun(start + m, count, which).
      2. Set rest to SequenceAssignment(tail, n - m, shifted, shiftedRun).
    5. Else,
      1. Set rest to SequenceAssignment(tail, n - m, shifted).
    6. If rest is not unmatched, return the list-concatenation of « m » and rest.
  9. Return unmatched.
Note

This is leftmost-greedy matching with backtracking, which is what the design means by taking arguments greedily and giving them back to satisfy the signature: a slot takes as many items as it can and yields one at a time until the slots after it can be satisfied. A parameter list, and a tuple's element list, is a regular pattern over a sequence — a fixed slot is one item, an optional slot is one or none, a rest is zero or more — and this operation is the match.

The result is deterministic. Because the search takes the greatest count first at each slot in turn, the assignment returned is the greatest in the lexicographic order on the count lists, so it does not depend on how an implementation searches; an implementation may use any method that agrees with it. The recursion terminates because each call passes a shorter slots.

The design's own example is the one to check the rule against. For function f(...a: [].<uint32>, ...b: [].<uint32>, c: uint32) called f(0, 1, 2), the first slot takes all three, the tail cannot be satisfied, it yields to two, 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.

1.10.4.3 ReferenceSequenceAdmits ( t, run, admits )

The abstract operation ReferenceSequenceAdmits takes arguments t (a Type Record), run (a List of arguments), and admits (an Abstract Closure taking an argument and a Type Record and returning a Boolean) and returns a Boolean. It checks a reference rest's collected sequence, using admits for invariant reference admission at each receiving position. The Type Record is the resolved collected contract, after applicable generic substitutions. It performs the following steps when called:

  1. If t.[[Kind]] is array, then
    1. If t.[[Extent]] is an integer and the number of elements of run is not t.[[Extent]], return false.
    2. For each element arg of run, do
      1. If admits(arg, t.[[Element]]) is false, return false.
    3. Return true.
  2. If t.[[Kind]] is tuple, then
    1. Let slots be a new empty List.
    2. For each element e of t.[[Elements]], do
      1. Append the Record { [[Rest]]: e.[[Rest]], [[Optional]]: e.[[Initial]] is not none, [[Element]]: e } to slots.
    3. Let one be a new Abstract Closure with parameters (index, slot) that captures run and admits and performs the following steps when called:
      1. If slot.[[Rest]] is true, return true.
      2. Return admits(run[index], slot.[[Element]].[[Type]]).
    4. Let many be a new Abstract Closure with parameters (start, count, slot) that captures run and admits and performs the following steps when called:
      1. If slot.[[Rest]] is false, return true.
      2. Let part be the sublist of run starting at start and containing count elements.
      3. Return ReferenceSequenceAdmits(slot.[[Element]].[[Type]], part, admits).
    5. Return true if SequenceAssignment(slots, the number of elements of run, one, many) is not unmatched, and false otherwise.
  3. For each element arg of run, do
    1. If admits(arg, t) is false, return false.
  4. Return true.

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 Reference Value, checks its live referent without conversion and preserves its available invariant location contract. A defaulted tuple position permits omission but supplies no location: a reference rest collects only the supplied borrows, creates no temporary for an omitted default, and retains bounds checks on subsequent indexing. A successful match is not proof of liveness after an intervening effect.

1.10.4.4 BindArguments ( sig, args )

The abstract operation BindArguments takes arguments sig (a Signature Record) and args (a List of arguments) and returns either a List of bindings or unmatched. It binds a call's arguments to a signature's parameters, giving each parameter the value or values it receives. It performs the following steps when called:

  1. For each named argument a of args, in order, do
    1. Let p be the parameter of sig.[[Parameters]] whose [[Name]] is a's name; if there is none, return unmatched.
    2. Assign a's value to p, and where p.[[Rest]] is true, assign to p the positional arguments following a up to the next named argument as well.
  2. If a positional argument after a named argument was not assigned to a rest parameter, return unmatched.
  3. Let positional be the positional arguments of args that precede the first named argument, in order.
  4. For each rest parameter r that a named argument assigned to, in parameter order, do
    1. Let required be the parameters not yet assigned to that are neither optional nor rest parameters and have no initial value.
    2. Let shortfall be max(0, the number of elements of required minus the number of elements of positional).
    3. Let nextRest be the next rest parameter after r that a named argument assigned to, if there is one.
    4. Let trailing be the elements of required that follow r in parameter order and precede nextRest if it exists.
    5. Let count be the minimum of shortfall, the number of elements of trailing, and the number of values assigned to r.
    6. Remove the last count values assigned to r and assign them, in order, to the last count parameters of trailing. These parameters are now assigned to.
  5. Let slots be a List containing, for each parameter p of sig.[[Parameters]] not yet assigned to, a Record { [[Rest]]: p.[[Rest]], [[Optional]]: p.[[Optional]] or p.[[Initial]] is not none }, in parameter order.
  6. Let admits be a new Abstract Closure with parameters (index, which) that captures positional and performs the following steps when called:
    1. Let p be the parameter which stands for.
    2. If p is a reference rest parameter, return true.
    3. Let target be p.[[Type]], or the element type of p.[[Type]] where p.[[Rest]] is true.
    4. If p is a reference parameter, return true if positional[index] satisfies the reference-argument judgment for target of 2.4, and false otherwise.
    5. Return IsAssignable(the Static Type of positional[index] in contextual type target, target).
  7. Let admitsRun be a new Abstract Closure with parameters (start, count, which) that captures positional and performs the following steps when called:
    1. Let p be the parameter which stands for.
    2. If p is not a reference rest parameter, return true.
    3. Let run be the sublist of positional starting at start and containing count elements.
    4. Let referenceAdmits be a new Abstract Closure with parameters (arg, target) that performs the following steps when called:
      1. Return true if arg satisfies the reference-argument judgment for target of 2.4, and false otherwise.
    5. Return ReferenceSequenceAdmits(the collected Type Record of p, run, referenceAdmits).
  8. Let counts be SequenceAssignment(slots, the number of elements of positional, admits, admitsRun).
  9. If counts is unmatched, return unmatched.
  10. Assign to each such parameter, in order, the number of elements of positional that counts gives it, a reference rest receiving their original locations, a value rest receiving an array of its type, and any other receiving the one element.
  11. For each parameter p of sig.[[Parameters]] that received nothing, do
    1. If p.[[Initial]] is not none, assign p.[[Initial]] to p.
    2. Else if p is a reference rest parameter, assign an empty reference run to p.
    3. Else if p.[[Rest]] is true, assign an empty array of p.[[Type]] to p.
    4. Else if p.[[Optional]] is false, return unmatched.
  12. Check each reference rest's assigned run against its complete collected contract using ReferenceSequenceAdmits; if any judgment fails, return unmatched.
  13. Return the bindings.
Note

A named argument pins its parameter before the positional match runs, so it prunes the search rather than widening it, and the positionals that follow a named rest join that rest as the paragraph above says. A rest that receives nothing is bound to an empty array rather than left unfilled, which is what makes b an empty array rather than an error in the design's example.

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 iterable fills positions in order, as in the base language. Its static argument facts are the iteration contribution of 1.14.8, rather than facts assumed from indexed storage. Because the two are distinguished by the presence of an iterator, an object used to supply arguments by name is one that is not iterable; an iterable, including an Array, spreads by position.

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. Evaluate each argument expression once and finish its contributions before evaluating the next argument. A reference run retains the forwarding rules of 1.4.4, including an empty run's occurrence; it does not undergo property enumeration. A tuple Composite iterates by kind under 25.7.2. For any other Object spread value, perform GetMethod with %Symbol.iterator% once. If it returns undefined (including a nullish property value), use named properties. Otherwise use GetIteratorFromMethod with that already obtained method; a non-callable property or abrupt lookup is an abrupt completion, not a fallback to named properties. Non-Object values retain their ordinary iteration requirements.

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 Get once and append the resulting named contribution. An earlier getter can remove a later property or change its enumerability or value before it is visited. Newly added keys absent from the captured key list are not visited. Do not enumerate first and then replay getters, and do not repeat collection for candidate signatures. Repeated fixed names retain BindArguments' sequential replacement rule; earlier expressions and getters still run.

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 IteratorStepValue behaviour, including its abrupt-completion behaviour; selecting a spread protocol does not add an IteratorClose when the base argument-spread evaluation would not perform one. An empty ordinary iterable or an object contributing no names supplies no argument and creates no default placeholder or named-rest scope. An empty reference run still carries its separate forwarding occurrence.

Where the called value is overloaded (1.10.3), the parameter names are those of the selected signature, and a named or object-spread call that no signature can bind is a type error by the same rule as a positional call that matches no signature.

Note 2

Named arguments do not change a signature's identity: a parameter's name is carried but is not compared (1.10.1), so the same function reached through two declarations with different parameter names is one type, and a named call selects by whichever names the interned signature carries.

1.10.5 Overloading on Return Type

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 participates in viability, through the contextual type of the call (1.6.5). The return type read here is the DECLARED one: a published inferred return type (1.6.2) does not make a signature viable or non-viable, so which signature a call selects never depends on a function body.

The contextual type of a call is the type its position requires. Where a call has no contextual type and more than one signature remains viable, the call is ambiguous and it is a type error.

Note

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 contextual type. 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 contextual type. The circularity is real and is resolved by rejecting the call rather than by guessing.

Return-type overloading exists for the operators. A SIMD comparison returns either a vector of lane results or a compact mask, which Intel's intrinsics distinguish by name and which real types cannot: 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 contextual type is an error rather than a default.

1.10.6 The Literal Ranking Completed

Table 21 orders the numeric types a literal may take. It is completed here for the types it omits, which are the ones a call may nonetheless select.

Table 32: Literal Ranking of the Remaining Numeric Types
TypeRank
numberAhead of float64, and so ahead of every other numeric type.
int.<N> and uint.<N> for an N that is not a named shorthandAmong 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 integer type. A wider N before a narrower one.
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.
Note

Ranking number first is the repair 1.6.9 records. The design's ranking begins at 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 contextual type, and the type an untyped program computes with. Without it, 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: 1.13.1 shows every untyped call into the overloaded standard library keeps its meaning only because number ranks first, so the ranking stands as a back-compatibility condition rather than a preference.

Placing the rationals after the integers keeps a literal that is an integer selecting an integer signature, which is what a program that declares both would expect. Within a family a wider width ranks first, following the design's own order, which prefers the type that can represent the most.

1.10.7 Arrow Functions

An arrow function's parameters may carry annotations and its body may carry a return annotation. The annotations occupy the positions 1.4.4 gives them, within the cover grammar the arrow syntax already uses.

ArrowFunction[In, Yield, Await] : ArrowParameters[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt [no LineTerminator here] => ConciseBody[?In]

When the production ArrowParameters : CoverParenthesizedExpressionAndArrowParameterList is refined using ArrowFormalParameters, a parameter may carry the annotations of 1.4.4.

Note

(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.

1.10.8 Annotations on the Remaining Function Forms

Every function-shaped production takes the return annotation the same way: a TypeAnnotation between the parameter list and the body. The productions below restate their ECMAScript definitions with that insertion (1.1); parameter annotations need no insertion here, since a parameter of any of these forms is a FormalParameter or BindingElement and carries its annotation there. A method takes WhereClauses the same way a function declaration does (16), between that annotation and the body: a generic method specializes as a generic function does, so the rule 4.10 gives - "checked at each specialization once its parameters are bound" - applies unchanged. A method's clause may name the parameters of its CLASS as well as its own, since both are bound at the specialization the clause is checked at.

MethodDefinition[Yield, Await] : ClassElementName[?Yield, ?Await] TypeParameters[?Yield, ?Await]opt ( UniqueFormalParameters[~Yield, ~Await] ) TypeAnnotation[?Yield, ?Await]opt WhereClauses[?Yield, ?Await]opt { FunctionBody[~Yield, ~Await] } GeneratorMethod[?Yield, ?Await] AsyncMethod[?Yield, ?Await] AsyncGeneratorMethod[?Yield, ?Await] get ClassElementName[?Yield, ?Await] ( ) TypeAnnotation[?Yield, ?Await]opt { FunctionBody[~Yield, ~Await] } set ClassElementName[?Yield, ?Await] ( PropertySetParameterList ) { FunctionBody[~Yield, ~Await] } GeneratorMethod[Yield, Await] : * ClassElementName[?Yield, ?Await] TypeParameters[?Yield, ?Await]opt ( UniqueFormalParameters[+Yield, ~Await] ) TypeAnnotation[?Yield, ?Await]opt { GeneratorBody } AsyncMethod[Yield, Await] : async [no LineTerminator here] ClassElementName[?Yield, ?Await] TypeParameters[?Yield, ?Await]opt ( UniqueFormalParameters[~Yield, +Await] ) TypeAnnotation[?Yield, ?Await]opt { AsyncFunctionBody } AsyncGeneratorMethod[Yield, Await] : async [no LineTerminator here] * ClassElementName[?Yield, ?Await] TypeParameters[?Yield, ?Await]opt ( UniqueFormalParameters[+Yield, +Await] ) TypeAnnotation[?Yield, ?Await]opt { AsyncGeneratorBody } GeneratorDeclaration[Yield, Await, Default] : function * BindingIdentifier[?Yield, ?Await] ( FormalParameters[+Yield, ~Await] ) TypeAnnotation[?Yield, ?Await]opt { GeneratorBody } [+Default] function * ( FormalParameters[+Yield, ~Await] ) TypeAnnotation[?Yield, ?Await]opt { GeneratorBody } AsyncFunctionDeclaration[Yield, Await, Default] : async [no LineTerminator here] function BindingIdentifier[?Yield, ?Await] ( FormalParameters[~Yield, +Await] ) TypeAnnotation[?Yield, ?Await]opt { AsyncFunctionBody } [+Default] async [no LineTerminator here] function ( FormalParameters[~Yield, +Await] ) TypeAnnotation[?Yield, ?Await]opt { AsyncFunctionBody } AsyncGeneratorDeclaration[Yield, Await, Default] : async [no LineTerminator here] function * BindingIdentifier[?Yield, ?Await] ( FormalParameters[+Yield, +Await] ) TypeAnnotation[?Yield, ?Await]opt { AsyncGeneratorBody } [+Default] async [no LineTerminator here] function * ( FormalParameters[+Yield, +Await] ) TypeAnnotation[?Yield, ?Await]opt { AsyncGeneratorBody } FunctionExpression : function BindingIdentifier[~Yield, ~Await]opt ( FormalParameters[~Yield, ~Await] ) TypeAnnotation[~Yield, ~Await]opt { FunctionBody[~Yield, ~Await] } GeneratorExpression : function * BindingIdentifier[+Yield, ~Await]opt ( FormalParameters[+Yield, ~Await] ) TypeAnnotation[+Yield, ~Await]opt { GeneratorBody } AsyncFunctionExpression : async [no LineTerminator here] function BindingIdentifier[~Yield, +Await]opt ( FormalParameters[~Yield, +Await] ) TypeAnnotation[~Yield, +Await]opt { AsyncFunctionBody } AsyncGeneratorExpression : async [no LineTerminator here] function * BindingIdentifier[+Yield, +Await]opt ( FormalParameters[+Yield, +Await] ) TypeAnnotation[+Yield, +Await]opt { AsyncGeneratorBody } AsyncArrowFunction[In, Yield, Await] : async [no LineTerminator here] AsyncArrowBindingIdentifier[?Yield] TypeAnnotation[?Yield, ?Await]opt [no LineTerminator here] => AsyncConciseBody[?In] CoverCallExpressionAndAsyncArrowHead[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt [no LineTerminator here] => AsyncConciseBody[?In]

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 4.

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 site rather than deducing it from a value. An accessor of a generic class reads that class's parameters as any other body of the declaration does; what it may not do is introduce parameters of its own.

A set accessor declares no return type, so its production is restated unchanged; its single parameter is annotated through PropertySetParameterList. When CoverCallExpressionAndAsyncArrowHead is refined using AsyncArrowHead, a parameter may carry the annotations of 1.4.4, exactly as the synchronous cover is refined in 1.10.7. The static and runtime semantics of an annotation on any of these forms are those of 1.10: the annotation gives the declared return type, an async function's annotation types the promise the call returns, and a generator's annotation types the values the iterator yields. A generator's annotation is SHORTHAND: 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 void, which states truly that a generator yields and does not return, and there is no equally supportable filler for a REJECT type — 1.6.2 declines to infer one for the same reason. It is a Syntax Error for an async function to declare a return type that is not a promise type.

Note

The design writes async function f(): Promise.<uint8, Error> and typed generator methods throughout; this clause is where those parses come from. A class constructor is the one method that takes no return annotation, which 1.11.2 states.

1.11 Classes, Interfaces, and Enums

This clause defines the nominal and structural types a program declares: the object type and its identity and subtyping, which complete the last cases 1.2 defers; the rules that make a class a value type; interfaces; enums; and the sealed hierarchy that gives a closed union.

1.11.1 Object Types

It is a type error for a reached ordinary property operation to have a participating receiver whose Static Type consists only of null and undefined alternatives. Named and computed reads, stores, updates, deletion and invocation through a member share this receiver judgment, independently of whether the property key is known. Participation follows 1.9.7. Other primitive receivers retain ordinary boxing. An optional chain contributes no operation on its skipped path; parentheses end the chain, so an outer ordinary access checks the result it actually receives. Existing declared-member requirements on unions are unchanged. Unknown receivers retain runtime checking, and this judgment performs no property-key evaluation or reference read and changes no reference-liveness requirement.

1.11.1.1 Typed Own-Property Initializers

PropertyDefinition[Yield, Await] : ( PropertyName[?Yield, ?Await] TypeAnnotation[?Yield, ?Await] ) : AssignmentExpression[+In, ?Yield, ?Await]

The annotation in { (x: uint8): 1 } declares the own property's storage type and supplies the contextual type of its initializer. It is a type error if the initializer's Static Type, after contextual literal propagation, is known and is not assignable to that declared type. The literal-fit and metadata rules of the declared type apply at this boundary. The judgment belongs to each definition independently of an outer object annotation or the final inferred shape, including when a later definition or spread overwrites the property, when the literal is discarded, and when it supplies a Composite.

At evaluation the property key and initializer are evaluated once, in their ordinary property-definition order. The value crosses the declared property boundary of Table 23 before the property is installed. An any-typed initializer is checked there at runtime. The checking pass does not evaluate an ordinary computed key or initializer to decide assignability. The definition's context also governs decimal, rational and other representation-sensitive literal processing; inferring the final object shape does not replace that processing.

For an ordinary own property with an established declared storage type, OrdinaryGetOwnProperty preserves its Type Object in an additional [[Type]] field of the returned Property Descriptor. The declaration comes from the property's typed-storage metadata, not from the current value or an inherited property. An explicitly declared any remains a declared type; an untyped own property does not acquire a [[Type]] field merely because its value has a known type.

FromPropertyDescriptor, when the descriptor has a [[Type]] field, additionally creates an own data property named "type" on its result with that Type Object as its value and with [[Writable]], [[Enumerable]] and [[Configurable]] all true. Thus the ordinary Object and Reflect own-descriptor operations preserve the existing declared-type metadata. This field is part of the fresh descriptor result: changing it does not change the source property's storage contract. A subsequent definition using the descriptor performs that definition's ordinary type validation, admission, conversion and defaulting, and cannot replace a protected backing type or acquire reference authority through reflection. Observing this metadata does not read the described property's value through a getter, run a conversion or extend reference liveness.

A Property Type Record describes one member of an object type. It has a [[Key]] (a property key), a [[Type]] (a Type Record), an [[Optional]] field (a Boolean), a [[Readonly]] field (a Boolean), and an [[Initial]] field (an ECMAScript language value or none, the member's declared default).

An Index Signature Record has a [[KeyType]] and a [[ValueType]], each a Type Record. No production of the type grammar writes one: an object type acquires index signatures through construction (1.12.5), they take part in identity below and in keyof (1.4.2.5), and their membership and subtype rules are parked with the objects work that will write the source syntax.

1.11.1.2 SameObjectType ( s, t, assumptions )

The abstract operation SameObjectType takes arguments s (an object Type Record), t (an object Type Record), and assumptions (a List of Records that have [[First]] and [[Second]] fields) and returns a Boolean. It determines whether s and t are the same object type. It performs the following steps when called:

  1. If s.[[Properties]] and t.[[Properties]] do not have the same number of elements, return false.
  2. For each element p of s.[[Properties]], do
    1. Let q be the element of t.[[Properties]] whose [[Key]] is p.[[Key]], or empty if there is none.
    2. If q is empty, return false.
    3. If p.[[Optional]] is not q.[[Optional]], return false.
    4. If p.[[Readonly]] is not q.[[Readonly]], return false.
    5. If p.[[Initial]] is none or q.[[Initial]] is none, then
      1. If p.[[Initial]] is not none or q.[[Initial]] is not none, return false.
    6. Else if SameValue(p.[[Initial]], q.[[Initial]]) is false, then
      1. Return false.
    7. If SameTypeWithAssumptions(p.[[Type]], q.[[Type]], assumptions) is false, return false.
  3. If s.[[IndexSignatures]] and t.[[IndexSignatures]] do not have the same number of elements, return false.
  4. For each integer j such that 0 ≤ j and j < the number of elements of s.[[IndexSignatures]], do
    1. Let a be s.[[IndexSignatures]][j].
    2. Let b be t.[[IndexSignatures]][j].
    3. If SameTypeWithAssumptions(a.[[KeyType]], b.[[KeyType]], assumptions) is false, return false.
    4. If SameTypeWithAssumptions(a.[[ValueType]], b.[[ValueType]], assumptions) is false, return false.
  5. Return true.
Note

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 object Type Record by key, which is what allows the interning of 1.2.2.9 to give them one Type Object, and orders the [[IndexSignatures]] by the fixed total order on their [[KeyType]]s. That ordering serves identity; whether the source's own member order should additionally be preserved for display and reflection, carried outside the canonical form the way a parameter's name already is (1.10.1), is settled against a second channel: interning merges every spelling of a type into one object, so a non-canonical order would be the first spelling's, an accident of module load order, and the determinism this specification enforces elsewhere forbids exactly that kind of accident. A reflected object type lists its properties in key order whatever order the source wrote.

1.11.1.3 IsObjectSubtype ( s, t, assumptions )

The abstract operation IsObjectSubtype takes arguments s (an object Type Record), t (an object Type Record), and assumptions (a List of Records that have [[First]] and [[Second]] fields) and returns a Boolean. It determines whether every value of the object type s is a value of the object type t.

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 is a String or a Symbol. An optional target still permits absence. The same presence requirement applies to readonly targets; readonly affects value variance rather than existence.

  1. For each element q of t.[[Properties]], do
    1. Let p be the element of s.[[Properties]] whose [[Key]] is q.[[Key]], or empty if there is none.
    2. If p is empty, then
      1. If q.[[Optional]] is false, return false.
      2. Continue.
    3. If p.[[Optional]] is true and q.[[Optional]] is false, return false.
    4. If q.[[Readonly]] is false and p.[[Readonly]] is true, return false.
    5. If q.[[Readonly]] is true, then
      1. If IsSubtype(p.[[Type]], q.[[Type]], assumptions) is false, return false.
    6. Else,
      1. If SameTypeWithAssumptions(p.[[Type]], q.[[Type]], assumptions) is false, return false.
  2. For each element y of t.[[IndexSignatures]], do
    1. Let x be an element of s.[[IndexSignatures]] such that SameType(x.[[KeyType]], y.[[KeyType]]) is true and IsSubtype(x.[[ValueType]], y.[[ValueType]], assumptions) is true, or empty if there is none.
    2. If x is not empty, continue.
    3. If s.[[IndexSignatures]] is not empty, return false.
    4. For each element p of s.[[Properties]], do
      1. If y accepts p.[[Key]] and t.[[Properties]] contains no element whose [[Key]] is p.[[Key]], then
        1. If IsSubtype(p.[[Type]], y.[[ValueType]], assumptions) is false, return false.
  3. Return true.
Note

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 covered by one of s's own, or - where s declares none - by s's properties. 1.4.2.9 gives the meaning the second case follows: { [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 (1.7.7) and what an object literal is already checked against (freshly); stating it here is what lets a value that has a type reach such a position, and not only one written in place.

The value type is covariant in both cases, which is not the rule a writable named member follows. A signature does not name a slot a program can be relied on to write through: a property it admits is admitted by its key, not declared, so the layout argument below does not reach it. The two cases agree deliberately - a source that declares a signature and one that does not should not be judged differently.

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 slot the program believes holds a Meter. This is the same rule the reference and array kinds already follow in 1.2.3.1, and it is the point at which this proposal parts company with an erasable type system, which can afford covariance there because nothing depends on the claim. Here the layout does.

A boundary that accepts a value into a position of an object type does not impose that type's members on the value. The value's own properties keep the types they were declared with, and a property the value declared without a type keeps the any type.

Note

This answers the question 1.7.6.1 raises and it answers it in the direction that admits the unsoundness, because the alternative is worse. Imposing the target's member types would mean that passing a plain object to a { 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 field's type is a property of the layout.

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 field of a sealed typed class, whose write is a type error after construction. Elsewhere the narrowing does not hold, since o.f may be a getter or may be assigned in between, and 1.8.5 therefore narrows bindings rather than paths.

1.11.2 Typed Classes

An imported class retains the contracts established in its declaration's lexical context, including instance and static members, constructor signatures, inherited and partial contributions, and implemented interfaces. Resolving those contracts in a consumer does not rebind their free names to the consumer's declarations or require the consumer to import the class's dependencies. Preserve the producing declaration's descriptions and source context when completing available actual identities or required computations; a consumer must not replace the producer's declaration evidence with a description formed in the consumer's scope. If admission of an inferred application reifies its arguments or revisits a generic constraint, first complete the inferred destination's declaration identity through its source bindings and perform that work in the actual declaration's captured context. The value being admitted must not select the destination identity. Required binding reads and computations retain the availability and failure ordering of 1.3.8. No constructor, getter, decorator or dependency body is executed to discover a contract. This rule neither proves the current constructor origin of a mutable expression nor grants initialization, access or reference-liveness permission.

An established superclass constructor origin is preserved through parentheses and lexical aliases whose value origin is established at the heritage read, including aliases of closed specializations. Source-position evidence follows 1.2.8.1. Resolve the declaration by lexical identity and bind its type arguments before deriving inherited instance and static members, constructor signatures, override checks, abstract and interface obligations, operators, layout or access permissions. Initialization order and the class self-name's temporal dead zone remain in force. An erased alias, a mutable alias without an established source-position origin, an unknown call result, mutation that invalidates the origin, or a cycle supplies no additional origin proof. A class carrying a decorator that may return a replacement supplies a bounded origin instead: the declared class or an unknown subclass of it (7.12). A bounded origin supports the prohibition on redeclaring an inherited typed field and the override and accessor checks against inherited members, which hold for every subclass; inherited members, constructor signatures, abstract and interface obligations, operators, layout and access permissions need an established origin. A decorator whose Static Type is callable only with a void or undefined return replaces nothing, and a class carrying only such decorators is established as itself. No superclass expression is executed by the checking pass.

An effective own instance field that masks an inherited concrete method or accessor must preserve its participating capabilities. Its readable type must be a conversion-free subtype of an inherited getter's effective return type, and its callable type must accept the inherited method's arguments and supply its result. A function-valued field may satisfy this member-call capability without acquiring the method's receiver marker. An inherited setter also requires writable storage accepting every value the setter accepts. It is a type error when established contracts violate these obligations. Determine actual own-field/prototype precedence before comparison; an inherited own field still masks a derived prototype member. The independent prohibition on redeclaring an inherited typed field continues to apply. Unknown, any and entirely untyped contracts retain their existing treatment, and open contracts are checked when specialization establishes them. No wrapper, identity change or reference-liveness relaxation is implied.

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 owner identity and separate static and instance namespaces. A field or method that replaces the descriptor is resolved before this comparison; an unpaired getter or setter remains valid. Establishing the pair invokes neither half.

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 published where inference establishes it. Resolve the effective descriptor in property definition order, using established String or Symbol key identity; a data property or ordinary method replaces accessor halves. A spread or unknown computed key prevents any conclusion depending on the descriptor it may replace. Unknown or any contracts remain dynamic. No getter or setter executes during this check.

Within a static member or static initialization block, this has the constructor-side type of the enclosing class declaration or expression, including its effective static members. This identity belongs to the class declaration node and its specialization, not to the availability of an outer binding for its name. A nested arrow retains that receiver; an ordinary nested function establishes its own receiver context.

It is a type error for a ClassHeritage expression to have a participating Static Type establishing only primitive values other than null, or a reliable typed invocation origin establishing that the value is not a constructor. The invocation-origin judgment used for new also applies here, including its lexical identity, alias and invalidation rules. A function type alone does not establish constructibility or its absence. null, a possible constructor, or an unknown alternative prevents an all-invalid type proof. Heritage is checked in its lexical scope before entering the class body, with the class self-name in its temporal dead zone and class parameters shadowing outer bindings. A self-name read does not establish an instance-layout cycle. For a participating non-null alternative not already known to fail constructibility, it is also a type error when the effective declared prototype member proves the subsequent prototype stage invalid: its type admits neither Object nor null. A possibly constructible base with such a prototype cannot complete heritage successfully. Combine these proofs over every possible selected path; a possible null base, an Object-or-null prototype, or an unresolved effective member/type prevents an all-invalid proof. An undeclared member of an open shape is unknown, not a proved undefined value. This is the heritage consumer's Object-or-null requirement, not the different prototype requirement of 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 Static Type of new of it is its instance type. Inherited counts because a subclass is a subtype: a typed class is automatically sealed, so a subclass that declared nothing and was therefore untyped would have EXTENSIBLE instances where its base has sealed ones, and those instances would still satisfy the base type. A class whose base is itself untyped is unaffected, and an untyped field or a dynamic modifier disqualifies a subclass exactly as it does a base. A typed class is automatically sealed and its prototype frozen, as 1.7.10 specifies. A class declared dynamic is not sealed and is not a value type.

A typed class is a value type class when every one of its fields has a type that is a value type. Instances of a value type class are values in the sense of 1.5.1: they are laid out contiguously, they are stored inline in a field or an array element of that type, and assigning one copies it. It is a type error for a value type class to contain itself, directly or through a cycle of value type fields, since the layout would have no finite size (9.4). A field that closes such a cycle is written with a type that is a reference (1.11.10).

Repeated field definitions in one class with the same established property key and placement share one storage contract. It is a type error for their explicit typed contracts to differ under SameType, or for their storage qualifiers (including readonly and layout controls) to disagree. Type metadata participates in that identity. Static and instance fields have separate keys; Symbol keys compare by identity. Compatible repeated contracts retain initializer order and last-value semantics while allocating one layout slot. A later unannotated initializer is still a store to the existing typed slot and must satisfy its contract. Entirely untyped duplicates keep ordinary ECMAScript behaviour. An unresolved computed key or dependent contract is checked when it is established; a determinable conflict is an Early Error. This rule does not permit redeclaring an inherited typed field.

An established constructor origin is preserved through a resolved module import, including renaming and re-exporting. Establish binding stability and decorator replacement bounds in the source owning the imported binding; an import binding's own immutability does not prove that its producer leaves the value unchanged. A subclass of that established imported constructor inherits its original nominal base, bound arguments, implemented interfaces, member contracts and applicable constructor signatures by the same rules as local heritage. A bounded origin supplies only the judgments valid for every permitted replacement subclass. Preserve lexical shadowing and ordinary alias and invalidation rules. Foreign heritage references resolve in their original source, not by matching names in the consumer. A class import read in value position denotes the constructor value and its static member contract, not the nominal instance type denoted in type position; an inferred immutable alias preserves that distinction. Complete available evaluated identities as specified in 4.8, at the boundary prescribed by 1.3.8. This judgment reads no current exported value and executes no user code; it does not initialize a binding, establish reference liveness, or change module evaluation order.

It is a type error for a class to declare a field whose name a class in its heritage chain declares as a typed field. 9.2 states the rule for a value type class, whose redeclared field would have no defined offset; it holds of every class because a typed field has one declared type, which subtyping, readonly permission and the typed store all read. A subclass that needs a different value for an inherited field assigns it in its constructor, or, for a value type class, declares a zero (1.11.5). A private name is per class and is not redeclared by a subclass's field of the same spelling, and a static field belongs to its own constructor.

Note 1

This clause once said that a field written T | null is a reference, which read against 1.11.4 as a claim about aliasing and is not one. A field declared A | null holding an A is reached by both sentences: this one calls it a reference, and the copying rule copies into a field of that type. They agree once the two are read as answering different questions - this one names a layout obligation, that the recursion stops rather than descending, while the copying rule names what a store does.

What a store does is copy. A value type class in such a field is unshared in every channel a program can reach: storing into it copies, reading out of it copies, === compares fields rather than references, and the value cannot be held weakly (1.12.9). Aliasing would require an identity, and 1.5.1 has already given identity up for these values; a value cannot alias without one. Sharing is reached through a reference type instead, which has an identity precisely so that it can.

An instance of a value type class REMAINS AN OBJECT. typeof reports "object" for one, and it has the identity an Object has: 1.5.1's "values that are not Objects" describes the types that clause ADDS, and this clause extends value SEMANTICS - copying and inline layout - to a class of Objects without removing them from the object type. A program that reads typeof sees what it saw before.

IDENTITY is the other half, and it does not follow typeof. An instance of a value type class has none - that is what 1.5.1 means by indistinguishable - so it is refused at the positions that require one: it cannot be a WeakMap or WeakSet key, the target of a WeakRef, or registered with a FinalizationRegistry (1.15.4). The two answers are consistent rather than in tension: 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 value type class are ===, Object.is and SameValueZero equal when their fields are, since 1.5.1 requires that "two values of the same value type that are the same value are indistinguishable" and a copy would otherwise be distinguishable from its source. Instances of DISTINCT value type classes are never equal, whatever their layouts, because the values of distinct value types are distinct.

It is a Syntax Error if the MethodDefinition of a class's constructor has a TypeAnnotation. A constructor declares no return type, since the class is the type a construction yields — for a generic class, the specialization 4.9 binds. Three clauses said so and none said it as an early error, so an implementation could accept the annotation, check the constructor's body against it, and then ignore it at the construction site — enforcing in one place a rule it discarded in another.

It is a Syntax Error if a typed class's constructor contains a ReturnStatement whose Expression is present and is not this. Bare return; and return this are the control-flow forms and remain legal.

Note 2

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 constructor's own object when it returns one, so new C() need not be a C, a declared field reads undefined, and a private-name brand check fails.

A refusal rather than a check, for two reasons. A base class's returning constructor breaks every subclass — a subclass that writes no 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 1.7.10's layout guarantees are statements about what a construction produces; a constructor that may yield a foreign object makes every one of them conditional on reading a constructor body, including one the reader did not write.

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 replacement for a constructor that must yield something else is a static method.

Scoped to a typed class so that this specification remains a superset: an untyped class keeps JavaScript's semantics unchanged, including a constructor that returns a proxy of its own instance. A class annotated only on a method, a getter, a constructor parameter, or a static field is not typed and is not reached, which is deliberate — only a field annotation makes an instance's shape a fact.

A field with no initializer holds the default value of its type (1.8.3.1), and it is a type error to declare a field whose type has no default and give it no initializer.

=== on two instances of a value type class compares them field by field, since the identity of a value type is its value. === on two instances of a class that is not a value type compares references.

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 type error if the effective return type of such a method is not a subtype of the inherited signature's effective return type (the declared type where present, and the published inferred type otherwise, under 1.6.2): an override may narrow a return, so that 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 slot is keyed by; a method whose parameter types differ from every inherited signature's is an overload, as 1.10.3 describes. Java and C# state this rule as written; a Rust trait implementation must match the trait's signature, which is the same commitment.

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 specialization resolves a dependent contract.

A field may be declared readonly, a modifier written before the field name and after static where both appear. A readonly field may be assigned only in its own initializer and in a body of a constructor of the declaring class. Every other assignment is a type error: at compile time where the type is known, and at run time otherwise, so an assignment from a method a constructor calls, from a constructor or method of a subclass, through a reference, or through reflection is a TypeError. The permission is the running constructor's, not the constructor's presence on the stack, so a method the constructor invokes cannot write the field even though a constructor is executing. readonly is shallow: it fixes the binding, not the object the field refers to, so a readonly field holding an object may have that object mutated.

It is a type error to store through a class member whose effective accessor has a getter and no setter, where that member is statically known. Setter presence is independent of its parameter annotation and of the declaration order of a getter/setter pair. This permission applies to simple, compound and update assignments and to destructuring and iteration targets; invoking a declared compound operator that does not replace the member is not such a store. The judgment respects inherited members, specialization, private names, Symbol keys, and the distinction between static and instance members. An own field shadows a prototype accessor regardless of declaration order. A getter-only property does not acquire field-initialization permission in a constructor; a static field or a different private name does not supply that permission. An unknown receiver retains the runtime property checks.

An effective accessor with a setter and no getter has the readable Static Type undefined. Its write contract remains the setter parameter type, or any where that parameter is unannotated. A getter/setter pair uses the getter result for reads and the setter parameter for writes, independently of declaration order. This applies to class and object-literal accessors, including static and private members and known Symbol keys. Resolve inheritance by the effective property descriptor: an own setter-only descriptor masks an inherited getter; the two descriptors do not supply halves of one pair.

A member marked protected is accessible only within its declaring class and subclasses of that class. It is a type error to select a statically known protected declaration outside that scope. Apply this permission to fields, methods and accessors, static and instance, using the declaration that supplies the effective member. Dot access and a statically established finite set of String or Symbol keys use the same rule, as does a property of an object binding or assignment pattern, which reads the declaration its key selects, and a rest element or object spread, which reads every declared field of an established typed-class instance that is not otherwise named. Every known selected declaration of a union receiver must permit the access, after narrowing and optional-chain short-circuiting. An inherited member retains its declaring owner; a public override supplies its own permission. Unknown keys and any receivers do not establish a protected declaration. This is a static access rule, without a runtime visibility barrier or a change to layout.

It is a type error to perform a selected private member operation when its participating receiver's Static Type proves that every remaining alternative is non-Object. This applies to private reads, writes, updates and calls. Optional access first excludes the nullish alternatives that skip the operation; an all-nullish optional receiver selects no access. A possible Object prevents this proof, irrespective of whether its static nominal type names the declaring class: private brands may be installed on other Objects. Unknown and any receivers retain runtime brand checks. This judgment does not change public primitive member access, private-name resolution, setter or readonly permissions, reference liveness, or the prohibition on borrowing private members.

Performing SetIntegrityLevel with frozen on an instance of a typed class, as Object.freeze does, makes every field of the instance readonly at run time in addition to making its properties non-writable. A subsequent assignment to any field is therefore a TypeError in every mode, rather than the silent failure a non-writable property gives outside strict mode. This applies to an instance of a typed class, the same instances that are automatically sealed; freezing an ordinary object is unchanged.

Note 3

That a value type class defaults field by field means an instance can come into existence without its constructor running, which 1.8.3 flags. This clause endorses it, because the design already requires it in two places and the alternative would break both. 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 constructor. A value type class is therefore a shape with a zero, not an object with an invariant its constructor establishes, and a class that needs its constructor to run states that by holding a field whose type has no default, which makes the zero-filled form a type error. A parameterized field is one way to hold one: a class of unit-typed fields has a zero exactly where the base declares the cast that admits it (1.8.3), so a design that wants its vectors zero-fillable writes that cast, and one whose brand is meant to be reached only through the operators that produce it need not. This is a real constraint on what a value type class can promise, and it should be stated in the design rather than left to follow from the array case.

1.11.3 Constructor Overloading

Constructor declaration ambiguity is checked across signatures with different numbers of formal parameters. Optional parameters, defaults and rests can make their admitted argument counts overlap. Use the same viability and specificity rules as ResolveOverload: overlap alone is not ambiguity, and a fixed signature that outranks a rest remains valid. Report a proved ambiguity at the declaration even when no construction is written.

A class may declare more than one constructor. The declarations are overloads of one construct signature set, resolved at a construction on the same terms as a call to an overloaded function (1.10.3).

The base language makes a second constructor in a ClassBody a Syntax Error. That error does not apply where the declarations are DISTINCT SIGNATURES and at least one parameter across the set carries a type annotation. This is the relaxation 1.10 already makes for a function name declared twice, applied to the one member the base language singles out.

The annotation is what admits the set. A class body carrying no annotation on any constructor parameter is exactly what it was: a second 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 (1.10.5), and a construction yields the class rather than a declared return type, so two constructors whose parameter types are the same are ONE SIGNATURE DECLARED TWICE. That is a type error at the class, reported as it is for a function declared twice, and not deferred to the constructions that would be ambiguous.

Note

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 shape 1.6 avoids for a converting constructor by the same reasoning.

Every one-parameter overload is a converting constructor (1.6). Where more than one could represent a value, the conversion resolves by the argument's type exactly as a call does; it neither picks arbitrarily nor refuses for the ambiguity that resolution is there to settle.

A VALUE TYPE class is not special here. Its default is built field by field and runs no constructor (1.8.3.1), and its layout is fixed by its fields (9), so neither depends on how many constructors it declares.

1.11.4 Copying a Value Type

Assigning a value of a value type, passing one as an argument, and returning one each COPY it. The copy is of the value's layout (9), padding included; it runs no constructor and no field initializer. There is no move that would leave the source invalid.

The positions at which this occurs are: initializing a binding from a value of such a type, assigning to one, storing into a field or an array element of that type, passing an argument, and returning. A read of a field or an element into any of those positions is one of them, so _e_ in let _e_: _V_ = _arr_[0] holds a copy and writing to it does not disturb the element.

CONSTRUCTION does not copy. A constructor call, an object-literal conversion, and a return of a newly constructed value build their result directly in the destination — a binding, a field, an array element, or the caller's receiving slot — so return { … } := _Matrix4_ writes the caller's storage in place, Matrix4 being a class that DECLARES a conversion from such a literal (1.6.8). An object literal does not convert to a class structurally: a class is satisfied by construction, not by shape, and the three declared forms are how a type states that a literal may become one. This is the copy elision that C++17 guarantees for prvalues, required here rather than permitted.

A field in this clause is a class field carrying a declared type (1.11.2), and an array element is an element of an array carrying a declared element type. These are the storage that holds a value of a value type INLINE, which is why storing into one copies. A plain object's property and a plain array's element are not fields or elements in this sense: they hold a REFERENCE, as they do for a value of any other type, and a program that wants a copy into one writes a conversion.

A SUBCLASS OF A VALUE TYPE CLASS IS NOT ASSIGNABLE TO ITS BASE. Where Sub extends Base, Base is a value type class, and Sub declares storage of its own, a value of Sub may not be placed in a position whose declared type is Base: not a field, an array element, a binding, an argument, or 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 (1.11.10) — holds any subclass by pointer and is unaffected; this is how a program expresses a hierarchy it means to be polymorphic. And the SUBTYPE RELATION IS UNCHANGED: Sub is a Base, _v_ is _Base_ and _v_ instanceof _Base_ both answer true, and a bound _T_ extends _Base_ instantiates at Sub carrying the whole value. Subtyping and storability come apart here, and only here: what a Base-sized slot cannot do is HOLD one.

The conversion _v_ := _Base_ (1.6.7) is the widening, and the only way to obtain the base part. Its result is a value of Base — its prototype is Base's, its methods are Base's, and it may be stored wherever a Base may. The loss is written in the program, which is the distinction this clause exists to draw: a conversion between types is spelled explicitly rather than performed silently.

A copy site is decided by WHAT THE SOURCE EXPRESSION IS, not by whether a type is written at it. A NAME, a field read and an element read each denote a value that already exists, so binding or assigning from one copies; a constructor call, a call, and a literal conversion each produce a value in place, so binding or assigning from one does not. 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 typed array's annotation stores each of its elements, so a literal naming an existing value copies it: 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 value type, as { … } := _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 site (Table 23), and a check may be skipped where the source provably satisfies the target: a skipped check is nothing, while a skipped copy is an alias. An implementation that copies only where a check runs will alias exactly where a program is most explicit about its types, which is the opposite of what this clause requires.

Copying a storage-backed value retains the source location's liveness requirements before reading its layout, including when no field read is needed. An abrupt completion while obtaining a field propagates from the copy operation; it does not publish a partially initialized copy as the destination's value. Earlier ordinary effects remain. Copying the layout includes nested value fields and does not retain a view into their original storage. It does not copy the identity of an ordinary reference-class value.

A ref binding (2.10) is the opt-out: it names a storage location rather than taking a value, so no copy and no allocation occurs, which is how a program passes a large value type without paying for it.

A method's this value is the receiver's STORAGE and not a copy of it, so a method that writes to this writes through to the receiver, whether the receiver is a binding, a field, or an array element.

Note

The last rule is what makes a mutating method possible at all: were this a copy, _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 1.9.9 depend on it: a declared compound operator is invoked as a method call and its target is not written, so the update it performs is a write through this, which a copied this would discard.

A copy running no constructor is coherent with 1.11.2's reading of what a value type class is: "a shape with a zero, not an object with an invariant its constructor establishes". A class that needs its constructor to have run states that by holding a field whose type has no default.

The copy carries the class's PRIVATE fields as well as its public ones, for the reason the default does: a private field is part of the shape. A copy that dropped one would leave a value of the type that its own methods could not read, and the identity rule above — two instances === exactly when their fields are, compared field by field — would compare a field one of them does not have.

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 (Table 23) may be skipped where the source provably satisfies the target, since a check that cannot fail does nothing. A copy is never nothing. An implementation that performs the copy as part of the boundary check will therefore copy or not according to what its checker happened to prove, which is one semantics decided by an optimisation.

1.11.5 Declared Zeros

A class may declare the value its bindings hold before assignment, in place of the one 1.8.3.1 derives field by field. The declaration is a static field named default:

ClassElement[Yield, Await] : static default = AssignmentExpression[+In, ?Yield, ?Await] ;

The class's declared zero is that AssignmentExpression's value. It REPLACES the derived default rather than adding to it: a class that declares one is stating that the field-by-field value does not describe it, which is why 1.8.3.1 consults the declaration before every other step. A class that declares none is unaffected.

The AssignmentExpression must be compile-time evaluable (1.2.7.1) and it is a type error otherwise, and it is evaluated ONCE, when the class is declared. The reason is the one 1.4.2.7 gives for a tuple element's Initializer: a type is interned, so one Type Object serves every use of it, and a zero that allocated per binding would either hand every value the same object or make the type's identity depend on when it was read. A compile-time evaluable zero is copied where it is used, so the sharing is unobservable — which is what a derived default already does, field by field.

It is a type error if the declared zero is not a value of the class.

Note 1

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.

Note 2

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 type error, as 1.8.3.1 already makes it.

1.11.6 Abstract Classes

An abstract obligation identifies an instance property key and a member kind: method, getter or setter. A same-named static member, field, or different accessor kind does not implement it. Property keys retain their identity, including Symbols and established computed keys. Compare full effective signatures, including parameter flags and generic parameters; normalize generator and async-generator implementation returns to their carrier types before comparison. An async implementation is compared using its declared Promise return. A compatible inherited implementation satisfies the obligation unless a nearer declaration masks it. An abstract redeclaration must itself satisfy the inherited signature and the ordinary override-return covariance rule; the nearest valid declaration governs subsequent implementations. Known violations are Early Errors, with the same judgment applied to contracts resolved during class-definition evaluation.

A class may be declared abstract, sealed, or dynamic. The productions below restate their ECMAScript definitions with the modifier list (1.1); sealed is specified by 1.11.9, dynamic by 1.11.2, and the abstract method form is an alternative of the ClassElement restatement in 18. It is a type error if a modifier is repeated, and a type error if sealed and dynamic are declared together.

ClassDeclaration[Yield, Await, Default] : ClassModifiersopt class BindingIdentifier[?Yield, ?Await] ClassTail[?Yield, ?Await] [+Default] ClassModifiersopt class ClassTail[?Yield, ?Await] ClassExpression[Yield, Await] : ClassModifiersopt class BindingIdentifier[?Yield, ?Await]opt ClassTail[?Yield, ?Await] ClassModifiers : ClassModifier ClassModifiers ClassModifier ClassModifier : one of abstract sealed dynamic partial

A member is abstract because it has no body; the abstract keyword before it is optional and says the same thing earlier. The absence of a body is what an implementation supplies, so the forms that have nothing to supply are not abstract members and remain a type error without a body, each for its own reason: a static member has no override site; a 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 shape stays an implementation detail. A get or set accessor is an abstract member: it has an override site and its annotation types the implementations exactly as a method's does.

An abstract method is a signature with no body, and its annotation types the implementations: it is a type error if a subclass implements an inherited abstract method with a signature the abstract declaration does not accept. It is a type error if a method with no body appears in a class not declared abstract, whether or not the abstract keyword is written, and a type error if a class not declared abstract leaves an inherited abstract method unimplemented. Where the Static Type of the constructor in new C(…) is an abstract class it is a type error; the [[Construct]] internal method of an abstract class's constructor throws a TypeError exception when NewTarget is that constructor itself, so a dynamic construction fails at run time while super() from a concrete subclass, arriving with a concrete NewTarget, runs the abstract constructor as a constructor body should.

The direct-instantiation restriction follows a constructor identity proved through stable binding origins, including aliases, parentheses and specializations whose declaration is known. It does not remove the abstract constructor's construction capability: heritage and super() with a concrete NewTarget remain valid. Reassignment, shadowing, any, decorators and other unresolved transformations prevent an identity proof; a function's return type alone does not identify its result as a constructor declaration. The judgment executes no constructor or reference and changes no runtime liveness requirement.

An abstract class is never a value type class (value type class). Its purpose is polymorphic dispatch through the hierarchy, which is reference behaviour, so an abstract class and every subclass reached through an abstract-typed binding are reference types.

Note

The design's Shape declares abstract area(): number; beside a concrete describe(), which is the division of labour: an interface is pure signature, while an abstract class carries the partial implementation a family of subclasses completes (design, README, Abstract Classes).

1.11.7 Interfaces

1.11.7.1 Interface Declaration Identity

A source interface declaration introduces a nominal type at an actual lexical binding instance, not at a Parse Node alone. Associate one opaque interface declaration identity with that binding instance when declaration instantiation creates it. All Type Records formed for that source interface carry the identity in [[DeclarationIdentity]]. The identity is specification data, not an ECMAScript value, Reference or permission to access a location.

  1. Repeated type references, forward and recursive formation, checking preparation, ordinary Evaluation and any rebuilding of the same binding use its one identity. Rebuilding is not a new declaration instantiation.
  2. A fresh binding created by another function call, block entry, loop iteration or other lexical activation has a distinct identity, even if its Parse Node and captured values are the same. Reading an outer binding, returning its Type Object, or reaching it through an alias, import or re-export preserves its identity.
  3. A generic application retains the identity of its primary interface binding and adds the arguments established by ordinary generic binding. Equal arguments of that same binding denote one Type Object. Equal arguments of different binding instances do not. Rules relating applications of the same interface declaration, including variance, family matching and an erased argument, require the same declaration identity before comparing arguments.
  4. A partial interface contribution targets the actual resolved primary interface Type Object. It preserves that primary's identity and updates only its matching applications, including those already formed. A later activation of the same source declaration does not receive or overwrite another activation's contributions. Failure publishes no contribution to either activation.

The member contract is formed using the lexical and generic inputs belonging to this declaration activation under 1.3.6. A class's runtime 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 wildcard matching every activation. Publication of one constructor's resolved contracts must not overwrite those of a constructor created by an earlier activation. Interning, reflection reconstruction and successful-evaluation caches cannot substitute another activation's contract. A source location, spelling, Parse Node or matching member shape alone is insufficient to establish this identity. A checker may represent an as-yet uninstantiated binding symbolically; a shared Parse Node alone supplies no proof that distinct activations denote the same interface.

Interface membership may still be structural, and structural aliases retain ordinary canonicalization. This identity rule changes neither the reference-liveness permissions of captured inputs nor the existing availability, evaluation and checking-phase rules. An implementation may share code and immutable structural summaries across activations when doing so preserves these identities and contracts. The identity itself need not retain an environment, a program value or a live reference.

Self-referential, mutually recursive and forward-referenced interface contracts are formed under 1.3.6, preserving lexical binding identity and the complete member graph in nested scopes as well as at the source-checking boundary.

Call signatures are obligations of an implements clause. It is a type error when established construction behaviour proves that the produced instance cannot be callable, as for an ordinary class with default non-callable construction. A method named 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 replacement objects returned by constructors whose capability is unresolved retain the obligation at the eventual typed boundary. Do not execute constructors or infer callability merely from the claimed interface.

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 type error if it denotes an established non-interface type. Alias chains do not create a new nominal identity. Bind generic arguments by BindTypeArguments, including names, packs, defaults, constraints and family-case selection, before verifying properties or operators. Retain the resolved interface declaration and bound arguments for subsequent nominal judgments; comparing source spellings or zipping arguments by written position is insufficient. Open applications retain their specialization obligations.

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 set of a future application, even when an 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 contribution can repair remains an early error. In particular, partials do not repair incompatible primary members, add instance storage or supply callability to established non-callable construction. A nongeneric class retains its ordinary constructor-completion obligation.

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 captured declaration context and bound generic inputs, including specialization-case captures. Preserve the current class family identity and in-progress interface graph in injected self arguments; syntax identity alone does not establish the self type of this activation. Apply the ordinary target-kind, argument, constraint and member obligations before publishing the completed application contract. Restore the caller's declaration context after an abrupt completion, and do not cache a failed resolution as a successful specialization. These rules do not execute a dependency body early, manufacture an unavailable interface binding, change module evaluation order, or grant reference-liveness permissions.

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 captured contexts. A member added to a type-lookup shape by an interface claim is not a declared class member. In particular, an absent optional interface member cannot supply a required member of another interface.

Distinguish entries supplied only by interface claims in a class's checking lookup shape from actual member declarations. An interface-only entry cannot supply a required member or constitute a declaration collision with a partial that supplies it. The actual contribution's contract replaces that lookup entry, while real declaration collisions retain their ordinary checks. Preserve this distinction through inheritance, source rechecking, repeated partial merges and completion of forward nominal records. A lookup entry does not establish registration, successful application admission or reference liveness.

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. Record those keys as declaration evidence before final admission. Do not re-evaluate a computed key expression to perform interface verification. A symbolic checking key is not the identity of an evaluated Symbol.

The resolved target graph may be assumed locally when comparing recursive member types. It does not establish member presence, write capability, operator presence, or callability. Do not publish that graph as a completed application contract or insert a successful specialization cache entry until every obligation succeeds. Failure restores the caller's contexts and discards the incomplete application; it does not roll back effects already performed by ordinary class definition evaluation. Do not construct a sample instance or invoke a member to obtain declaration evidence.

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 published inferred results. Property availability and write capability must also hold. This method rule does not replace writable storage invariance for function-valued fields. Operators are checked separately from ordinary properties by their declared signatures, including inherited and applicable partial declarations.

For an imported interface, retain the resolved exporting Module Record, original interface declaration, generic parameter contract and member contract through renaming and re-exporting. Written application arguments resolve in the importing source; the interface's defaults, constraints and member descriptions retain their original declaration context. A parse-time unresolved import is not evidence that a specialization pattern names no generic constructor: repeat the dependent judgment when import resolution supplies the declaration. Verification of an imported 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 1.3.8; an open application retains its explicit specialization deferral. An available declaration failure must not be suppressed by treating the imported interface as unknown, and a required computation must not be replaced by a symbolic summary or a same-spelled consumer binding. Computations retain the availability and failure ordering of 1.3.8; no dependency body is executed to discover a contract. These requirements do not change interface identity, structural value membership or reference liveness.

For a class type whose resolved declared hierarchy establishes an interface type with the required declaration identity and arguments, IsSubtype uses that nominal refinement without imposing a second structural comparison of member summaries. This includes recursive self-types and relationships used by reflection or dependent specialization bounds. Member verification remains an obligation of the declaration and application; unresolved obligations, including callable capability, remain at their prescribed boundaries. This nominal judgment does not replace structural membership checks on runtime values, equate distinct declaration activations, or change reference permissions or liveness.

Declaration-site verification includes each applicable index signature. Check established own enumerable instance fields, including inherited fields installed on the instance, against the signature's value type, except keys explicitly declared as interface properties, which retain their named contracts. Compare actual String and Symbol keys. Prototype methods and accessors are not enumerable instance fields. An index signature does not require every admitted key to exist. Unknown computed keys and dynamic additions retain their runtime checks; no constructor is executed to discover them.

Declaration-site satisfaction includes the effective member's availability and read/write capability. A readonly field or getter-only property does not implement a writable interface property. Preserve optionality, readonly covariance, writable-property invariance and the ordinary function-signature relation, including inherited and specialized members. A declaration must not promise an interface that its effective property capabilities cannot satisfy.

An interface is declared with the productions below; InterfaceDeclaration is an alternative of the Declaration restatement of 6.1, and ClassTail is restated with the implements clause (1.1).

InterfaceDeclaration[Yield, Await] : interface BindingIdentifier[?Yield, ?Await] TypeParameters[?Yield, ?Await]opt { InterfaceBody[?Yield, ?Await]opt } InterfaceBody[Yield, Await] : InterfaceMemberList[?Yield, ?Await] TypeMemberSeparatoropt InterfaceMemberList[Yield, Await] : InterfaceMember[?Yield, ?Await] InterfaceMemberList[?Yield, ?Await] TypeMemberSeparator InterfaceMember[?Yield, ?Await] InterfaceMember[Yield, Await] : TypeMember[?Yield, ?Await] OperatorDefinition[?Yield, ?Await] ClassTail[Yield, Await] : ClassHeritage[?Yield, ?Await]opt ImplementsClause[?Yield, ?Await]opt WhereClauses[?Yield, ?Await]opt { ClassBody[?Yield, ?Await]opt } ImplementsClause[Yield, Await] : implements ImplementsList[?Yield, ?Await] ImplementsList[Yield, Await] : TypeReference[?Yield, ?Await] ImplementsList[?Yield, ?Await] , TypeReference[?Yield, ?Await]

A class's WhereClauses are specialization predicates over its type parameters (4.10): every application of the class, in an expression, a type position or a heritage, is judged against them as an application of a generic function is, before the body where the source text shows it and when the class is specialized otherwise. They hold for every member of the class: a method's own 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 TypeMember forms of an object type, index signatures and optional members with defaults included, and additionally operator members, as interface Ordered<T: type> { operator<(other: T): boolean; } written with declaration-site <…>; a type satisfies an operator member by declaring the operator. It is a type error if a class whose ClassTail carries an ImplementsClause does not satisfy every member, operator members included, of every listed interface.

An interface declares a contract. Its type is a nominal type whose [[Declaration]] is the interface declaration, and a class that implements it is a subtype of it by 1.2.3.1, which follows the declared hierarchy. An interface may also type an object, an array, or a function structurally.

A class that implements an interface cannot remove the contract: deleting a member the interface requires throws a TypeError exception, and this holds through a subclass, a reference, and reflection.

The declaration is verified where it is made. It is a type error if a class declares that it implements an interface and does not declare every member the interface requires, an optional member excepted, with a type assignable to the interface's; a generic method satisfies the interface's through the identification SameFunctionType performs (1.10.2.1), so 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.

Note 1

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 1.7.6.1, so an object that has the members satisfies an interface-typed position whether or not any class declared it.

Note 2

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 1.2.3.1, which relates two nominal types along declared inheritance and no other way; an interface's type is nominal, so a class reaches an interface-typed position by DECLARING implements and not by matching the shape. A value question - is this value of that type - is answered by 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 true, and let s: Small = new Rich() a type error. That is the same visible-and-blind split 1.7.7 describes: the checker can see that the source is 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 1.2.3.1 forbids on purpose: a class states a construction and an identity as well as a shape, and it is the identity its type is for. A shape with no identity is written as an object type, and satisfies structurally in every position.

1.11.8 Enums

The missing-first-initializer rule is checked directly for every enum declaration in its lexical type scope. A known non-numeric underlying type requires an explicit first value even in a nested, unused body; closed enum evaluation is not a prerequisite. Empty enums, numeric default starts and valid later omissions retain their rules. An unresolved underlying type is checked when specialization or evaluation supplies it, without executing an ordinary enclosing function during checking.

The enum switch judgment uses the enum declaration identified by the discriminant's resolved Static Type. A transparent alias, a known member read or a known call result preserves that identity; a shared underlying primitive type does not. Each enumerator label resolves in its own lexical scope and must name that same enum declaration. A default covers remaining values, and enumerator names sharing one value require coverage of that value only once. An unknown or any discriminant supplies no enum exhaustiveness proof.

The labels of an enum 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 type error by 1.8.5.4. Coverage is of values, so a case for every value covers the enum however many names share one; a default that covers an enumerator without a label, a sentinel included, is live.

An enum is declared with the productions below; EnumDeclaration is an alternative of the Declaration restatement of 6.1.

EnumDeclaration[Yield, Await] : enum BindingIdentifier[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt { EnumMemberList[?Yield, ?Await]opt ,opt } EnumMemberList[Yield, Await] : EnumMember[?Yield, ?Await] EnumMemberList[?Yield, ?Await] , EnumMember[?Yield, ?Await] EnumMember[Yield, Await] : IdentifierName Initializer[+In, ?Yield, ?Await]opt

It is a type error if two enumerators of one declaration have the same name. Every initializer is evaluated in the compile-time evaluable fragment (1.2.7), since an enumerator is a constant of the program. It is an Early Error for an initializer to reach a built-in outside the fragment where the source text establishes the reach: by a reference to a global that the program neither shadows nor replaces before the declaration and whose value is that built-in, made directly, through a const bound to such a reference, or within a function declaration the initializer calls. Any other reach is the TypeError the fragment's evaluation raises, since a name in the source is not the built-in it reaches. An enumerator's initializer stands in a position whose contextual type is the underlying type, so a literal there takes it (1.6.6): 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 contextual type is applied rather than required, since an initializer may also be a function of two parameters, which this clause calls rather than stores.

It is a type error if an enumerator's value is already an enumerator of another enum of the same agent. What the source text shows of this rule is an Early Error: two enumerators of different declarations whose initializers are references to one immutable binding hold one value. A collision reached any other way, through a second binding or a value a call returns, is known only when the second declaration evaluates, and is a TypeError thrown there; identity is a fact about values, and a value built while checking is not the one the program later holds. Where the underlying type's values carry their own identity, that is an object, a function, or a symbol, an enumerator IS the value its initializer denotes, which is what makes it usable as that value without conversion. A value serving two enums would therefore be a value of two nominal types, and 1.5.1 admits none. Where the underlying type's values are compared by content, an enumerator is a distinct value of that content carrying its enum, so two enums may name the same content, and enum A { X = 0 } and enum B { Y = 0 } declare different values that this rule does not reach.

Note 1

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 1.7.1.1 reports the enum for that value.

An enum declaration introduces a nominal type whose values are its enumerators. The type after : is the enum's underlying type, and an enum declared without one has the underlying type int32. Any type may be an underlying type, including a function type and symbol.

The first enumerator, when it has no initializer, takes 0, and it is a type error when the underlying type is not numeric, since a non-numeric enumeration must define its starting value. A later enumerator with no initializer takes the result of applying the underlying type's prefix increment operator operator++ to the previous enumerator's value, with the previous enumerator itself unmodified; where the underlying type declares no prefix increment, it takes a value equal to the previous one. An enumerator's initializer may name the enumerators declared before it in the same enum, each bound to its value as it is evaluated; it is a type error for it to name the enumerator it belongs to or one declared later, since the evaluation proceeds in declaration order and the binding does not yet exist. The enum's own name is not in scope there, being uninitialized until the declaration completes, so an earlier enumerator is named directly rather than through the enum. An enumerator initialized with a function of two parameters is given the result of calling that function with the enumerator's index and its name as a String, and a following enumerator with no initializer is given the result of calling the most recently given such function with its own index and name, until an initializer replaces it; an initializer that is not such a function sets its enumerator's value without disturbing the function for those after it. Such a function is not a closure.

Note 2

The increment above is the underlying type's own, not an operation on numbers. A 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 enum type is a subtype of its underlying type, so a value of an enum type is usable wherever the underlying type is required and no conversion is written. The reverse is explicit: calling the enum type with a value of the underlying type returns the enumerator whose value it is, and throws a TypeError when it is not one of them.

A switch whose discriminant is a value of an enum type is checked at compile time. Each case label must be an enumerator of that enum, and it is a type error for a label that is not one; and where the switch has no default, it is a type error unless it has a case for every enumerator. Where it has a case for every enumerator, it is a type error for it also to have a default: the row of 1.8.5 gives the default its discriminant with every case's type removed, which is empty, so the default can never be taken (1.8.5.4), as a default after every atom of a match subject cannot (26.3.1). This is the enum row of the narrowing and exhaustiveness rules (1.8.5), the same closed-set reasoning a sealed hierarchy uses (1.11.9): the enumerators are a fixed set the declaration owns, so covering them is a claim the checker can prove.

An enum's Type Object has %Enum.prototype% as its prototype, which has %Type.prototype% as its own, so an enum carries everything a Type Object carries and an enumeration surface besides. That surface is: toString, which takes a VALUE and returns the key of the enumerator having it, or undefined where no enumerator has it; keys, values, and entries, each returning an Array Iterator over the enumerators in declaration order, of the keys, of the values, and of two-element key-and-value Arrays respectively; and @@iterator, which is entries; and underlying, an accessor giving the Type Object of the type an enumerator's value is of. An enum's Type Object also has an own property for each enumerator's POSITION, so an enumeration is indexed by position beside being indexed by name. Each such property has the attributes { [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }, and the Type Object is non-extensible once its enumerators are defined: an enumerator is a constant of the program, 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 Static Type is the enum's type, and TypeError exceptions otherwise.

Note 3

toString takes the value as an argument rather than being a method on an enumerator, because an enumerator IS a value of the underlying type: the one-way subtype rule above is what makes 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 enum from its underlying type. Indexing by position rather than by underlying value is the counterpart choice - a lookup BY VALUE is the reverse conversion and is already spelled 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.

Note 4

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 enum can be used for arithmetic, indexing, and comparison without a cast.

An enum's subtyping is a further case of the primitive and nominal rules in 1.2.3.1, which follow a declared class hierarchy and a metadata parameterization but do not reach an enum's underlying type. It is stated here rather than there because it is the same shape of relation, a type that refines another and is subsumed into it, and the specification should ultimately give the three one rule rather than three.

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 Type Object rather than on a reflection node for the reason 1.12.4 gives for every named leaf: an enum shares the primitive node, whose payload is the interned Type Object itself, and a walker that needs to know a leaf is an enum reads family on that node rather than a kind of its own. An engine that emitted a distinct node kind for an enum instead would put a kind in reflection output that Table 33 does not list, and would have nothing for 1.12.5 to consume.

Reflect.typeOf(Count.Zero) reports Count, by the rule of 1.12.1 that a value's runtime type is the most specific type of which it is a value. This does not make the enumerator anything other than a value the underlying type also accepts: membership in int32 follows from Count being a subtype of it, not from a second runtime type, so the uniqueness 1.7.1.1 requires is preserved and no conversion is performed. The case is exactly parallel to a metadata parameterization, where Reflect.typeOf of a Meter is float32.<{ m: 1 }> and not float32, while m instanceof float32 is nonetheless true.

What remains is that this specification now has three relations of one shape: a metadata parameterization refining its primitive, an enum refining its underlying type, and a class extending its base. Each is a case of the refinement relation that 1.2.3.1 now names once, and the design would be better stating the enum case as subtyping rather than as an implicit conversion, which is what it is: describing it as a conversion is what makes it look like a fifth exception to Table 13 when it is not an exception at all.

That an enumerator reports its enum is a requirement on the VALUE and not only on the type: an implementation must be able to recover the enum from the value. Where the underlying type's values are compared by content, an enumerator may be a distinct value carrying its enum. Where they carry their own identity, the enumerator is the value itself and the enum is recovered from it by other means, which is what the at-most-one rule above makes possible.

1.11.9 Sealed Classes

A class declared sealed may not be extended outside the module that declares it, so its subclasses are a fixed and known set. A sealed class is a reference type (1.11.10) rather than a value type, so a field of a sealed type holds an instance of any of its subclasses directly rather than through a nullable union.

A sealed abstract class can be neither instantiated nor extended outside its module. Its concrete subclasses are therefore a closed set with no case among them for the base, and a switch over a value of its type is exhaustive when it has a case for each subclass. A default in such a switch is then a type error, for the reason 1.11.8 gives for an enum: it can never be taken.

In a switch whose discriminant's Static Type is a sealed class, each case label is a class, and CaseClauseIsSelected selects a clause where InstanceofOperator(input, clauseSelector) is true rather than by strict equality, the labels being tested in source order. Each label is a narrowing form over what the labels before it left (Table 24), so a label for a class no value left can be of, a class outside the hierarchy, one an earlier label named, or a subclass of one an earlier label named, can never succeed, and is a type error by 1.8.5.4.

Note

This is the algebraic data type, assembled from parts the proposal has for other reasons rather than from a new construct. The cases are the subclasses, the case labels are their type objects, which are values because types are values, and the match is the switch the control structures section already defines. What makes it work is that the set is closed: exhaustiveness is a claim about a hierarchy no other module can extend, which is why it is reserved to a sealed class and an enum and is deliberately not available for a union of literal types, whose members are not a declaration anyone owns.

That a sealed class is a reference type is what lets a field hold a subclass instance polymorphically. A value type class cannot, since its field embeds the base slice of the layout and has no room for a subclass, which is why the two forms differ. A T | *null* field is NOT the polymorphic spelling: an optional over a value type with a layout is inline (10), a discriminant and a payload sized for T, so it has exactly as little room for a subclass as the bare field does. It expresses ABSENCE, not polymorphism. Holding a value type class polymorphically is spelled by a reference type - a 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.

1.11.10 Reference Classes

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 field declared with its type holds a reference of the implementation's width, and a field of that type may hold an instance of any of its subclasses.

A class declared sealed or abstract is a reference type for its own reasons (1.11.9), so neither needs the modifier. The kind is inherited: a class whose base chain reaches a reference type is one.

Because a reference type is reached through a reference, a field of one closes a cycle that would otherwise have no finite layout (9.4), and a reference type has no layout to report (Table 58) though a field of one occupies a reference's width.

Note

Two things need this modifier, and neither is served by the reference types that already exist. A class that contains itself has no finite inline layout, and sealed is the only existing way to say so - which forbids the extension an open class exists to offer. And a typed class meant to be subclassed by other modules is a value type class, so a field declared with its type embeds only the base slice and an instance of a subclass assigned to one loses everything the subclass added; such a class has always needed to be a reference type, and reference (1.11.10) is now the way to say it.

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 (1.11.4), and a linked structure built from copying boxes copies its whole tail at every link; a box that instead aliased would have acquired an identity, and would be handing one to a value whose === this specification defines as structural. Rust can offer the use-site choice because it has no collector and ownership must therefore be written down. A language whose collector owns lifetime has one kind of reference, so the only question left is whether a given class is reached through one, which is a property of the class.

1.11.11 Partial Classes

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 type error; immutable aliases preserve established class origins. A local declaration shadows an outer declaration in the ordinary way. A partial class creates no new binding for either lexical resolution or this judgment. Unknown, imported or dynamically supplied origins are not thereby proved non-class, and no type builder or constructor executes merely to classify the target.

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 constructor, and a named operator registers in the class's operator table, each as a fresh declaration of the member would place it.

The partial keyword is required to extend a class this way. A class declaration whose name is already declared, and which is not partial, is an error, so a program cannot fork a class's behaviour by an accidental re-declaration. It is a type error if 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 set of a sealed hierarchy and does not change a class's layout. Extending a class with a 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 (1.11.9), while a partial extension adds no subclass and is allowed. A partial declaration over a CLASS does not re-open the constructor or add fields; it is a channel for methods and operators. A partial declaration over an INTERFACE may add members, and adds no instance state by doing so, since an interface declares a shape and constructs nothing: the three reasons above - no subclass, no instance state, no change to a layout - all hold of it, which is why the restriction a class needs an interface does not. A member already declared on the interface is a TypeError rather than an override, so the meaning of an interface does not depend on the order its declarations load.

A partial declaration of a generic class or interface takes no parameter list of its own (1.4.3.1). With no list, its members are in the scope of the primary declaration's parameters: partial class Box { get(): T { … } } over class Box<T: type> adds a member to every application. A specialization list instead restricts the applications receiving the contribution. For example, 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 capture's bound.

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 replacement cases does not apply. Nested application patterns resolve the receiving constructor by declaration identity and expose its declared parameters, including argument labels, value domains, and defaults. Captures bind in the partial's member signatures and bodies; primary parameters continue to denote the target application's arguments. A wildcard imposes no additional condition and introduces no binding.

Within a partial class body, including member signatures and nested function bodies, a bare type reference to the name written in the partial declaration denotes the class application receiving that contribution. Preserve the resolved target's evaluated family identity and ordered arguments, including value arguments and arguments matched by wildcards or nested captures. This rule applies to instance and static members and to a target named through an immutable alias or renamed import. A nearer lexical declaration, type parameter or capture of the same name shadows it. An explicit type argument list, a qualified type name, and a reference outside the partial body retain their ordinary meanings. No new value binding is introduced; value-position lookup is unchanged.

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 constructor. Installing a contribution retains the actual target context, and temporary context is restored after either normal or abrupt completion. This does not alter partial registration order, computed-key effects, application admission, cache publication, storage or reference liveness.

Each partial is matched independently using the specialization-pattern rules. Every matching contribution is added; there is no most-specific winner among partials. Overlapping patterns are valid when they add distinct members. A member colliding with a primary member or another applicable partial's member is a type error, even when the signatures are identical. Overloads and paired accessors declared together within one contribution remain governed by their ordinary declaration rules. Instance and static member namespaces are distinct.

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 constructor and Type Object identities are preserved; instance storage, layout, and existing reference-liveness obligations are unchanged. New methods are checked with the same parameter, result, storage, and reference judgments as methods in the primary. An interface contribution extends the required contract without adding state or manufacturing members on existing objects.

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 contribution's source context and application pattern; do not flatten conditional members into every application. A nested partial declaration does not become a top-level module contribution merely because its source is contained in that module. Temporary checking passes and rejected source checks must not publish their partial checking summaries as successful evidence for another source. Rechecking a source must not duplicate its contributions, and discarding a failed source's checking publication must preserve successful publications from other sources. This is a rule for checking evidence, not permission to execute a dependency body, initialize a binding or change reference liveness; the existing loading, runtime registration and module checking boundaries continue to apply.

A statically established collision or incompatible member use is an early type error. Where the target or application is unavailable to the checker, the corresponding runtime operation performs the check. Loading an extension may require invalidating cached member and contract information; it does not authorize an engine to retain a proof based on the earlier member set.

Note

The metadata channel of the decorators and metadata extension is the principal use: a program adds typed Symbol-keyed members to an intrinsic metadata interface by declaring a 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 constructor is completed; a later partial statement cannot repair a failure at that point. Likewise, a superclass contribution must have been registered before it can supply actual declaration evidence for a derived constructor. Publication of a generic family is not completion of an application: matching contributions already registered for the family are evaluated on an in-progress application before its final interface admission. An application attempted before required registration fails at that admission point. After a caught failure, a later attempt following successful registration is checked again; failure supplies no successful application cache entry. These rules preserve ordinary statement order, computed-key effects, lexical availability and reference liveness. They do not hoist partial declarations or defer nongeneric constructor admission to the end of a source group or module.

1.12 Reflection

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 Type Object, the operation that exposes a type's structure, and the effect of a declared type on the existing reflective operations.

1.12.1 Reflect.typeOf ( value )

This function performs the following steps when called:

  1. Return GetTypeObject(RuntimeTypeOf(value)).
Note

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 true while Reflect.typeOf(m) is not float32.

It is the companion of typeof, which is unchanged and reports the underlying language type: typeof a is "number" for a uint8 binding while Reflect.typeOf(a) is uint8. typeof on a non-class Type Object is "object", and typeof uint8 === "object" therefore doubles as feature detection for this proposal.

1.12.2 The Opacity of a Type Object

A Type Object exposes no property that reveals the structure of the type it denotes. Given the Type Object for Player | null no property yields its members, and given the one for [].<uint32> no property yields its element type or its extent. A Type Object's [[TypeRecord]] is an internal slot and is reachable only through 1.12.4.

One exception is display: a Type Object has a toString method returning the canonical source form of the type it denotes, so calling String on the Type Object for 'a' | 'b' yields the String "'a' | 'b'". The printed form exists for diagnostics and authored error messages, not for inspection: its format follows the canonical form and is therefore stable, but parsing it is not the supported way to read a type's structure, which remains 1.12.4.

A Type Object is callable: it has a [[Call]] internal method, so a type may be applied to an argument as T(v). For a plain type the call is the explicit conversion of 1.6, the same operation as v := T. For an enum type the call is the reverse conversion of 1.11.8, returning the enumerator whose underlying value the argument is. This conversion call does not make a non-class Type Object a function to typeof, which reports "object" for it (1.12.1); the callability is the conversion surface, not a change of kind. Callability does not affect a Type Object's identity, so the interning of 1.2.2.9 is unchanged and two mentions of one type remain one callable object.

A Type Object carries [[TypeRecord]]. A class constructor also denotes its associated instance type, including a specialized class constructor's complete arguments. Type-input reflection operations resolve that denotation through ResolveReflectedType. This is an internal association of the actual constructor value, not an inherited property or a lookup by declaration spelling. A class constructor retains its ordinary call and construction behaviour and its "function" result from typeof; accepting it as a type input does not classify every callable as a Type Object.

Note

Opacity is what lets the interning of 1.2.2.9 be an implementation strategy rather than an obligation to materialize a graph. An engine that represents [].<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.

1.12.3 ResolveReflectedType ( value )

The abstract operation ResolveReflectedType takes argument value (an ECMAScript language value) and returns either a normal completion containing a Type Record or a throw completion. It resolves a type-input reflection argument to the Type Record it denotes. It performs the following steps when called:

  1. If value is a Type Object, return value.[[TypeRecord]].
  2. If value is a class constructor with an associated Type Object, return that Type Object's [[TypeRecord]].
  3. Throw a TypeError exception.

Use the association of the actual evaluated constructor, preserving its declaration identity, source context and ordered type and value arguments. A generic declaration constructor denotes its declaration base; resolving it does not apply defaults. This operation performs no property access, coercion, constructor invocation or specialization formation. A callable, class instance, bound function or Proxy with no association of its own does not inherit another value's association. In particular, no Proxy trap is invoked to discover a type.

Argument evaluation and specialization formation retain their ordinary order and failures before a reflection call. Resolving a type input neither initializes a binding nor grants access or reference-liveness permission. Reflect.typeOf takes a value rather than a type denotation and continues to use RuntimeTypeOf.

1.12.4 Structural Reflection

Reflect.getReflection.<Reflect.Type>(_t_) first obtains record as ? ResolveReflectedType(t), then returns a reflection node describing the structure of record. The node has a kind property whose value is a String naming the [[Kind]] of record, and the further properties of Table 33. Every property of a node that denotes a type holds a Type Object, so a walker recurses by reflecting it in turn.

Table 33: Reflection Nodes by Kind
kindFurther properties
"primitive"type, the Type Object itself; generic, either undefined or a record with base (an intrinsic declaration's String name or a nominal declaration Type Object) and arguments (a List of Type Objects and values), the decomposition of a generic instantiation. This one node covers every named leaf: the built-in primitives, classes, interfaces, enums, and the any, void, and type types. It also carries family, the String the Type Object's own family reports (Table 59), so a walker holding a node need not return to the Type Object to ask which of those a leaf is.
"literal"value, a String, Number, Boolean, BigInt, or Symbol; base, a Type Object.
"parameterized"base, a Type Object; metadata, an object: the metadata half of a parameterization such as float32.<{ m: 1 }>.
"union"members, a List of Type Objects, in the canonical order; empty for the never type.
"intersection"members, a List of Type Objects, in the canonical order.
"tuple"elements, a List of records with type (a Type Object), rest, and initial.
"array"element, a Type Object; extent, a non-negative integer or undefined for a dynamic length.
"object"properties, a List of records with name (a String or Symbol), type (a Type Object), optional, readonly, and initial; indexSignatures, a List of records with key and value (Type Objects).
"function"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 Type Object or undefined), and narrows (a List of records with target, a String or the String "this", and type, a Type Object). The metadata properties are always undefined here and are populated only where the same shape reflects a declaration (7).
"reference"target, a Type Object: the ref borrow type of 1.4.2.

There is no back-edge node. A reader walking a recursive type reaches the Type Object itself, so the reflection graph is CYCLIC and a recursive walk over it must track the types it has visited. A named back-edge is what a serializer needs to flatten such a graph and re-tie it on read; it is not what a reader needs, and it could not name an anonymous cyclic type.

Note

The node kinds and the Type Record kinds of Table 1 correspond, and the correspondence now round-trips: every kind is readable, and writable through 1.12.5, with two deliberate wrinkles. The named leaves, primitive and nominal alike and with them any, void, and type, share the one "primitive" node, whose payload is the interned Type Object itself with a generic instantiation additionally decomposed, so a leaf is opaque to a walker exactly as 1.12.2 requires and a nominal type never exposes its declaration. And the deferred kind has no node at all, because specialization evaluates every deferred application before any code runs, so reflection cannot encounter one.

A record's [[Origin]] (23) is not among these properties and is not reflected at all. It is a host-facing channel, and the reason is the one that clause gives: canonicalization unions origins across structurally identical declarations, so a program that could read one would observe its own type's origins change when an unrelated module declared the same shape. A tool reaches it by whatever means an implementation provides.

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 enum's enumerators are exposed. That context and the rest of the reflection facility, including its metadata, are defined by 7. This clause defines only the type-object context it operates on.

A union's arms are reflected in the canonical order rather than in the order written, since canonicalization has already reordered them and the Type Object is shared with every other spelling of the same type. A function's signatures are reflected in declaration order, which 1.10.2.1 preserves for this reason.

For a concrete intrinsic application listed in Table 11, the generic property contains base, the intrinsic declaration's String name, arguments, its complete normalized arguments, and parameters, a List of records with name, domain, and default where one is declared. A generic reconstruction with such a String base binds and validates arguments by 1.5.25 before interning. Nominal applications, including library types such as Promise, retain their declaration Type Object base. A bare 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 1.4.2.3 before interning. Preferred standard width aliases and explicit applications reconstruct to the same Type Object.

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 Type Object), and arguments. A fixed entry has kind: "fixed" and type or value; a wildcard has 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 wildcard is reflected in that equivalent normalized form. These descriptions are constraints, not concrete Type Objects. Reconstruction within a generic signature retains the declaration context and constraints; a standalone family-pattern description passed to Reflect.makeType throws a TypeError exception.

1.12.5 Reflect.makeType ( node )

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 1.4.2.7. A violation throws a TypeError here; a required compile-time builder invocation is rejected by the checking pass.

The input is structural: any object matching a node's shape in Table 33 describes that node. A Type Object or associated class constructor used as the input, or in a type-valued position within it, contributes the Type Record obtained by ResolveReflectedType. This function performs the following steps when called:

  1. If node is neither a Type Object, a class constructor with an associated Type Object, nor an Object matching a node's shape in Table 33, throw a TypeError exception.
  2. Let record be the Type Record that node, read as a description, describes.
  3. If record is not a valid type, throw a TypeError exception with the message the equivalent source declaration produces, or, where no declaration can write the type, a message naming the rule it violates.
  4. Return GetTypeObject(record).

For every Type Object T, Reflect.makeType(Reflect.getReflection.<Reflect.Type>(T)) is T. A class constructor supplied through its association round-trips to its associated canonical Type Object; resolving the denotation does not change the constructor value's identity. Construction canonicalizes and interns by 1.2.2.8 and 1.2.2.9, the same operations that give a source-written type its identity, which is what makes the round trip the identity function rather than an equivalence. Reconstruction is STRUCTURAL AND COMPLETE: a node's payload is rebuilt to its full depth, so a nested record within a parameterized node's metadata becomes a record again rather than remaining the object the node carried it in. A reconstruction that stops short produces a type that answers correctly to every question except identity — the same kind, the same base, the same field values, and stable under further round trips — which is why this paragraph states an identity and why a conformance suite that checks shape rather than identity does not test it.

Note

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 type error (1.4.2.1) and the construction is never (1.2.2.8). A written intersection is an author naming two types that share no value, which is almost always & typed for |; 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 1.4.2.9, and the declaration and a builder's construction reach the same Record. A recursive type is reached by a back-reference rather than a node, so construction ties its knot through the builder fixpoint rather than by accepting one; a constructed cycle arises only from the fixpoint of 1.2.7.3. The function is compile-time evaluable, and at run time it is an ordinary function returning the same interned objects, so String(Reflect.makeType(…)) prints the canonical form of 1.12.2.

1.12.6 Reflect.isAssignable ( source, target )

Its typed signature is (source: type, target: type): boolean; both parameters accept the type denotations resolved by ResolveReflectedType (1.6.2.24).

This function performs the following steps when called:

  1. Let sourceRecord be ? ResolveReflectedType(source).
  2. Let targetRecord be ? ResolveReflectedType(target).
  3. Return IsAssignable(sourceRecord, targetRecord).
Note

This is the checker's assignability relation (1.2.3.2) exposed unchanged, excluding the separate conversion availability of Table 13; the conditional types of an erased system are written 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 true for 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 compile-time evaluable.

1.12.7 Reflection and Declared Types

The existing reflective operations respect a declared type. Reflect.set on a property with a declared type t performs RequireType(value, t) and throws where the value does not satisfy it. 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 Object, and a descriptor with a type and no value gives the property the default value of that type (1.8.3.1), taken in the realm of that Type Object rather than in the caller's, since a parameterization's default is a crossing and a crossing consults a realm's declarations. Reflect.deleteProperty on a property with a declared type, and on a member required by an implemented interface, throws a TypeError exception.

A Proxy constructed with a type argument T has a [[RuntimeType]] internal slot whose value is T's Type Record, so Reflect.typeOf reports T rather than the shape of its target, and its traps are checked against T. A Proxy constructed without one has no such slot and is of 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 typed own property, is checked against that type at the trap boundary. A trap is refused where its result would make the type T reports false: the 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 function type whose signatures agree on a return, the apply and construct traps are checked against it. The Static Type of new Proxy .<T> Arguments is T, and the Static Type of a 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 typed class or a typed array throws a TypeError exception: such a value is backed by a layout rather than a property table, a field access does not consult a handler, and there is no point at which a trap could correctly run.

Note

Reflection is a boundary in the sense of Table 23, not an exception to it. A value entering a typed position through Reflect.set is checked exactly as one entering through an assignment, which is what keeps a typed field's type a fact about its storage: were reflection able to write past the check, an engine could not lay the field out on the strength of its type, and the proposal's premise would not hold. That a typed property cannot be deleted is the same guarantee in the other direction, since a layout with a hole in it is not the layout the type described.

A Proxy reporting the type it was constructed with rather than its target's shape is what makes the trap checks meaningful: the contract a proxy enforces is the one it was given, and reporting anything else would describe a value the program never promised.

At a statically established intrinsic Proxy construction or Proxy.revocable operation, it is a type error if every possible target alternative has a typed-array stamp or sealed typed class storage that ProxyCreate forbids. Include inherited sealing; 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 replacement, dynamic lookup, eval and unknown effects. Read only established intrinsic data; do not execute a getter, trap or other user code to obtain provenance. A run-time identity guard alone cannot justify an Early Error. Unresolved origins retain the run-time checks. The effect analysis is bounded in time as well as in kind: only an effect that can run before the selected construction can replace what the construction reads, so an operation after it in evaluation order, or in a function no counted region names, does not stand the proof down; and an operation whose every operand is a literal or a binding of one plain primitive type is the built-in operation, which no primitive block may replace (6.5) and which runs no user code, so it does not stand the proof down either. These requirements also govern the finite constructor-seed proof in 1.15.5.

1.12.8 Library Constructor Contracts

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 CanBeHeldWeakly and the specialized parameter type T. Apply ordinary named and positional argument binding and established spread contributions. Unknown constructor identity or argument correspondence defers the affected judgment; a shadowed constructor receives its own contract. No collection or liveness observation is needed for the type judgment.

FinalizationRegistry.<T> has constructor parameter 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 type error, and an unannotated callback parameter receives the held-value type. The callback's result is ignored. Named construction uses the public name callback. At an any boundary the corresponding argument contract is enforced at runtime. Construction never invokes the cleanup callback to check it and promises no cleanup scheduling; the held value remains unconstrained by weak-target eligibility.

1.12.9 Weak References and Typed Objects

An instance of a typed class cannot be held weakly. Passing one to the WeakRef constructor, using one as a key of a WeakMap or a member of a WeakSet, or registering one as the target of a FinalizationRegistry throws a TypeError exception. This is CanBeHeldWeakly returning false for such a value: a value type has no identity (1.5.1), so there is no object whose liveness a weak reference could observe, and no cell a weak collection could key on. A value class rejected here also cannot be the target of a Proxy (1.12.7): the instance of a typed class that seals its instances, a value backed by a layout whose identity its type, not the reference, governs.

Eligibility follows the effective class hierarchy. A subclass inherits a base's typed sealed storage even if the subclass declares no fields, and a 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.

Note

A reference type, an ordinary object or a class that carries no typed field and is therefore not sealed, is held weakly as before, since it has the identity a weak reference is about. For a value class, sealing typed storage removes weak-reference eligibility. The reference-kind exemption concerns identity, whereas Proxy eligibility concerns physical storage: a reference class with sealed typed fields remains ineligible as a Proxy target. The two predicates therefore do not have identical boundaries. A composite object is refused in the same positions for the mirror-image reason, an identity that coincides with contents rather than none at all; 25.7 states the exclusion and merges both into CanBeHeldWeakly.

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 CanBeHeldWeakly admits: object, symbol, any, an object, array, tuple or function type, a class that is not typed and sealed, or a union of these; it is a type error at the application otherwise, in every position an application can appear. A value type, a literal type, null, undefined, string, number, boolean, bigint, a composite type, and a typed sealed class are refused, and so is the nullable union of a typed sealed class, since the value a weak reference is checked against is the same instance under either binding type. The argument of the WeakRef constructor and the target and unregisterToken of FinalizationRegistry.prototype.register are checked the same way where their Static Type is known; 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 Static Type, and a bare construction of one of these intrinsics, reached directly or through an immutable binding, is as much a known receiver as a typed one. Where the Static Type is not known, CanBeHeldWeakly decides at run time as before.

1.13 The Numeric Library

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.

1.13.1 Overloading of the Existing Functions

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 1.10.3. A function that merely returns a number, with no parameter whose type could select a signature, is not overloaded and is unchanged. Table 34 lists the signatures: which functions, over which families, with what return, so that a call selects a signature as any overloaded call does and no conversion is written where the arguments already have a signature's types.

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 is a Number takes the OTHER branch for a value of a numeric type, so 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.

Note 1

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: ToBigInt throws a TypeError for a Number, and throws one for a uint32 as well, since ToBigInt performs no conversion from a sized integer type. The BigInt constructor's own Number step is unaffected and continues to answer for a Number. A program that wants a BigInt from a typed integer writes the conversion, u := bigint, and a boundary converts one exactly as it converts between any two numeric types (1.6).

The listing ranges over the sized integer types int.<N> and uint.<N> for every N the type system admits, over bigint, over the binary float types of 1.5.3, over the decimal types of 1.5.4, and over the rational types of 1.5.5. Coverage is not conditioned on which types an implementation has values for: the float128 and decimal rows are as normative as the rest. number is not in the set, because the existing signatures over the Number type are unchanged and are what an untyped call selects. The vector types are not in the set, their arithmetic belonging to the SIMD extension's operators, and the complex numbers extension declares the rows of its own family, noted below the table.

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 value type is assignable to another (1.2.3.2): a call mixing two typed widths is viable at no signature, and it is a type error, thrown as a TypeError exception where resolution happens at run time. The program states the conversion. A literal argument beside a typed one is ranked rather than typed and takes the parameter's type where it can represent it, so Math.max((1 := uint8), 3) selects the uint8 signature and is a uint8, while Math.max((1 := uint8), 300) is a type error, 300 being representable at no signature the 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 integer type, sized or bigint, the exact mathematical result is converted at T by the checked treatment of 1.7.7.1: a representable result is a value of T and an unrepresentable one throws a RangeError exception, so 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 Table 19, rounded to the nearest value of T with a finite result beyond the type's range becoming an infinity of its sign, which is the treatment the type's own arithmetic applies, so 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 RangeError exception. For a rational type the result is exact, and a fixed-width result whose lowest-terms numerator or denominator does not fit its int.<N> throws a RangeError exception. Two domain errors are raised before any result exists: Math.sqrt of a negative value of an integer type throws a RangeError exception, and Math.pow over an integer type throws a RangeError exception when its exponent is negative, whatever its base.

The semantics are stated on mathematical values. Where a row's result is exact, an implementation must produce it exactly at every width; an integer row computed through a binary64 intermediate that returns a wrong integer carrying the type does not conform. Where a row is implementation-approximated, the approximation is computed as if at the argument type's format or better and rounded once to that format. Math.sqrt is correctly rounded at every binary float and decimal width, as IEEE 754-2019 requires of the operation; at every width but one, the other approximated rows carry the latitude the Math functions have always had. At float128 they carry none: every implementation-approximated row is correctly rounded, its result the 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 integer and rational families have no such values, which is why their domain cases above throw instead. The existing conventions of the functions, among them the NaN and signed-zero handling of 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 integer roots are such answers: for an integer T, Math.sqrt and Math.cbrt return the exact root truncated toward zero as a value of T, the direction integer division truncates (1.9.6) and the semantics the BigInt Math proposal gives BigInt.sqrt and BigInt.cbrt. The transcendental functions have no integer counterpart, so they have no integer rows, and an integer-typed argument to one fails resolution; the program writes the conversion it means.

The listing declares one signature per parameter list: no function of the numeric library is overloaded on its return type alone, so a bare call is never ambiguous (1.10.5 remains available to operators and to user code, where a call without a contextual type is legitimately an error). A contextual type participates in resolution as it does anywhere. Where a call's numeric arguments are untyped literals, the contextual type selects a typed signature and the literals take its parameter type, so the call is a computation at T rather than a conversion of a Number result: const a: uint8 = Math.pow(2, 10); resolves at uint8, computes the exact 1024, and throws a RangeError exception at the return, while uint8(Math.pow(2, 10)) is the untyped call followed by a wrapping cast and is 0, the distinction of Table 22 visible at one call site. Where an argument is typed and the contextual type is of another family, no signature is viable and it is a type error; the cast the program then writes selects the computation by its placement, float64(Math.sqrt(10n)) being the integer root converted, exactly 3, and Math.sqrt(float64(10n)) the real root of the converted argument. A cast constrains nothing here: a Type Object call accepts any numeric source, so it supplies no contextual type and a call within it resolves bare.

Table 34: The Numeric Library Signatures
FunctionsInteger typesbigintBinary float typesDecimal typesRational types
abs, signA value of T; abs of the most negative value of a signed type throws a RangeError exceptionA value of the typeA value of TA value of TA value of the type; abs throws where the magnitude's numerator does not fit its int.<N>
min, maxA value of TA value of the typeA value of TA value of TA value of the type
floor, ceil, round, truncThe argumentThe argumentA value of T, exactA value of T, evaluated exactly; the result's cohort member follows the roundToIntegral rule of IEEE 754-2019The nearest int.<N> in the function's direction, checked at int.<N>
sqrt, cbrtThe integer root, truncated toward zero, a value of T; sqrt of a negative value throws a RangeError exceptionThe integer root, truncated toward zero; sqrt of a negative value throws a RangeError exceptionA value of T; sqrt correctly rounded, cbrt implementation-approximatedA value of T; sqrt correctly rounded, cbrt implementation-approximatedNo signature
exp, expm1, log, log1p, log2, log10, sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, asinh, acosh, atanhNo signatureNo signatureA value of T, implementation-approximatedA value of T, implementation-approximatedNo signature
atan2, hypotNo signatureNo signatureA value of T, all arguments of one T, implementation-approximatedA value of T, all arguments of one T, implementation-approximatedNo signature
powThe exact power, checked at T; a negative exponent throws a RangeError exceptionBigInt::exponentiate; a negative exponent throws a RangeError exceptionA value of T, implementation-approximatedA value of T, implementation-approximatedNo signature
clz32For N ≤ 32: 32 less the bit length of the mathematical value taken modulo 2**32, checked at _T_No signatureNo signatureNo signatureNo signature
clzN less the bit length of the mathematical value taken modulo 2**_N_, checked at _T_ (1.13.4)No signatureNo signatureNo signatureNo signature
imulFor N ≤ 32: the product's low 32 bits as an int32, whatever T its arguments carryNo signatureNo signatureNo signatureNo signature
fround, f16roundNo signatureNo signatureThe value rounded through binary32 or binary16, a value of TNo signatureNo signature
sumPreciseNo signatureNo signatureFor an iterable of values of one T: the exact sum, rounded once to TNo signatureNo 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 typed form Math.random.<T>() belongs to the random extension. The value properties of the Math object, Math.PI among them, remain values of the Number type.

The Static Type of a member access reading the length property of an array is the index type (1.2.6), as is the Static Type of a member access reading capacity. The value READ is a value of that type and not a Number, so a.length is index type is true where the array has an element type, and a count therefore carries its width into whatever it is used in. An array with NO element type is untouched: its length is a Number as it has always been.

The Static Type of a COMPUTED member access on an array — a[i] — is the array's element type. It had been the any type, which left indexing untyped in both directions of the check: a store to an element was checked against the element type and a read from one was not, so 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 integer, or not less than the extent is a type error rather than the RangeError it would raise at run time. An index that is not a literal keeps the run-time check, which remains the rule for every index; the static case is the one an implementation may elide, the extent being a compile-time constant (1.4.2.7).

Note 2

The bound on which functions are overloaded is a compatibility rule, and it is the same one that leaves parseInt and parseFloat alone (1.13.5) and that decides the bigint predicate rows (1.13.6). An overload is selected by an argument's type, so a call written without one is unchanged; but a function with no numeric parameter has nothing to select on, and giving its result a value type would change what every existing call returns. 'A'.charCodeAt(0) === 65 is true today, and a uint16 result would make it false, since the values of distinct value types are distinct (1.5.1). The same reasoning covers indexOf and lastIndexOf, whose === -1 is the universal idiom, the Date getters, and the byte lengths. Where a typed result is wanted from such a function, the program writes a conversion.

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 typed code read worse than untyped code, which is the failure mode the design is avoiding.

The existing signatures are unchanged, so Math.min of two Numbers is a Number and every existing program means what it meant. The overloads are additions, and a call with untyped literal arguments and no contextual type selects among them by Table 21, which ranks number first and so keeps an untyped call on the signature it selects today.

Note 3

The bigint rows are those of the BigInt Math proposal: 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 proposal places the operations on the BigInt constructor rather than on Math, because in the base language a 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 proposal value for value, and if the proposal advances, BigInt.sqrt(x) and Math.sqrt of a bigint are one mathematics with two spellings.

The integer roots follow the shape integer division set. The result type leaves nothing to decide, so the type decides, and toward zero is the direction this specification's integer arithmetic already rounds: Math.sqrt((17 := uint8)) is (4 := uint8), and Math.cbrt of a negative integer value is defined, the cube root of -9 truncating to -2. A program that wants the real root of an integer writes the conversion, one cast that states the meaning.

Note 4

Math.pow and ** deliberately part ways at an integer type. (2 := uint8) ** (10 := uint8) wraps to 0, because the operators wrap (1.9.6); Math.pow((2 := uint8), (10 := uint8)) throws a RangeError exception, because a declared return is a checked boundary. This is the a + 1 against Math.addChecked(a, 1) distinction of 1.13.2 again, stated here so it reads as the design it is rather than as a disagreement: the operator is the cheap wrapping form and the function the checked one. What ** itself does with a negative typed exponent is a question for 1.9.6; this clause specifies the functions only.

1.13.2 Checked and Saturating Arithmetic

The operators wrap on overflow (1.9.6). Where wrapping is not wanted, the named forms of Table 35 are used. Each is overloaded for every integer type and returns a value of its operands' type.

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 Table 35 therefore means the exact result of THAT operation, not of an operation in the rationals: the division forms divide as / divides, truncating toward zero (1.9.6), so 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 element type's range, or a binding initialized out of range, yields unrepresentable (Table 19) and raises. An arithmetic overflow is not a mistake about a value but an operation the type performs modularly by definition, which is what a fixed-width integer IS and what every use of one for hashing, checksums, or bit manipulation depends on. The two therefore meet without contradicting: in 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, Table 35 names the forms that refuse or saturate instead.

A zero divisor produces no result at all, so neither treatment applies to it: Math.divChecked and Math.divSaturating both throw a RangeError exception when the divisor is zero. Saturating is a rule about a result the type cannot hold, and there is no result here to be nearest to.

Table 35: Checked and Saturating Forms
FormResult
Math.addChecked, Math.subChecked, Math.mulChecked, Math.divCheckedThe exact result where the type can represent it. Otherwise a RangeError. A zero divisor is a RangeError.
Math.addSaturating, Math.subSaturating, Math.mulSaturating, Math.divSaturatingThe 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 RangeError.
Note

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 RangeError exception, and Math.addSaturating(a, 1) is 255.

These forms exist for the integer types because only there is wrapping the default. A float already saturates, to an infinity, and a decimal already raises a RangeError, since a decimal's range is a property of the type rather than of the format (Table 19). A rational already raises a RangeError on an unrepresentable result. Each family's overflow behaviour is therefore the one its values make available, and the named forms give the integer types the two the others have built in.

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.

1.13.3 Floored Division

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 integer type, and both also have a signature over the Number type.

A zero divisor follows the family. At an integer type both throw a RangeError exception, since an integer type has no infinity and no NaN and so has nothing to return. Over the Number type both answer as the operators do: Math.mod(_a_, 0) is NaN, as _a_ % 0 is, and Math.divFloor(_a_, 0) is an infinity, as Math.floor(_a_ / 0) is.

Note

These are the other half of the pair 1.9.6 describes: 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 assumed so that a program picks the pair it wants instead of receiving one by default.

Unlike the checked and saturating forms, this pair is not restricted to the integer types. Those exist only there because only there is wrapping the default, and a checked form over the Number type would have nothing to check. This pair is not about overflow but about rounding direction, which is meaningful for any real, and its own motivating use is written in untyped code: 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.

1.13.4 Counting Leading Zeros

Math.clz is overloaded for the sized integer types, and for them alone. For a value of an integer type T of width N, it returns N less the bit length of the argument's mathematical value taken modulo 2**_N_, as a value of _T_, checked at the return as 1.13.1 checks every declared return. The operation is a question about an N-bit two's-complement encoding, so the families without one have no signatures: bigint has no width to count from the top of, and the float, decimal, and rational types have no integer encoding to count in. There is no untyped signature; Math.clz32 is already the 32-bit count.

Note

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 field whatever its argument's width and clz counts in the argument's own. Typed code that means the argument's width, which is most of it, writes clz; clz32 remains for the 32-bit field itself and for untyped callers.

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 RangeError exception, the same edge the clz32 row of Table 34 notes. 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 Table 21, which selects the widest width that can represent it. That is well defined and almost never meant, since the width is the semantics here, so a program writes the width it means, by a typed argument, by an annotation on the destination, or by a cast: const n: uint8 = Math.clz(1); is 7.

1.13.5 Parsing

Each numeric type has a parse function. For an integer type and for bigint its signature is parse(_string_, _radix_ = 10); for the Number type and the binary floating-point, decimal, rational, and complex types it is parse(_string_). The Number type's 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 SyntaxError when the string is not a literal of the type, and a RangeError when it is a literal whose value the type cannot represent.

Each type also has a tryParse function with the same parameters, returning a value of the type where parse would return one and null where 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 null: that is a mistake in the program, and reporting it as a failed parse would name the wrong thing.

Note

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 integer type cannot represent 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 SyntaxError exception, since the string is not a uint8 literal, while uint8.parse('256') throws a RangeError exception, since it is a literal and the value is out of range. This is the same distinction Table 22 draws between a value of the wrong type and a value of the right type out of range.

tryParse returning a nullable union rather than a sentinel is what lets the failure be handled by narrowing (1.8.5): the result is a uint8 in the branch that tests it against null and needs no cast there. A string is deliberately not a conversion source for a numeric type, so let c: uint8 = '1'; is a type error and the parse is always written; 1.7.7.1 is where that is enforced, and it is enforced at the explicit conversion too, so '1' := uint8 is a type error for the same reason. The pair is what makes the strictness livable: a program that knows its input writes parse and lets the failure throw, and one that does not writes tryParse and handles the null.

1.13.6 The Numeric Predicates

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 1.10.3. Each overload takes one value of a numeric type and returns a boolean, answering as Table 36 gives for the value's family. Where that answer is the same constant for every family of the argument's type, the overload's result is that literal type, true or false, so a test of it is judged by 1.8.5.4. The literal result is the ordinary Static Type of the selected call, available equally to inference, equality, match and switch. Its proof requires an established intrinsic origin and applicable argument contract; a shadowed binding, replaced method or unresolved origin must not borrow a result from the intrinsic's spelling. A merely untyped binding does not acquire an inferred contract from this rule. The existing signatures are unchanged: an untyped call coerces, or declines to, exactly as it does today.

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 ToNumber on their argument, which throws a TypeError exception for a BigInt, so each declares one. The four Number statics already answer false for a BigInt, because a BigInt is not a Number, so none of them declares one and a BigInt passed to a Number static reaches that static's existing signature and is answered exactly as it is today.

Table 36: The Numeric Predicates
PredicateInteger typesbigintBinary float typesDecimal typesRational typesComplex types
isNaN, Number.isNaNfalsefalse, at isNaN; none declared for Number.isNaN, which answers false as it does todaytrue where the value is the NaN of Ttrue where the value is the NaN of Tfalsetrue where either component is NaN
isFinite, Number.isFinitetruetrue, at isFinite; none declared for Number.isFinite, which answers false as it does todaytrue where the value is finitetrue where the value is finitetruetrue where both components are finite
Number.isIntegertruenone declared; Number.isInteger answers false as it does todaytrue where the mathematical value is integraltrue where the numerical value is integral; 1.00 is integraltrue where the denominator is 1none declared; Number.isInteger answers false as it does today
Number.isSafeIntegertrue where the mathematical value's magnitude is at most 2*53 - 1; constant *true for N ≤ 53none declared; Number.isSafeInteger answers false as it does todaytrue where the mathematical value is integral with magnitude at most 2**53 - 1true where the numerical value is integral with magnitude at most 2**53 - 1true where the denominator is 1 and the magnitude is at most 2**53 - 1none declared; Number.isSafeInteger answers false as it does today

A complex answers the two classification predicates by its components, because a component can be NaN or infinite: isNaN is true exactly when either component is NaN, and isFinite exactly when both are finite, so a NaN component, not being finite, makes it false. These are the answers of Python's cmath.isnan and cmath.isfinite. It declares neither integer predicate, which ask about a place on the real line that a complex does not have; Number.isInteger and Number.isSafeInteger, which do not coerce, answer false for it as they do for any value that is not a Number.

The complex numbers extension declares its family's rows for all four predicates. The expected shape is the componentwise one: a complex value is NaN where either component is, and finite where both are.

Note

The integer and rational columns are constants, and that is the point of them: a sized integer is a type whose 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 RangeError exception instead: the values exist in the type (1.5.4), so the predicate tests the value rather than asserting a constant the type system does not guarantee.

Every test is of the mathematical or format value, never of a representation. At a width past 2*53, `Number.isSafeInteger` of an `int64` holding 260 is *false because the value is out of safe range, not because a payload lost precision.

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 proposal adds, so giving them answers displaces nothing. A BigInt needs no new syntax at all. Number.isInteger(5n) is false in code running today, and making it true under the flag would change the meaning of a program that opted into nothing, which nothing else in this proposal does. The rows are therefore declared where they displace an error and declined where they displace an answer: 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 true while Number.isFinite(1n) is false. The two functions differ in kind already, one coercing its argument and the other testing it, and this proposal closes only the case where the coercion threw; it does not make them agree, and it makes them disagree about no value that either answered before. 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 true and true where the magnitude is at most 2**53 - 1; they are recorded here rather than in the table because a table of what the predicates answer should not carry an answer no call can obtain.

The other rows repair a quieter hazard: without them, Number.isNaN of a float32 NaN is false and Number.isInteger of an int32 is false, each predicate silently testing representation where its name promises a value test.

1.14 Arrays and Tuples

This clause completes the array and tuple kinds: the membership test 1.7.6.1 defers, the default value of a tuple, and the checking of an element store.

A Tuple Element Record describes one position of a tuple type. It has a [[Type]] (a Type Record), a [[Rest]] field (a Boolean, true for a ... element), and an [[Initial]] field (an ECMAScript language value or none, the position's declared default).

1.14.1 Tuple Sequence Domains

An established tuple sequence domain interprets a tuple's fixed elements, finite array rests and nested tuple spreads in order, when the result has at most one unbounded homogeneous array rest and obeys the effective default-placement rules of 1.4.2.7. A fixed element contributes one position of its [[Type]], retaining its declared default if any. A rest whose operand is an array with finite extent N contributes exactly N required positions of the array's element type; extent zero contributes none. A tuple rest contributes its ordered contents recursively. A dynamic array rest contributes a possibly empty homogeneous run. An unresolved extent or repeated active tuple in recursive spread expansion establishes no new positional domain.

Several written unbounded rests may also establish this domain. After ordered tuple expansion, combine consecutive unbounded regions only when their element types are the same type and every required position between them has that same type. Retain the intervening required count: its positions may be represented at the end of the fixed prefix of the homogeneous region because every affected occupied position retains the same type at every admitted length. Repeat this judgment for further unbounded rests. Zero finite extents contribute no positions. Preserve the effective default-placement rules and the original default records. The result must have at most one unbounded homogeneous region; different intervening types, unresolved counts and active recursive expansion supply no such proof. This positional equivalence does not merge Type Objects, erase finite minima, or select a partition from current values. All established-domain rules below, including contextual conversion, retained backing and independent reference liveness, apply to this family. SequenceAssignment retains its original count judgment where the distribution among written slots is required.

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 element type. Omission never skips a supplied position. Several finite rests may contribute to a fixed region. Positions and lengths are determined from declarations and counts, never from current element values.

This is a semantic expansion, not a requirement to allocate one Type Record per repeated position. Implementations may retain finite counts and declared run boundaries. It does not change CanonicalizeType: tuple operands still flatten there, while finite array rest records retain their existing reflected form and identity. Mutually assignable positional domains need not be the same Type Object.

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 finite or nested expanded domain. These judgments neither read elements nor weaken reference invariance.

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 element type's default if none is declared, and gives an unbounded rest zero elements. Omitted converting construction retains its logical output length and source default records by effective position, without expanding a finite count into type metadata. It reuses supplied observations and selected evidence, applies the existing overlapping-domain and publication rules, and produces a separately admitted Array before an enclosing setter receives it. A zero finite count does not request a default. Publish the complete domain on the produced tuple.

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 finite domain uses the existing bounded positional copying rules; the presence of a written finite rest does not turn it into an unbounded copy. Established literal lengths and singleton indexed-read proofs use its exact positional map and bounds. This does not establish the output of a replaceable iterator or broaden unknown-spread contextual typing.

1.14.2 Ordinary Nominal Tuple Membership Storage

This clause extends owned Array membership admission to ordered tuple patterns containing ordinary identity class types together with the scalar types of 1.14.5. Each class target is a resolved, non-generic source class with the permanent own prototype identity required by 1.7.7.2. Value class copying, metadata, union element types, defaulted positions, unresolved counts and active recursive expansion are outside this specialization. Required fixed positions, finite array rests, dynamic array rests and nested tuple spreads retain their ordered counts; zero contributes no position. Stable positional domains retain their existing location contracts and receive the same protection of nominal child membership.

A successful receiving membership admission retains the complete ordered pattern on the owned Array, including admission reached through a selected structural property, union, intersection, parameter or return contract. Pure IsOfType retains no guard. Preserve any previously acquired storage contract and every independently acquired guard. At an otherwise unestablished location, derive its position projection from declarations, counts and admitted length as in scalar rest storage; do not narrow it to a child's current class or to the first successful partition.

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 path requiring an exotic prototype observation supplies no ordinary witness. Keep the existing selected evidence and earlier-effects rules; failure publishes none of this admission's new guards and does not undo an independently completed receiving boundary.

A retained owner guard depends on the ordinary prototype links that support its accepting membership evidence. Before publishing a change to any such link, check every affected retained owner's complete sequence against all its acquired patterns, using the prospective replacement at that link. All other positions retain their current owned data values. A child may change roles, and the assignment may change, if the complete pattern remains satisfied. Apply independently acquired exact class obligations as well. Do not invoke a getter, conversion, constructor or Proxy trap during this check. If an affected owner has no accepting ordinary assignment, the ordinary [[SetPrototypeOf]] operation returns false before changing the link or any live dependency. Retain its existing same-prototype, extensibility and cycle checks and their order.

After a permitted prototype publication, update the affected owner dependencies from the new accepting evidence without intervening user code. Previously unsuccessful roles need no independent dependency if the retained accepting evidence remains guarded; whenever that evidence changes, reconsider the complete pattern. A valid new prefix becomes guarded, and obsolete prefixes cease to constrain that owner. A target prototype's own outgoing link is irrelevant to a role that already reached it, but another role or owner may depend on that link.

Ordinary element and length publication must preserve all retained ordered guards and existing location contracts. Refresh nominal dependencies from the actual resulting owned data without intervening user code. Replacement or removal retires only the removed owner's relationship to that occurrence; repeated occurrences, other owners and independently completed exact class boundaries retain their obligations. A partially failed length truncation retains dependencies for its actual surviving prefix and releases the removed suffix. Follow the existing staged insertion, removal, failure-precedence and reference-liveness rules. Repartition alone does not remove a location or retarget a reference. After user conversion effects, revalidate the prospective complete sequence rather than treating retained element identities as unchanged membership facts.

Dependency bookkeeping must not strongly retain an owner or its child/constructor graph solely through a surviving prototype or child. The weak-clearance timing allowance of 1.7.7.2 applies to these owner dependencies. Cleanup invokes no ECMAScript code. This clause changes neither language-level weak-target eligibility nor independent reference removal or relocation semantics.

Note

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 ordered sequence and is refused; changing only the second may succeed. Removing an occurrence releases that owner's dependency, not another owner's promise.

This membership and storage specialization is extended by the bounded selected constructor plans below. A constructor body or nominal default is never a speculative scalar computation. General constructor/operator applicability, initial Proxy/array-like backing, metadata, value-class copy transport and structural descendant roles require their separately specified rules.

1.14.3 Selected Ordinary Constructors in Ordered Rests

This clause extends the ordered conversion and store planner to the ordinary identity-class patterns of 1.14.2 when no established positional sequence domain applies. Existing complete unchanged membership takes precedence. Otherwise retain indexed observations, form candidates without executing constructors, and select a complete count assignment under the scalar-rest ranking and all acquired backing obligations. Required runs cannot be consumed by an earlier rest.

A bounded constructor candidate requires an ordinary class target and an explicitly declared constructor body with exactly one simple value binding parameter, without rest, reference, or destructuring. The parameter may be optional or carry a default. Retain every eligible body and its parameter Type Record resolved in its declaration context; a reflection record for the first body is not the overload group. Constructors of other arities do not become converting constructors merely because defaults permit a one-argument direct call. An unavailable annotated signature supplies no proof, rather than an implicit any parameter. An unannotated or any parameter admits the supplied value. A primitive parameter may admit an existing member or an intrinsically pure checked conversion under the scalar-rest purity rules. An ordinary identity-class parameter may admit an existing ordinary member. Do not run a constructor, source conversion operator, metadata operation, getter, Proxy trap, reference read or effectful parameter conversion to discover applicability. Generic destinations and other parameter forms require a separate applicable plan.

A supplied undefined value at an optional or defaulted parameter uses its ordinary optional/default binding rule. A supplied non-undefined value must satisfy the parameter admission proof regardless of its default. Applicability does not evaluate a default initializer; declaration-time compile-time evaluability and validation, and selected parameter binding, retain their existing requirements. Absence of a retained declaration set or an unavailable signature is not proof that no converting constructor applies.

After pure parameter admission has removed unavailable candidates, rank the remaining converting constructors using ordinary overload resolution. Retain a unique selected body, the class constructor and the original supplied argument. Retain an ambiguity as a selected applicability error and report it before invoking selected user conversions or initializing an instance. Do not treat it as a failed pure parameter conversion that permits another assignment or conversion form. A run contributing no selected item requires no selection.

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 TypeError exception if none exists. Constructor arity alone is insufficient proof of applicability. Selecting a constructor uses the existing constructor precedence; this clause supplies no speculative operator fallback.

After selecting the complete assignment, execute each retained constructor body exactly once in input order with the retained supplied value, using its class constructor for allocation, instance fields, private state, this binding and NewTarget. Do not rerank its overload group after field initializers or earlier selected conversions have performed effects. An invalidated actual parameter admission fails the selected operation instead of selecting a different body. Ordinary direct construction retains its existing overload resolution. Its actual parameter boundary remains checked, including when the enclosing tuple conversion was explicit. Propagate an abrupt completion without trying another assignment or another conversion. Verify that its normal result is an ordinary member of its selected class; otherwise throw a TypeError exception. A successful selected class conversion completes its independent exact prototype obligation under 1.7.7.2. An unchanged nominal child does not acquire an exact class obligation merely from its selected tuple role.

After the selected operations, validate the complete prospective sequence against every acquired ordered pattern. A later constructor effect may change the nominal membership of an earlier retained Object. Accept a valid repartition; refuse an invalid complete sequence before publishing it. On a fresh converted Array, install both the declaration-derived projection and the complete pattern, and establish its ordinary nominal dependencies before publication. On an existing owner, also revalidate the retained data, length, guard and backing observations before the current store. Preserve earlier effects and independently completed receiving obligations on failure. These checks do not grant new reference authority or restore a removed or relocated reference.

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 constructor on a retained movement value while planning the new supplied item. Existing SameType movement geometry and failure precedence remain applicable.

Note

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 constructor taking Boolean is not applicable merely because it has one parameter. A selected constructor that throws is not retried through another rest.

The operator extension below adds bounded source and inbound plans. General effectful or composite parameter checks, nominal defaults, and generic specialization require separate plans. Their absence from this specialization is not a general language prohibition.

1.14.4 Selected Ordinary Conversion Operators in Ordered Rests

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 Type Record, callable, form and parameter signature in the declaration's context. Target matching uses SameType, including nominal declaration identity; display spelling is not identity. Retain all same-form alternatives rather than overwriting the previous declaration. An unavailable annotated signature is not an unannotated any parameter.

Source lookup follows ordinary internal prototype links without observable Gets or Proxy traps. For the exact target, use the nearest prototype's source declaration set; a nearer declaration for a different target does not hide it. Inbound candidates come from the target's own declaration set and must declare that exact target. Source declarations have no parameter. Inbound parameter applicability uses the simple checked proof of 1.14.3. Initially exotic lookup, partial additions, static forms and more general signatures require separate coverage.

After complete unchanged membership has failed, prefer an applicable converting constructor, then an applicable source conversion, then an applicable inbound conversion. A proved inapplicable constructor may give way to a later form; a selected constructor's throw may not. A signature whose applicability is not established by this bounded proof must not be treated as proved inapplicable in order to skip a higher-priority form or a competing declaration. Within an inbound form, discard a candidate if another applicable parameter type is a proper subtype of its parameter type. An unannotated parameter has type any. More than one maximal applicable candidate is ambiguous; source declarations for the same selected target are likewise ambiguous when no unique declaration remains.

Retain an ambiguous candidate during the complete count assignment. If the selected assignment includes an ambiguity, throw a TypeError exception before invoking any selected user conversion. Do not discard the ambiguous candidate to retry a less-greedy assignment. Whole unchanged membership and a run that contributes zero selected items do not invoke or select an unnecessary conversion. Pure parameter failures retain their existing unavailable-candidate and failure-order rules; ambiguity is a selection error, not such a parameter failure.

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 undefined as receiver and the retained input value as its argument. Do not repeat operator lookup after earlier conversion effects. The actual inbound parameter boundary remains checked even inside an explicit tuple conversion. Invoke selected conversions once in input order, propagate selected abrupt completions without fallback, and verify every normal result against its selected ordinary class. A successful result completes the independent exact obligation required by the constructor-plan clause.

Retain the same post-effect complete-pattern validation, owner/backing revalidation, publication order and reference authority/liveness rules as the constructor plan. A changed source lookup path does not replace a retained callable. A changed nominal role in an earlier resulting child still participates in final complete-pattern validation. Independently completed parameter and result obligations survive a later outer failure. Declaration registries must not globally retain an unreachable owning prototype solely because its declarations were recorded.

Note

This clause specifies the bounded ordered-rest extension, not a replacement of all general conversion dispatch. General signature evaluation beyond the supported constructor proof, metadata and generic contexts retain their separate proof obligations.

1.14.5 Scalar Rest Storage

This clause specifies the owned Array specialization for a tuple whose ordered expansion consists of required fixed positions, finite array rests and dynamic array rests over unparameterized number, string, boolean, bigint, symbol, null, undefined, float16, float32, float64, or resolved int and uint types. Nested tuple spreads retain their ordered contributions. A finite count must be resolved and non-negative; unresolved counts and active recursive expansion supply no specialization. An element type carrying metadata, a union element type or a defaulted position is outside this specialization. An already established stable positional domain retains its existing rules. This specialization does not define the general heterogeneous rest conversion or storage algorithm.

The tuple's ordered scalar pattern is a List of scalar Type Records with minimum and maximum counts. A required position has counts one and one; a finite array rest has its extent for both counts; a dynamic rest has minimum zero and unbounded maximum. Zero-count runs contribute no positions. A List of values satisfies the pattern if it can be partitioned into consecutive runs within those counts, each of whose values satisfies that run's scalar type. Repeated types retain their written run positions. For an admitted total length and occupied index, the position projection is the canonical union of the scalar destinations that can occupy that index in any complete count assignment of that length. Determine these possibilities from declarations and counts, independently of current values or a selected partition. The pattern, and not this union alone, constrains the sequence. Counts may be retained compactly without allocating one record per finite element.

A successful owned Array admission retains its ordered scalar pattern as a guard together with the ordinary selected structural evidence. Direct admission and admission selected through a property, tuple or array element, union, intersection, parameter or return boundary have the same obligation. Pure membership acquires no guard. Publish no new guard from a failed admission. Before publication, revalidate the observed length, initialized own data elements and their values against the retained observations and every newly acquired pattern. Independently established backing is not evidence that those values remain unchanged. Existing narrower element, tuple and property contracts remain authoritative. A new guard adds an obligation and does not replace those contracts.

A fresh owner with no established positional or element backing uses the position projection as its invariant location contract and retains the complete pattern separately. Guard acquisition neither freezes the current partition nor changes Type Object equality. Identical guard reacquisition does not reorder or replace a guard. Distinct guards are retained in successful acquisition order; guards acquired together retain the selected admission traversal order. The earliest guard supplies the written greedy ranking for later stores, subject to all guards and all existing backing obligations.

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 1.14.6, and select a destination for every item before executing effectful conversions. General rest assignment obtains its indexed input before searching; this clause does not introduce additional source Gets. An already admitted scalar is a viable candidate unchanged. For an intrinsically pure scalar conversion, its result and range failure may be determined and retained without invoking user code. For an effectful conversion, determine only whether the existing implicit or explicit rules supply an applicable operation; do not execute it during search. An implicit numeric-family prohibition remains a prohibition.

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 RequireType, including the rule that a value of a proposal numeric type retains its type through any. The same RequireType guards govern fixed tuple positions, array elements and object members under Table 23. Preserve this admission mode through nested receiving operations and construction-result forwarding. Thus an any source containing decimal128(7) does not implicitly fill [...[].<bigint>, ...[].<number>]; it throws a TypeError. An explicit conversion of that source uses the explicit numeric conversion rules instead. Retaining a pure result does not change which of these two operations the program requested.

Select the greatest count List in the existing leftmost-greedy order that accounts for every item and satisfies every acquired guard and backing obligation. On a conversion to a fresh tuple there is one pattern, the receiving tuple's. On a store, rank by the earliest acquired guard; additional guards constrain the result without replacing that ranking. Required counts must be filled. Retain every viable assignment of an additional guard, including alternatives where an earlier run gives an identical scalar back to a required later run; selecting only its earliest matching run is insufficient. Existing values not supplied by the operation cannot be converted. If the complete unchanged proposed sequence already satisfies these obligations, retain it without conversion. A missing new position contributes its selected scalar type's DefaultValueOf, where that is not none; a supplied undefined is a value and is not missing.

If no assignment is viable, propagate the first retained pure scalar conversion failure in input-index and written-run order, or throw a TypeError exception if no such failure exists. After selection, execute each selected effectful conversion once in input order, retain pure results, and verify every result against its selected scalar destination. Propagate a selected conversion's abrupt completion without trying another assignment. A normal result outside that destination throws a TypeError exception. Publish a converted Array only with the projection and ordered guard needed by its successful target admission. Default construction gives each dynamic run zero elements and independently initializes each required scalar position from its scalar type's DefaultValueOf. If any required position has no default, the tuple has no default. A zero count does not request a default. The result carries the complete backing and guard.

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 ordered pattern; they do not repeat the first rest's default regardless of surrounding values. An accessor cannot replace an owned typed element. Ordinary nonextensibility, length writability, fixed extent, descriptor compatibility and the existing initialization/publication rules remain applicable. Every proposed length must satisfy all acquired count ranges, including when earlier broader backing is retained. A resize must preserve SameType for every surviving location of the actual backing. Check these geometric obligations before indexed growth conversion or intrinsic element work. They do not establish that the eventual values satisfy the ordered guards.

A finite scalar pattern need not admit every prefix of an admitted sequence. For owned Array truncation, after the existing nonwritable-length refusal and before deletion, determine the actual stopping length from current own descriptors and independently protected typed properties. This is the larger of the requested length and one more than the greatest index at or above it whose deletion would fail. Inspect no getters and execute no user code. Both the requested prefix and that stopping prefix must satisfy every guard, and the actual backing must preserve every surviving location contract at either length. Otherwise throw a TypeError exception before this truncation deletes any elements. If both qualify, perform ordinary descending deletion, partial-failure length restoration and requested writability handling. Do not roll back earlier effects, including independently completed length-conversion effects or earlier valid intrinsic writes.

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 TypeError exception; do not overwrite or undo the independent effect. An earlier abrupt completion retains its precedence. Reacquiring an identical guard is not a change. New independently acquired property obligations also apply to the final ordinary definition. No new guard or converted element from the failed receiving operation is published.

Intrinsic movement requires the existing SameType relationship between declared source and destination locations and preserves the moved value. It must not use conversion to repair an ordered-guard violation. Keep ordinary reads, comparator calls, writes and abrupt-completion order. Check every actual published state; earlier valid writes survive a later failure. Growing insertion continues to retain only its designated private tail under 1.14.10.2, and removal retains 1.14.10.3. A successful geometric plan does not waive a subsequent guard or liveness check.

A borrow uses the established invariant location contract, following reference aliases and Proxy aliases of an already guarded owner. The current scalar member does not narrow that contract. A known reference target other than any must be SameType with the known location contract other than any; value membership alone is insufficient. Existing narrower backing remains the source of this contract. Allocation generation and element-removal identity follow the actual owned backing even through a Proxy alias. Recheck the receiving borrow after conversion effects and immediately before its final own data definition, even when intervening growth or removal restored the original length. Replacement alone preserves identity, while removal/recreation and relocation retain the failures in 2.10.

Note

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 shape still produce an ordinary untyped Array.

1.14.6 Source Observations During Sequence Conversion

A concrete array or tuple receiving operation retains its own source observations across membership and subsequent elementwise conversion, for both implicit and explicit conversion. A failed membership result discards speculative admission evidence under the existing rules, but does not discard the source values already obtained by that operation. This rule concerns the sequence's own length and indexed properties; it does not combine separate candidate, member or child receiving operations.

Associate an initially empty observation record with the source Object and this concrete operation. It has a retained length value, initially unobserved, an optional completed length conversion, and indexed entries. To obtain the sequence's length or an indexed value:

  1. If this observation record already has a value for the requested property, return that value.
  2. Let value be ? Get(the source Object, the requested property key).
  3. Retain value in the corresponding entry of the observation record.
  4. Return value.

Use this operation at the sequence-read points of membership and conversion. Retain actual undefined as a value; it is not unobserved. Where the receiving operation converts the retained length to a count, retain that completed conversion as well and reuse it if needed again. A later source mutation does not replace a retained value or count. An abrupt Get or length conversion propagates immediately. This does not change the accepted length values, the existing count conversion, or the order of shape checks.

Observation is lazy: retention performs no additional source Get. For a homogeneous array or an established positional tuple domain, membership visits indices in increasing order and stops at its first failed position. For general tuple rest matching, obtain the supplied indexed values in order before the assignment search and reuse those values for its alternatives. Conversion reuses the observations that membership obtained, then obtains any remaining supplied values in increasing order as their conversions are reached. An earlier abrupt conversion prevents later Gets that membership has not already performed. Preserve the existing impossible-length checks before indexed Gets.

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.

1.14.7 Membership

For an array Type Record t, a value v is of the type t when v is an Object, v's length is t.[[Extent]] where that is not dynamic, and every element of v is of the type t.[[Element]].

For a tuple with an established sequence domain (1.14.1), membership uses that domain's admitted lengths and exact position types. For a tuple Type Record t without a rest, or with a defaulted fixed prefix followed by one trailing unbounded homogeneous array rest, a value v is of the type t when it is an Object, its length is at least the number of required fixed positions, it has no surplus positions unless a rest collects them, and every supplied value is of its positional type. An index within the declared fixed prefix has that position's [[Type]]; every later index has the rest array's element type. Only unsupplied fixed positions are optional, exactly when their [[Initial]] is not none. A supplied value, including a hole's ordinary Get result, cannot skip an incompatible defaulted position. For rest shapes not covered by these domains, use SequenceAssignment (1.10.4.2) over t.[[Elements]] and v's length, retaining the same positional restriction on defaults before the first rest. The predicate checks each supplied value against its receiving fixed type or the collected rest's sequence type, and success requires an assignment other than unmatched.

For general rest shapes, retain the collected sequence's count constraint inside SequenceAssignment's admitsRun predicate. A finite array rest of extent N accepts exactly N items, including zero when N is zero; an unbounded array rest imposes no exact count. A failed collected count continues the ordinary greedy giveback search rather than rejecting only the first unconstrained assignment. Expand tuple spread operands in their declared order before this judgment. A defaulted fixed position may take zero items only when the supplied sequence is exhausted at that position; it cannot skip a supplied incompatible value to let a later rest receive it.

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), finite array rests (their exact extent), dynamic array rests (zero to unbounded), and nested tuple spreads. An unresolved count or active recursive expansion supplies no new range proof. If an established range excludes the observed length, membership returns false before indexed Gets, and tuple conversion throws a TypeError exception before indexed Gets or element conversion. A literal with an established exact produced count outside that range is a type error, including in an uncalled body; an unknown iteration count is not such a proof. A possible total length establishes neither a successful type assignment nor a unique positional backing contract. These count judgments do not select new storage contracts or weaken reference invariance.

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.

Note

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 typed position; an array whose static type is already the target is not checked at all (1.7.9), and 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 object and function kinds raise, and it has the same answer here as there: the check as specified is eager, and a lazier one would need the wrapper the higher-order case would need.

1.14.8 Static Iteration Contribution

A possible non-terminal result with value?: T contributes T | undefined: reading an absent property supplies undefined. Optionality does not promise a value and does not by itself erase the known contribution. Use this read type consistently for synchronous, asynchronous and delegated iteration, including loop bindings, spreads and array patterns. Apply the selected operation's await stages afterwards. A terminal alternative contributes only where the operation consumes its completion value; no yield is invented for a done-only result. Unknown result shapes or any retain their existing uncertainty.

Exclude an iterator-result alternative as terminal only when its done property is required and established truthy. In particular, done?: true may be absent and then reads as undefined, so that alternative's value remains a possible yielded value. Optionality and all viable union alternatives participate in the contribution. Use the same distinction for loops, spreads and array patterns.

Apply the same conservative contribution judgment to every array-pattern destination: an explicitly annotated binding, a binding inferred at an existing participating site, an existing assignment location, and a collected rest destination. Preserve independently established fresh-literal or immutable-sequence positions. A done-only protocol contributes no yielded value. Iterator exhaustion, defaults and closing retain their existing semantics; a storage extent does not establish an iteration count. Store permission, reference decay and liveness judgments still apply to assignment locations.

For for await, derive the contribution from the selected async or sync protocol. A real async iterator awaits the result of 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 synchronous iterator's contributed value. Thus a real async iterator may contribute a Promise value, while a synchronous iterator yielding Promises contributes their assimilated values. Preserve optional-hook and union alternatives; any unresolved path leaves the contribution unknown. Done-only paths contribute no value. Apply this contribution to typed loop bindings, assignment heads and unannotated body bindings. Indexed storage alone supplies no exact iteration count.

For a fixed parameter list and a single trailing positional spread, it is a type error when a required value parameter that no other argument can fill rejects both omission and every possible yielded value. Omission supplies undefined. Use the ordinary parameter boundary, including implicit conversions, rather than assignability alone. This proof needs no yield count. Defaults, optional parameters, admitted undefined, a viable overload or an unknown contribution prevent the proof; more complex mappings retain the judgments their available facts establish. The rule applies to ordinary calls, constructor calls and super calls.

A value spread in a call, a constructor 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 element type, where the selected iterator's declared protocol establishes one, and a known ordered sequence of position types, where that protocol also guarantees the sequence. Either fact may be unknown. An element type alone establishes no minimum, maximum, or exact number of yielded values.

It is a type error if a spread argument's Static Type proves that it can participate in neither positional iteration nor the named object-spread binding of 1.10.4. This judgment applies independently of whether the callee's signature is known, in an ordinary call, construction and 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-iterable object merely because it supplies arguments by name.

A declared iterable protocol (28) supplies the type of the values returned by its iterator's next method when done is false. The terminal return value contributes no element to a spread or an array pattern. A signature for an iterator method supplies these facts only where the method selected by the access is known to satisfy that signature; an unknown method lookup or an unconstrained replacement leaves the affected facts unknown.

A mutable Array's array or tuple storage type does not by itself constrain its 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 (25.7.2); a fixed tuple shape without optional or rest positions supplies those ordered position types and their exact count.

The same contribution is used when checking call arguments, when checking an array literal against an array or tuple contextual type, when deriving an array literal's own Static Type, and when deriving an array pattern's position types. An array pattern uses a guaranteed sequence's corresponding position type, or the guaranteed yielded element type where only that fact is known; an unknown contribution leaves its unannotated binding's type unknown. An explicit sub-pattern annotation still tests and narrows the yielded value. Matching retains its iterator cache, effects, length test, and non-match behaviour. A successful length test establishes no indexed storage type or storage extent. A known sequence preserves positional correspondence for following arguments or elements. An unknown count loses correspondence from that position onward, but does not erase preceding known positions or the types of subsequent contributions. A contribution is checked against the destinations its known facts establish, including a homogeneous rest parameter or array element type; a positional or arity judgment requiring unavailable facts is deferred under 1.2.8. A value obtained from iteration is not a fresh literal expression and acquires no literal-propagation privilege merely because its indexed source once held a literal.

Note

Replacing an iterator may change both its yielded values and their count without changing indexed storage. For example, a tuple storing one String may have an iterator that yields two uint8 values; an ordinary spread observes those two values and the iterator's effects. A runtime guard used to optimize that lookup cannot justify rejecting a source text before the guard runs.

This judgment does not change the separate index-based semantics of reference iteration (2.7), or the binding of spread type arguments (1.4.2.2). The additional eligibility rules for binding a variadic parameter (4.4) do not substitute indexed storage facts for the iterator's contribution to ordinary value arguments.

1.14.9 The Static Type of an Array Literal

Where a contextual type (1.6.5) requires an array or tuple type, an array literal is checked against it element-wise and the boundary converts, so the literal reports no Static Type of its own and the conversion that builds an Array carrying the element type is not elided.

During evaluation, each direct element receives the contextual type of its own destination. For an array, this is its [[Element]]. For a tuple with a declaration-proved fixed prefix, homogeneous rest and required suffix domain, use that positional domain, including nested tuple rests and finite array runs. With no source spread remaining, the actual accumulated element count plus the remaining direct elements and elisions determines the final supplied length. Use that length to anchor suffix positions without evaluating later expressions. A trailing comma contributes no position; omitted trailing defaults are completed under their existing rules rather than counted as supplied elements.

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 SameType, and retain finite runs compactly. A remaining fixed tail may exclude all suffix alignments. Where different destination types remain possible, this rule supplies no unique tuple position context. Where no declaration-proved positional domain is available, it supplies none. These judgments neither inspect element values nor invoke a getter or iterator, and do not establish that the eventual supplied length or value will be admitted.

A direct element's fresh construction result retains its selected alternative through the enclosing Array's live data-property contribution under 1.7.2. Perform admission at the ordinary receiving boundary, after the same element and iterator effects. Replacement or deletion of any intervening contribution, including replacement by the identical value, ends that association. Propagate failure of the selected alternative without falling back to another arm; retain the declared union as the storage contract. Ordinary reads, spread operands, and values obtained from ordinary iteration do not gain a pending construction choice under this rule. Reentrant boundaries remain independent, and reference liveness is unchanged. An abrupt element completion prevents the later receiving admission.

These evaluation rules do not make an observed iterator count available during type checking. Static checking continues to use the declared iteration contribution under 1.14.8, including its uncertainty rules, and retains every independently provable diagnostic. In particular, a later indexed read from an unknown-length tuple retains its possible-position type even if a particular initializer had a known supplied length.

Where no such contextual type reaches it, the Static Type of an array literal is the array type whose [[Element]] is the join of its elements' Static Types, each widened as a contribution is (1.6.3), and whose [[Extent]] is dynamic. An elision contributes undefined and a spread element contributes the element type of its static iteration contribution (1.14.8), where known. Indexed storage alone does not determine that contribution. Where any element's Static Type is unknown the literal's is unknown, since an array holding something unspeakable is not an array of the join of the rest. An EMPTY literal likewise has no Static Type: it says nothing about its elements, and reporting an element type of never would say something false, since element types are invariant and a never-elemented array is assignable to no other array type.

Note 1

A literal every element of which is a literal does not ANCHOR an inference (1.6.2.2). [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.

Note 2

This rule changes the meaning of one shape of program that carries no annotation, and the change is intended rather than overlooked.

  1. [1n].includes(1) evaluates to true, where in the absence of this proposal it evaluates to false.

The literal is a [ ] . < bigint >, so includes is read at that element type and its argument is a bigint position; literal propagation then builds the argument as a BigInt, and the search compares it against a BigInt element that holds the same mathematical value. Every step is a rule of this proposal applied where it says it applies.

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 Static Type — costs an element read its type, costs a function returning a literal its inferred return, and leaves the non-convergence rule of 1.6.2.3 unreachable, since the shape that grows at every step is a literal containing a recursive call.

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 false and to the not-found index. Each searches for a value the array's element type cannot hold, which is the mistake this proposal exists to report, and the answer they gave was the answer that concealed it. One diagnostic is weakened: an element whose value the callback's parameter type cannot hold, as in [300].map(f) where f takes a uint8, is now reported as a type error naming number and uint8 rather than a range error naming 300. The stricter report arrives earlier; the weaker one described the value better.

1.14.10 Defaults and Stores

An elision in an array literal occupies a position and contributes undefined to that position's contextual element judgment. It is a type error when that value is not assignable to the destination element type, for an array or tuple, including an argument or return context. A hole is supplied, so it does not activate a tuple position's default; only an omitted trailing position does.

The default value of a tuple type is the array whose elements are, for each element of [[Elements]] whose [[Rest]] is false, its [[Initial]] where that is not none and the default value of its [[Type]] otherwise; a [[Rest]] element contributes no elements. It is none where an element has an [[Initial]] of none and a [[Type]] with no default.

A store to an element of an array of element type t checks the value against t by Table 23. length is of the index type. Deleting an element of a typed array throws a TypeError exception.

A store to a position of a tuple checks the value against the [[Type]] of the element that receives that position, by the same table; a position an element whose [[Rest]] is true collects takes that element's collected type. The type checked against is the one the VALUE carries rather than the one the reference through which it is reached would give it, which is what makes the covariance of 1.2.3.1 sound: 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 TypeError exception rather than growing the Array, and so does an assignment to 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.

1.14.10.1 Initialization on Length Growth

When an explicit definition or assignment to an Array's "length" increases its length and the Array carries typed array or tuple backing, apply the following rule after the typed count conversion, fixed-extent check and surviving-position contract check. An unchanged or decreased length retains the existing rules.

  1. Let oldLength be the Array's current length.
  2. Let newLength be the requested length.
  3. Let descriptor be the requested length Property Descriptor with its value replaced by the converted length.
  4. If the Array is not extensible, or IsCompatiblePropertyDescriptor returns false for descriptor and the current length descriptor, return false from the property definition, without computing defaults.
  5. Record the Array's current backing contracts and fixed extent, if any.
  6. Let values be a new empty List.
  7. For each integer index from oldLength (inclusive) to newLength (exclusive), in ascending order, do
    1. Let type be the actual backing type of position index at newLength, including every applicable conjoined contract.
    2. Let value be DefaultValueOf(type).
    3. If value is none, throw a TypeError exception.
    4. Append the value to be stored, with value-type copying as required by 1.11.4, to values.
    5. If the Array's current length differs from oldLength, or its backing contracts or fixed extent differ from those recorded, throw a TypeError exception.
  8. Recheck extensibility and compatibility with the current length descriptor as above. If either check fails, return false.
  9. Without executing user code between these checks and publication, create an own data property for each new position, with its corresponding prepared value and [[Writable]], [[Enumerable]] and [[Configurable]] all true, and apply descriptor to "length".
  10. Retain the current capacity if it suffices for newLength. Otherwise grow the allocation to sufficient capacity and invalidate references according to 2.10.
  11. Return true.

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 undefined. Their creation invokes no inherited setter and does not repeat a conversion already performed by DefaultValueOf. A type without a default is not replaced by an untyped zero or undefined. This rule initializes an explicit length increase; it does not require defaults for insertions whose values are supplied by a caller or moved by an intrinsic.

1.14.10.2 Initialization on Indexed Growth and Insertion

For a typed Array's own indexed data definition that increases length, first apply the existing destination boundary conversion, fixed-extent and surviving-position rules. Prepare every new position in increasing index order, retaining the converted supplied destination value and computing DefaultValueOf of the actual backing type for each unsupplied position. Apply the preflight, conflict checks and publication rules of 1.14.10.1 to the complete new tail. The supplied destination retains the requested descriptor attributes; implicitly initialized gap positions are writable, enumerable and configurable own data properties. No element, requested length, capacity or relocation from this growth is published if preparation fails.

Creating a new own typed element with a data or generic descriptor whose [[Value]] is absent uses the position's default, or throws a TypeError exception if it has none. An explicit undefined value is supplied and undergoes the ordinary boundary check. Omitting [[Value]] when redefining an existing property retains its existing value. A new accessor descriptor does not initialize a data position and throws a TypeError exception. Ordinary nonextensibility and length-writability refusal precede missing-default computation. A missing own element inside an existing extent uses the same default rule; if default computation independently creates that property or changes the published length or backing, throw a TypeError rather than overwrite the independent result.

For a growing intrinsic unshift or splice on an owned typed Array, after ordinary receiver, length and index/count processing and the existing mutation-plan checks, record the current published length, backing contracts and fixed extent. Retain privately the operation's own data definitions at indices in the proposed new tail. Keep the ordinary order of property reads, conversions, species construction and setters; writes within the recorded extent occur immediately. Private definitions are not visible through reads, keys, descriptors, length or reference acquisition.

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 published length, backing contracts or fixed extent differ from those recorded, throw a TypeError exception. Recheck after intervening lookup or conversion effects and before applying the write's own property definition, even if those effects have created its destination. Earlier abrupt completions retain their ordinary precedence. Existing-element value writes and capacity-only changes do not by themselves invalidate this plan. Failure discards private definitions and preserves prior ordinary writes and independent effects; it does not retry or roll them back.

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 none. Use the same default order, conflict checks and publication rules as explicit length growth. Recheck length-descriptor compatibility and extensibility; refusal is a failed final Set and therefore throws a TypeError for these intrinsics. Publish the complete tail and final length without user code between revalidation and publication. Inherited setters and successful conversions are not repeated.

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 2.10; growth within capacity preserves surviving locations. These rules do not change untyped Arrays, non-Array receivers, or nongrowing intrinsic operations.

1.14.10.3 Deletion and Removal of Typed Array Positions

An owned typed Array's [[Delete]] throws a TypeError exception for an index in its backing contract's protected domain, regardless of the alias used, current occupancy, or the property's configurable attribute. The domain is every canonical array index for growable homogeneous backing, indices below a fixed extent, and indices admitted by the tuple backing's permitted length domain. Noncanonical keys, Symbols, and fixed nonpositions retain ordinary deletion behaviour. In particular, the string key "-0" is not an array index. Existing early deletion errors continue to apply where the receiver and key prove protection; otherwise property-key conversion and runtime refusal keep their ordinary timing. Refusal does not invalidate a reference.

For a shrinking intrinsic pop, shift, or splice on an owned typed Array, after ordinary argument processing and mutation-plan validation, record the current published length, backing contracts, fixed extent, and proposed final length. Keep the ordinary order of element reads, movement writes, species work, replacement conversion, and setters. At each ordinary trailing-deletion step, revalidate the recorded plan and check the existing own descriptor's configurability. A nonconfigurable descriptor throws a TypeError at that step. Otherwise defer removal and reference invalidation until final truncation. Only indices in the recorded extent that leave the proposed extent receive this treatment; deletion of a surviving position retains the direct-deletion rule.

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 published length, backing contracts or fixed extent differ from those recorded, throw a TypeError exception. These write checks belong to the specific intrinsic receiver, key and execution context; callback and nested operations execute independently. Earlier abrupt completions retain their ordinary precedence. Value-only or capacity-only changes do not by themselves invalidate the plan. On failure retain earlier writes, independent effects, and every tail location not actually removed; do not retry or roll back.

At the final length update perform the ordinary typed length truncation, bypassing only the refusal of individual indexed deletion. Recheck length writability and the current element descriptors. Nonwritable length refuses before removal. Remove trailing elements in descending index order without intervening user code. A nonconfigurable barrier retains the ordinary failed-truncation state: length is one past the barrier, lower positions remain, and successful higher removals remain removed. Apply any requested nonwritable length as required by ArraySetLength. An earlier deferred-deletion refusal has removed no tail locations; earlier movement and replacement writes are not undone. Direct length truncation uses the same removal and barrier rules. A failed throwing Set still throws a TypeError.

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 abrupt completion. Completed higher removals remain. This rule also applies to an otherwise untyped Array carrying such required properties, and to a contract acquired during intervening effects.

Actual removal, including a successful removal before a final descriptor barrier, invalidates that element's identity under 2.10. Surviving references retain their locations and observe moved or replaced values. Shortening itself does not change capacity or relocate backing; later regrowth does not revive removed references. These rules do not change ordinary untyped Array mutation or authorize deletion from callbacks, surviving typed positions, or unsupported non-Array backing.

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 TypeError exception - 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 shape the operation produced: toReversed answers the positions in reverse, slice the window it took, with the shape it was given, and toSorted the shape it was given where every position is the same type. Where the operation determines no shape - a comparator-driven sort of positions that differ, or any of these over a tuple with an unbounded rest - the result is an ordinary Array carrying no element type.

For a bounded tuple without a rest, including one with trailing defaults, or a tuple whose rests all have finite expansion, including a defaulted effective tail, these copying shapes describe the positions actually produced at the operation's captured length. A slice retains its selected positions and a reversal reverses the occupied positions; these results have exactly the produced positions as required positions. A with copy retains the complete source storage domain after its replacement check. A sorted copy retains that domain when all occupied position contracts are the same. Omitted source defaults neither add output positions nor supply construction initializer metadata to the copy. The source retains its complete admitted length domain.

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 TypeError exception before element work. For an anchored suffix, compare each moved source contract at the old length with its destination contract at the proposed length. Earlier ordinary argument effects are retained. Where the intrinsic origin, contracts and arguments statically establish an impossible plan, the existing type-error rules apply.

A planned move between tuple positions requires SameType for the source and destination backing types; value membership, subtyping or a possible conversion is not sufficient. For 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 replacement does not move the remaining positions; each inserted value still crosses its own destination boundary. For 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 abrupt completion does not roll back earlier effects or independently completed boundaries. An impossible 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 2.10. A user replacement for an intrinsic receives no intrinsic plan; invoking the intrinsic through another reference still executes its plan.

A method of the array that takes or returns an ELEMENT takes or returns it at the element type. 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 element type; 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 1.6.6, so 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 typed collection: the key and value positions 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 value type or undefined, since a lookup that finds nothing answers undefined: the Static Type of get on a Map.<K, V> is V | undefined rather than V, so a binding of type V does not admit it without a test. An operation that RETURNS a collection carries an element type determined by where its elements can come from: 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 element type is not known, the result's is not either, and the result carries none: a union with an unconstrained set is unconstrained, and answering 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 1.15; a collection with no type arguments constrains none of these positions and is 1.15.7.

These are ordinary parameters of a declared type, so Table 23 governs them and nothing further is needed: a literal that cannot take the element type is an Early Error, and a value of the 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 type error rather than false, for the reason 1.8.5 gives for a narrowing whose test can never succeed: the answer is knowable from the types, so reporting it as an ordinary false would hide a mistake rather than answer a question. An UNTYPED array declares no element type and so constrains none of these positions.

Note

This reconciles the two rules 1.8.3 leaves in tension. A tuple defaults element-wise, but an element that carries a default already has a value for its position, so the initializer is used where there is one and the type's default only where there is not. [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 1.6.6 records: the design specifies 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 design's example: a literal element store follows the literal rule and is rejected, because the compatibility argument does not reach new syntax, [].<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 typed value stores by Table 22 as before, so the runtime wrap survives where it belongs.

1.14.10.4 Relationship to %TypedArray%

%TypedArray% and its constructors are UNTOUCHED by this proposal. A 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 value type. %TypedArray% is the system for byte-oriented work against existing APIs, and the array types are the system for typed storage; a program picks one per data structure rather than converting between them.

Where they meet is a BUFFER, which both already understand. A %TypedArray% exposes its buffer, Span.<T>(buffer) (11) reinterprets those bytes at the element type T, and the result is a window — so writing through it writes the same bytes the %TypedArray% reads.

An implementation SHOULD report the deprecation once per agent, on the first use of a %TypedArray% constructor or of %TypedArray.prototype%. Once per agent rather than per use: a diagnostic that fires inside a loop over binary data is noise, and the purpose is to tell an author that the family they are using has a successor, which needs saying one time. The report names the replacement rather than only the deprecation.

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.

Note

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 index type is a value of a numeric value type, and an untyped operand meeting one throws a TypeError at the operation, the checker deferring what it cannot see (1.8.2 gives an unannotated 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 false rather than raise. The first two are the ordinary way binary data is processed, and the third reports nothing at all.

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.

1.14.10.5 Ordered Element Types

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 element type - an unannotated Array - is unaffected, as is any call that supplies a comparator.

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.

Table 37
TypeOrder
A numeric value typeBy numeric value. A float type places NaN last and -0 before +0, as %TypedArray%.prototype.sort does, so that an array and the corresponding TypedArray agree.
A 64-bit integral typeBy 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.
bigintBy value, compared AS a BigInt. Converting to a Number to compare would change the ORDER above 253, not merely lose precision.
stringLexicographically, which is what the String comparison already gives.
booleanfalse before true.
An enumBy the rule its underlying type already carries (1.11.8): a numeric underlying type by value, a string underlying type by DECLARATION POSITION rather than alphabetically.
A class declaring operator <By that operator.
Note

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.

1.15 Keyed Collections

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 any defers the produced-value check to insertion; its known parameter and receiver contracts are still checked. This per-call dynamic result rule does not make any a subtype of V. Runtime insertion enforcement is unchanged and no occupancy inspection is used to justify an early error.

A member contract determined by a typed collection's resolved built-in identity and type arguments applies equally through a dotted name and a statically known String property key. This includes a constant alias or parenthesized key, and preserves the member's parameter, result and count types. A same-named user class does not acquire a library contract. This judgment supplies a Static Type, not a proof of intrinsic method identity; it does not elide property lookup, key evaluation, receiver binding or reference-liveness checks. Unknown keys and any receivers retain runtime checks.

Map, Set, WeakMap and WeakSet take type arguments, and their members follow from them. 1.14.10 states what their key, value and element POSITIONS admit, alongside the array's; this clause states the count they report, the members that walk them, the type they report as their own, which values are of them, how they acquire their types, what their key positions admit, and the collection that has none of it.

1.15.1 get Map.prototype.size, get Set.prototype.size

Where the collection carries type arguments, size has the Static Type index type and the accessor returns a value of that type. Where it does not, size is a Number and the accessor is unchanged.

size is an accessor property whose set accessor function is undefined, on a typed collection as on an untyped one. An array's 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 (2.11.1) is of the index type and reports only.

WeakMap and WeakSet have no size, and reading one from a value of either type is a type error rather than undefined. A member the type does not declare is a mistake the type can report, which is the treatment 11 gives the operations a window does not have.

Note

The accessor returns a VALUE of the index type and not 𝔽 of a count. The distinction is the one 2.11.1 states for a capacity, whose algorithm returned 𝔽 at every step while index type said the count it reports is of the index type; the dfn was the side that was right, and that clause now agrees with it. A count reported as a Number cannot be compared with one reported at the index type, which is the whole reason for naming the type.

1.15.2 Iterating a Keyed Collection

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.

Table 38: Iteration members of a typed keyed collection
MemberOn Map.<K, V>On Set.<T>
keysIterator.<K>Iterator.<T>
valuesIterator.<V>Iterator.<T>
entriesIterator.<entry type>Iterator.<entry type>
@@iteratoras entriesas 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 28: the relation is a DECLARATION rather than a structural inspection, so it is decided by brand and a hand-written iterator entering the same position pays the structural check instead.

On a Set, keys and values are the same function object, so they answer the same type; and entries yields a pair of the element with itself, which is what the language does and what a destructuring for-of over a set therefore binds.

WeakMap and WeakSet have none of the members in the table above, nor size (1.15.1), and reading any of them from a value of either type is a type error rather than undefined, whether the read is a member access, a computed access whose key is statically established, or a property of an object binding or assignment pattern. The last is not a missing-key rule for patterns: it is this rule, applied to a read. A weak collection is not enumerable: what it holds may be reclaimed between one step of a walk and the next, so there is no order to report and no count to report it against. The absence is a consequence of weakness rather than an omission to be filled in.

Note

The return type is the Iterator of 28 rather than a per-collection type. There is no MapIterator or SetIterator, for the reason that clause already gives: the bare-argument shorthand carries the narrower element type that TypeScript needs those names for, so the family stays at six interfaces and two classes.

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.

1.15.3 RuntimeTypeOf of a Keyed Collection

A typed collection carries the Type Record it was built at, and 1.7.1.1 reports it: 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.

Note

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 shape and the same prototype chain, and a Map has no own properties at all - so inference read the literal type of an empty object and reported neither the specialization nor the bare nominal.

1.15.4 Key and Element Positions

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 Table 18 do not apply: a value that is not already of the declared type is a TypeError exception rather than a converted one. Every other position, including a Map's VALUE, is unaffected and converts as 1.6.1 states.

This governs the positions wherever they are reached - the methods that store, the methods that search, and the entries a constructor's seed supplies (1.15.5) - because a rule enforced at some of them is one a program can route around.

Note

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: 1.7.7.1 admits a numeric source only where the target represents it exactly and raises a RangeError otherwise, so a 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 "5". What distinguishes a Set is that its element IS its key.

1.15.4.1 Probes Performed by the Set Operations

The set operations of 1.14.10 test each element of one operand against the other. Where the other operand's element type cannot hold the element, that test is false rather than a thrown exception. An exception arising from the other operand's has for any other reason propagates unchanged.

Note

A test a user writes with a needle the element type cannot hold is an error, because it can never succeed. A probe performed BY an operation is a different question with a known answer, and reading it as false is what makes the constant-folding this specification describes fall out of iterating: "an intersection of a Set.<uint8> and a Set.<string> is empty without iterating, and isDisjointFrom is true, since distinct value types share no values."

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 path - so the fold held in the direction where the element could be converted and failed in the other, and which direction that was depended on which operand was smaller.

1.15.5 Constructing a Typed Keyed Collection

At a proved intrinsic collection construction, a non-nullish participating seed must satisfy the synchronous iteration protocol used by the constructor. It is a type error if established facts prove that this protocol cannot be obtained. Omitted, null and undefined seeds retain empty construction. A primitive category alone does not establish absence: boxed primitive prototype chains may supply the iterator, including through prior source texts. Establish the relevant lookup and effects without executing a getter. Unknown or replaceable protocol facts remain dynamic.

Each iterated Map or WeakMap entry must be an Object before its key and value are read at property keys "0" and "1". It is a type error when established traversal reaches a non-Object entry; later duplicate keys cannot undo this failure. Entries need not be arrays, have length two or themselves be iterable. Ordinary data Objects may supply either position. An established missing positional read supplies undefined, but short array syntax alone does not prove absence of an inherited property. Preserve getters, proxies and intervening effects as unknown when their reads or traversal consequences are unavailable. Only after ordinary insertion are surviving entries checked for typed adoption, using the duplicate-key rule below.

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 1.7.7.1, and the converted value replaces the one held. Where any of them cannot be converted, the collection does not acquire the type and a TypeError exception is thrown.

This is what checks a CONSTRUCTOR's seed. new Set.<uint8>(["a"]) is a type error, and new Set.<uint8>([1]) holds a uint8 rather than the Number the literal arrived as, so an element that entered through the constructor and one that entered through add are of one type afterwards.

Where intrinsic construction, seed iteration and insertion behaviour are statically established, finite fresh literal seed facts may determine the contents at acquisition. It is a type error if an established surviving key, value or element cannot undergo the conversion required above. Apply the same judgment to explicit construction and immediate contextual adoption. For Map, preserve key/value positions and resolve proved duplicate primitive keys by the collection's equality rule before checking the surviving values: an overwritten value has no adoption obligation. A later unknown key that could overwrite a value prevents proof that that value survives. Seed expressions still evaluate in their original order; this judgment does not preconvert or omit transient entries.

This bounded proof requires the intrinsic-origin conditions of 1.12.7 and established iterator/adder identity. General iterables, unknown spreads, getters, replaced operations, arbitrary mutation histories and unresolved effects retain run-time checking. Reuse the existing numeric-literal and declared-conversion rules; do not infer a permanent element type for an untyped collection, infer a closed shape from structural membership, or change weak-key eligibility. Empty and nullish seeds remain admitted.

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 1.15.7.

Note

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 constructor has already consumed its seed. Checking what is found reaches further in any case, since it also governs the ANNOTATION spelling 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.

1.15.6 Membership of a Keyed Collection Type

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 (1.2.3.1 Note 5). The 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> (1.5.20), and it is sound for the same reason — a store through the wider view is checked against the instance's own field type at run time (1.7.10), whatever the static type permitted. A collection carrying no type arguments is of the bare type Map and of no instantiation of it.

This is what is, a when pattern naming an instantiation, a typed catch clause, and narrowing all read, so the four agree by construction.

Note

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 1.2.3.1 exists to prevent. Inspection is also unstable, since a single store invalidates the answer, and it costs a walk of the collection at each test.

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.

1.15.7 Collections Without Type Arguments

A Map or Set written with no type arguments is unaffected by this clause and by every rule of 1.14.10 that concerns a collection. Its size is a Number, its keys and values are unconstrained, iterating it binds at the any type, and no program that does not use these types can observe any of the above.

This is the collection reading of the rule an array already has, that "an array with no element type is untouched by any of this" (1.4.2.7). It is stated because a collection has no other place it would be stated, and because the two forms of one container are otherwise easy to conflate: a specialization adds nothing to a shared prototype and changes nothing about a constructor, so the typed and untyped forms coexist in one program without interacting.

Note

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 any type and behaves as it does today.

1.15.8 GetSetRecord ( obj )

The abstract operation GetSetRecord takes argument obj (an ECMAScript language value) and returns either a normal completion containing a Set Record, or a throw completion.

This operation reads the size of a set-like and converts it with ToNumber. Where the value read is of the index type, that conversion is not performed. It is revised to:

  1. If obj is not an Object, throw a TypeError exception.
  2. Let rawSize be ? Get(obj, "size").
  3. If rawSize is a value of the index type, then
    1. Let sized be the mathematical value of rawSize.
    2. If sized is not a non-negative integer, throw a TypeError exception.
    3. Let intSize be sized.
  4. Else,
    1. Let numSize be ? ToNumber(rawSize).
    2. NOTE: If rawSize is undefined, then numSize will be NaN.
    3. If numSize is NaN, throw a TypeError exception.
    4. Set intSize to ! ToIntegerOrInfinity(numSize).
    5. If intSize < 0, throw a RangeError exception.
  5. Let has be ? Get(obj, "has").
  6. If IsCallable(has) is false, throw a TypeError exception.
  7. Let keys be ? Get(obj, "keys").
  8. If IsCallable(keys) is false, throw a TypeError exception.
  9. Return a new Set Record { [[SetObject]]: obj, [[Size]]: intSize, [[Has]]: has, [[Keys]]: keys }.
Note

A count is CHECKED rather than coerced, which is the discipline 2.11.2 states for the operations that ACCEPT a count: an operation that accepted a String as a count would disagree with the operations that report one. Without this revision a set operation between two typed sets would route a value of the index type through an implicit numeric conversion, which is the one thing the conversion rule (1.6.1) does not do.

The ELSE branch matters as much, and is the existing steps unchanged. The argument to a set operation may be any set-like, whose size is an ordinary Number and whose failure modes have observable error kinds — a missing size is a TypeError by way of NaN, and a negative one a RangeError. Routing every set-like through ToIndexType would collapse both into a TypeError and change the behaviour of programs using no types at all, which 1.15.7 forbids.

1.16 Extensions

The design is organized as a core and a set of extensions, each of which adds types, operations, or syntax to the core rather than revising it. This clause states what an extension may do and records the hooks the preceding clauses have already named.

1.16.1 The Extension Mechanism

An extension may add types to 1.5, provided each is described by a Type Record of one of the kinds of Table 1. It may define the operations of 1.5.23 for a type it adds, and may declare operators for a type that does not define them (1.9.9). It may add syntax, subject to the constraint the whole proposal is under: the syntax must be an error in the current language. It may add library functions, overloaded by 1.10.3.

An extension may not revise the type-checking framework of 1.2. In particular it may not add a kind to Table 1, may not change SameType or IsSubtype for a kind it did not introduce, and may not introduce an implicit conversion beyond those of Table 13.

Note

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 units extension wants 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.

1.16.2 The Hooks

Table 39 lists the extensions the preceding clauses name and what each is relied on for. It is a map of the obligations this specification has incurred outward, not a specification of the extensions themselves.

Table 39: Extensions and the Hooks They Fill
ExtensionRelied on for
primitive metadataThe meta types the design ships over the protocol of 6, which is where they are declared rather than specified: the Dimensions of units of measure with its unit aliases and dimensional algebra, the 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 layoutThe 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 1.11.2 states are delivered by 9.
SIMDThe 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 19.
simdGather, scatter, and masked load and store, which vectorize an indirect access and need an addressing model this specification does not have. The design defers them too (simd.md), and every other operation of that document is stated in the clauses 1.5.7 through 1.5.14.
genericsThe design's full generics surface built on the model of 4: specialized overloads and their resolution; constraint families as specialization selectors (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 design writes and this specification neither states nor defers elsewhere. The parameter kind, substitution and specialization, the where clause, and declaration-site variance are delivered by that clause.
dependent record typesThe design's JSON Schema correspondence, by which a 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 5.
decoratorsDelivered by 7, which now covers the shape of every reflection (7.11), what each context admits as a replacement value (7.12), addInitializer (7.13), the retrieval forms (7.14), the metadata channel, and the resolution and order of a decoration. What remains at this hook is the decorator SYNTAX itself, which rides the decorators proposal rather than this one.
complex numbersThe operators and Math overloads of the complex types. The type of an imaginary literal and the conversions are delivered by 20.
operator overloadingThe math library the design works the feature through, and the four conditions it states an engine must meet to compile that library well. The declaration forms, the receiver rule, and the resolution are delivered by 18.
threadingSpecified by 43. What remains at this hook is what that clause leaves to a host: the sizing and management of the pool (43.8.5), the disposition of host-scheduled work still pending when a thread's function has settled (43.1), the AbortSignal the cancellation rules refer to, and the concurrent Map and Set the design would like but does not require. Two things the clause records as possible future work rather than as gaps: an explicit opt-in to settle-side execution of a promise reaction, and an option pinning the parallel partition so that a result reproduces across implementations and not only within one.
rangesThe 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 1.7.9 relies on are delivered by 21.
type programmingThe 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 1.12.5, 1.12.6, 1.5.18, and 1.2.7, the kit and the evaluable fragment by B and A, symbol literal types by 1.5.19, the declarative checker facts by 8, contracts, trials, and declared inverses by 16 and 17, and structural matching, provenance, and distribution by 22, 23, and 24.
Note

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 typed program meets a network, and it is built from the layout, the metadata hooks, and the 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 Type Object's identity means (42), which is why it alone among the outer extensions has a clause here.

The dependency now runs in one direction without exception. The metadata parameterization that 1.2.3.1 once forward-referenced is core (1.5.20), as the refinement relation it shares with an enum's underlying type and a class's base always suggested it should be. What remains with the primitive metadata extension is the protocol's hook surface: the judgments beyond the branding default, and the conversion machinery.

1.16.3 Coverage of the Design Documents

Every document of the design has a disposition here: it is specified by clauses of this document, it is deferred to its own future clauses with a hook already named, or it is deferred outright, in which case whatever it adds must fit the mechanism above. The examples directory is a corpus this specification is checked against rather than a source of requirements.

Table 40: Disposition of the design documents
DocumentDisposition
READMEThe core; specified throughout this document.
typeobjects.mdSpecified: the Type Object and the computed types of 1.4.2.4.
typeprogramming.mdSpecified: builders, canonicalization, the evaluation budget, structural matching (22), provenance (23), the expansion artifact (24), and the two operators of its §4.1 — keyof (1.4.2.5) and indexed access (1.4.2.6). Its third, typeof x, is not an operator of this specification: the type query is Reflect.typeOf (1.12.1), which that section says needs no builder.
higherkindedtypes.mdSpecified: 4.13, which states the [[Arity]] on a parameter Type Record, what may be bound to a higher-kinded parameter, and how the four clauses of 4 read when a parameter is one. The refusals of the quantified constraint and of inference are normative, and both rest on 1.2.7.4.
generics.mdPartially specified: the parameters that are types and values, specialization, the where constraints, and variance are 4. Specialized overloads and their resolution, constraint families as specialization selectors, variadic packs, argument-bound value generics and inference from an expected type, the constraint-family forms, and generic parameters on a decorator are deferred, and Table 39 records them as this clause's hook.
dependentrecordtypes.mdSpecified: 5.
primitivemetadata.mdSpecified: 6.
memorylayout.mdSpecified: 9, with placement new in 1.4.6.
operatoroverloading.mdSpecified: 1.9 and 18.
operators.mdSpecified: the result of an operator by family (1.9.4), of which that document is the informative presentation, with the instructions a target lowers each family to.
decorators.mdSpecified: 7 - the reflection contexts and the shape of each one's reflection, the metadata channel, replacement values, addInitializer, the retrieval forms, and the resolution and order of a decoration. Decorator syntax itself rides the decorators proposal.
complex.mdSpecified: 1.5.6, 20, and the imaginary literal.
rational.mdSpecified: 1.5.5.
decimal.mdSpecified: 1.5.4.
composites.mdSpecified: 25, layered on the upstream Composites proposal with the deviations of 25.8.
patternmatching.mdSpecified: 26, with the discriminated where chains it consumes in 5.3.
The iteration typesSpecified: 28, owed to the protocols the core defines rather than to any extension, and the clause the generator types are now stated in terms of.
pipelineoperator.mdSpecified: 41, with the token recorded as the proposal's current spelling and every rule stated in terms of the topic rather than the token.
doexpressions.mdSpecified: 39, with the generator types and the divergence analysis it consumes in 33 and 27, both owed to the core rather than to it.
simd.mdSpecified: the vector type's formation is 1.5.7; its lanes, the broadcast cast, the horizontal sum, and the conversion between a bit vector and an integer are 1.5.8; permutation across one and two sources is 1.5.9; the component accessors with their desugaring are 1.5.10; the lane-wise comparisons with their result forms are 19; the operations that consume a mask are 1.5.12; the target's preferred width is 1.5.13; and what an alias of a vector type has that a class wrapping one does not is 1.5.14. Gather, scatter, and masked load and store are deferred by the design itself, which 1.5.13 records.
standardlibrary.mdPartially specified: the numeric library and the overloaded library rules are 1.13; the keyed collections' count, the untyped carve-out, and the GetSetRecord revision are 1.15, with their key, value and element positions and the set-algebra result types in 1.14.10. The iteration members, the entry type, and the Iterable relation are 1.15.2; the constructors and the rule by which a collection acquires its type are 1.15.5; which values are of a collection type is 1.15.6, what one reports as its own type is 1.15.3, and what a key or element position admits is 1.15.4. Set.<any> and Map.<any, any> as the family tops are 1.2.3.1 Note 5. Map.groupBy and Object.groupBy are 1.6.2.24. Library application binding is specified by 1.4.2.3. The bounded generic-capability, Promise aggregate-input and iterable-callback early-error judgments are specified by 1.2.8.3. Remaining runtime details of the iterator helpers, the Promise statics, and Array.fromAsync are deferred.
errorhandling.mdSpecified: 1.7.12; the errors the proposal raises are stated at their checks.
references.mdSpecified: the ref type is in the grammar and the borrowing rules are 2; a returned reference is consumed as a location by the forms of 2.8, and a borrow of an SoA element is 2.9; a destructuring ref member is 2.2; the deferred form (a user iterator or callback yielding references) is noted there.
threading.mdSpecified by 43: threads and their lifetime, the scheduling of promise reactions across them, callThread and cancellation, the shared modifier's placement and stability rules, shared typed memory as events of the memory model, Atomics over typed values, Lock, Condition, and ThreadLocal, and parallel iteration. The host latitude that remains is listed at its hook (Table 39).
ranges.mdPartially specified: the range literals, the Range value, and its iteration are 21.6 atop the hook of 21; the interval in the type and its specialization, the uint8.<1..=6> bounds desugaring, slicing to a view, and the random and Temporal integrations are deferred.
serialization.mdPartially specified: typed JSON parsing (the JSON.parse.<_T_> overload and its converting, validating walk) is 45, atop the boundary and metadata machinery the core already gives; the deferred surface (class targets, the exact wide numerics, typed JSON.stringify, streaming, typed JSON modules, structuredClone, and the binary layout) is noted there. 42 is the adjacent normative text.
soa.mdThe type and its layout: 15; array views, which its own view form follows, are 14. A reference into an SoA is 2.9, with its liveness rules in 2.10; the element and view semantics are still to come.
regexp.mdPartially specified: the capture-type inference of a literal is 3; the match-result surface it enables (the typed exec result and group-name checking, the typed String methods, the flag-dependent shapes, composition, and string narrowing) is deferred and noted there.
random.mdDeferred; a library extension.
temporal.mdDeferred; a library extension.
specnotes.mdThe plan this document follows; not itself a source of requirements.

2 References and Borrowing

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 design's references extension (Table 39): the ref argument and ref return, the ref parameter, the ref lexical binding and its rebinding, and the reference for-of loop. The ref type is already in the grammar; this clause adds the syntax of the borrowing forms and the runtime that the type describes.

2.1 Syntax

ref is a contextual keyword, not a reserved word, and every borrowing form claims it by one rule: ref is claimed only when the token that follows it, with no line terminator between, can begin the operand as written — an IdentifierReference or this for a RefExpression, whose operand is an expression, and a BindingIdentifier (an identifier, or 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.

RefExpression[Yield, Await] : ref [no LineTerminator here] LeftHandSideExpression[?Yield, ?Await]

The operand is a LeftHandSideExpression because a borrow names a place, and the claim rule restricts which tokens may begin it: 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 topic %, not to make ref % mean anything but so that its refusal (41.3) carries its own diagnostic rather than surfacing as an unexplained token. A RefExpression appears in exactly two positions, an argument and a return; the rest of this clause gives each, and the binding forms, in turn.

An argument may be a borrow. Argument is restated from 1.10.4 with the one added alternative; per 1.1 this block is the production's complete form.

Argument[Yield, Await] : AssignmentExpression[+In, ?Yield, ?Await] ... AssignmentExpression[+In, ?Yield, ?Await] NamedArgument[?Yield, ?Await] RefExpression[?Yield, ?Await]

A return may be a borrow. This is the first restatement of ReturnStatement.

ReturnStatement[Yield, Await] : return ; return [no LineTerminator here] Expression[+In, ?Yield, ?Await] ; return [no LineTerminator here] RefExpression[?Yield, ?Await] ;

return ref followed by a line terminator keeps its base meaning: the restriction inside RefExpression makes the new alternative unavailable, the Expression alternative derives the identifier, and automatic semicolon insertion completes the statement, exactly as it does today. return ref x derives only through the new alternative, since two adjacent identifiers have no expression reading.

A lexical binding may be a borrow. LexicalBinding is restated from 1.4.4; the ref alternative requires its Initializer structurally, since an alias without a location to alias is nothing, and it joins neither the BindingPattern alternative, because the borrow binds one name to one location, nor the TypedInitializer alternative, because := infers a binding's type from a value and the initializer of a ref binding must denote a location.

LexicalBinding[In, Yield, Await] : BindingIdentifier[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt Initializer[?In, ?Yield, ?Await]opt BindingIdentifier[?Yield, ?Await] TypedInitializer[?In, ?Yield, ?Await] BindingPattern[?Yield, ?Await] Initializer[?In, ?Yield, ?Await] ref [no LineTerminator here] BindingIdentifier[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt Initializer[?In, ?Yield, ?Await]

It is a Syntax Error if the ref alternative of LexicalBinding is contained in the LexicalDeclaration of a ForStatement head.

Note

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 design document yet states.

A mutable ref binding may be re-aimed by a statement of its own. This is the first restatement of Statement.

Statement[Yield, Await, Return] : BlockStatement[?Yield, ?Await, ?Return] VariableStatement[?Yield, ?Await] EmptyStatement ExpressionStatement[?Yield, ?Await] IfStatement[?Yield, ?Await, ?Return] BreakableStatement[?Yield, ?Await, ?Return] ContinueStatement[?Yield, ?Await] BreakStatement[?Yield, ?Await] [+Return] ReturnStatement[?Yield, ?Await] WithStatement[?Yield, ?Await, ?Return] LabelledStatement[?Yield, ?Await, ?Return] ThrowStatement[?Yield, ?Await] TryStatement[?Yield, ?Await, ?Return] DebuggerStatement RefRebindingStatement[?Yield, ?Await] RefRebindingStatement[Yield, Await] : ref [no LineTerminator here] BindingIdentifier[?Yield, ?Await] = LeftHandSideExpression[?Yield, ?Await] ;

The claim is the exact shape: after the same-line name, the next token must be =, 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 ExpressionStatement cannot, so no statement changes meaning.

A reference for-of head marks its binding. ForDeclaration gains the marked alternative; the claim declines when the token after ref is of, so for (const ref of a) continues to bind an identifier named ref.

ForDeclaration[Yield, Await] : LetOrConst ForBinding[?Yield, ?Await] LetOrConst ref [no LineTerminator here] ForBinding[?Yield, ?Await]

It is a Syntax Error if the ref alternative's ForBinding is a BindingPattern. It is a Syntax Error if the ref alternative of ForDeclaration occurs in a for-in or for await alternative of ForInOfStatement: the borrow aliases an element slot, which the index-based for-of of 2.7 supplies, and which neither the property keys a for-in walks nor the values an async iterator yields can.

A parameter's ref is already in the grammar (FormalParameter in 1.4.4, and the parameter lists of function types in 1.4.2.10); this clause adds its two Early Errors. It is a Syntax Error if a FormalParameter has ref and its BindingElement is a BindingPattern: the borrow binds one name to one location, and destructuring a borrow is the pattern's own (ref a) member form (2.2). It is a Syntax Error if a FormalParameter has ref and its BindingElement contains an Initializer: a default runs exactly when the caller supplied no argument, and a value the callee builds has no caller-side location to borrow, so the combination could never bind and is refused at the declaration rather than at the first defaulted call. The optional marker ? 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 TypeError exception.

An arrow function takes ref parameters through its covers. The parenthesized cover admits a ref parameter directly, refined exactly as 1.10.7 refines an annotated parameter, so (ref a: int32) => {} and the design's zip(a, b, (ref x, ref y) => { x = x + y; }) are covered; and through the async call cover a RefExpression argument refines to the same parameter, so 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.

2.2 Typed Destructuring Members

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 specialization bindings before checking it. After ordinary named, positional, spread and rest argument mapping, check established property or iteration contributions at those member boundaries, recursively through defaults and nested patterns. A written whole-pattern default is independently checked against its nested annotations. This does not turn an object pattern into a closed object type or infer mutable iterator yields from indexed storage. Missing properties, getters, prototype fallback, iteration positions and collected rest shapes need their own proofs; unknown contributions remain dynamic. Existing conversion and borrowing rules apply.

It is a type error for an object binding or assignment pattern to consume a participating source whose Static Type contains only null and undefined alternatives. Participation follows the operand-contract judgment of 1.9.7. Apply this source-eligibility judgment even to an empty pattern, and recursively to the selected contribution of each nested object pattern after an enclosing default or fallback. A property's default does not bypass RequireObjectCoercible on its container. A viable or unknown source alternative prevents the negative proof. Ordinary non-nullish primitive boxing, object-literal spread, borrowing permissions, reference decay and runtime liveness are unchanged; the checking pass reads no runtime reference. An unrelated annotation does not retime an ordinary JavaScript destructuring exception.

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.

BindingProperty[Yield, Await] : SingleNameBinding[?Yield, ?Await] PropertyName[?Yield, ?Await] : BindingElement[?Yield, ?Await] ( refopt BindingIdentifier[?Yield, ?Await] ?opt TypeAnnotation[?Yield, ?Await]opt ) Initializer[+In, ?Yield, ?Await]opt ( refopt BindingIdentifier[?Yield, ?Await] ?opt TypeAnnotation[?Yield, ?Await]opt ) : BindingElement[?Yield, ?Await]

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 (1.7). With a rename the parenthesized name is the property and the binding follows the colon, so { (a: uint8): b } reads property a and binds b. An Initializer supplies the value when the property is absent, as it does for any member.

A member marked ? uses undefined as its missing-value sentinel, both for an absent property and for a present property whose value is undefined. Apply an Initializer first when that value is undefined. If the resulting value is undefined, bind it without enforcing the optional member's annotation; otherwise enforce the annotation normally. An optional member annotated with T therefore introduces a binding whose read type is T | undefined, including a renamed binding. A checked default known not to produce undefined makes the read type T. Check present contributions and defaults at their respective boundaries; the optional marker does not admit an incompatible non-undefined value. Ordinary narrowing may remove the undefined alternative. This rule does not admit optional reference bindings.

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 Static Type is known. It is a type error if a rest annotation or an existing assignment destination is proved incompatible with that result category under the applicable boundary conversion rules. Unknown types and possible Object-compatible alternatives prevent that proof. The judgment does not manufacture a closed empty-object shape, restrict annotations to one Type Record kind, or infer enumerable own properties from structural membership alone. A collected property shape requires independently established copy facts; unknown exclusions and descriptor properties remain dynamic. Source coercibility, copying effects, reference decay and liveness are unchanged.

An object binding or assignment pattern consumes known property-read contributions from typed arrays and tuples as it does declared structural members. Established count contracts and known position keys contribute at annotated destinations and the existing typed-parameter and participating-constant inference sites. Equivalent literal or known computed property keys agree; common known union contributions are joined. Unknown properties or alternatives retain the gradual judgment, and mutable unannotated bindings gain no permanent storage annotation from this rule. Property contribution extraction is distinct from iteration and does not use an overridden iterator to type an object-pattern read. The existing default, bounds, storage and reference-liveness rules are unchanged.

A member marked ref borrows the property's location on the object being destructured, by the rule of 2.4.1, and binds its name to that location; reads and writes through the binding reach the property. It is a Syntax Error if such a member has an Initializer, for the reason a ref parameter may not have one (2.1): a default is a value the callee builds and has no location to borrow. It is a Syntax Error for such a member to be marked ?, 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 is not an Object, are each a TypeError. This is the only destructuring form whose targets exist before it runs, which is what it is for: one borrow retargeted across the turns of a loop.

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 :=.

Note

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 BindingPattern consumes its argument as a value and a Reference Value decays there (2.3).

2.3 Reference Values and the Absence of Identity

A Reference Value is a value produced by a ref expression that holds a storage location: a Reference Record (in the sense of the base specification) whose base is an Environment Record for a variable or an Object for a property or an array element. A Reference Value is a primitive value in the sense that it is not an Object; nothing is allocated when one is created.

A Reference Value has no observable identity. Every operation that reads a binding holding one dereferences to the value at the location, and a Reference Value that reaches any position consuming a value decays to the value at its location first. In consequence typeof, Reflect.typeOf, ===, and instanceof see the referenced value and never the reference, and a Reference Value cannot be stored in a binding that outlives its location, a property, an array, or a collection: the only bindings that hold one are the 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.

Note 1

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 Reference Value could otherwise be observed are these. A Reference Value decays when it becomes the base of a property access or of an optional chain, when it is the callee of a call, when it is gathered by a rest parameter, when it is the argument of a parameter that is a BindingPattern, when the arguments object of a call is created, and on entry to any built-in function. It decays at an ordinary call's return, so a call whose callee returned a borrow yields the referent's value at every position but the location-consuming ones, which 2.4 defers. The rule for each is the same rule: the position takes a value, so the value at the location is what it gets.

A ref parameter makes a parameter list not simple, as a default value or a destructuring pattern does. The arguments object of a function with 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.

Note 2

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 Reference Value handed to one decays on entry, and the list such a function forwards, or stores as bound arguments, holds values.

2.4 Reference Parameters and Arguments

A ref argument f(ref e) evaluates e to a storage location and passes a Reference Value holding it. The location is obtained by RequireBorrowableReference (2.4.1), so it is a TypeError exception if e does not denote a location: a value, a private member, a super property, and a property of a primitive have no location a borrow could alias. 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 TypeError exception if the argument to a ref parameter is not a Reference Value, since a plain value has no location to borrow, and a Reference Value passed to a parameter that is not ref decays to its referent.

At final binding of every reference rest, including a trailing rest reached through an unknown callable, check the actual run with ReferenceSequenceAdmits (1.10.4.3) before initializing its binding or entering the function body. Use the resolved collected annotation, retain positional tuple and nested-rest contracts and stated array extents, and require each supplied location's invariant type and current referent to satisfy its receiving contract without conversion. Omitted tuple defaults contribute no location. Failure is a TypeError exception; no partially admitted reference run is published. Recheck liveness after earlier parameter initializers and other intervening effects, even if distribution previously succeeded. The same sequence judgment applies statically where the run and its determining contracts are known; unknown iteration counts or unresolved inputs retain their existing runtime boundary.

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 TypeError exception is thrown; the value at the caller's location is not rewritten, because a borrow that converted its referent would silently change storage it does not own. A ref parameter's binding is not re-enforced by the parameter type pass of 1.7 for the same reason.

At that single-parameter receiving check, retain the normal referent read and its abrupt completions, resolve the receiving type in its current generic context, and obtain the available declared location contract without an additional value read. Follow reference aliases and linked import-binding indirections using internal environment and backing records. A typed property contract is keyed by its actual String or Symbol key; an Array or tuple element uses its established element or position contract. The contract belongs to the receiving location even when its data is inherited. This lookup neither executes user code nor changes the Reference Record used for actual reads, writes and permission checks.

If both that available location contract and the required referent type are other than any, require SameType before admitting the parameter. A mismatch throws a TypeError exception before initializing that parameter or entering the function body. Continue the existing current-referent membership check without conversion when the invariant agrees. The same available-contract judgment applies when a Reference Value is checked against a reference Type Record at a reference-typed parameter or return; it is not confined to scalar-rest backing. A current value which happens to satisfy a narrower or wider type does not establish that invariant location type.

An unavailable location contract and existing any boundaries retain their dynamic rules, including the existing metadata-specific checks; do not infer a new general invariant contract merely from the current value's runtime type. Keep the receiving read's existing position, ordinary getter exceptions, immutable import permissions and reference-liveness failures. Earlier parameter initializers retain their effects, and later initializers do not run after an abrupt ref-marked binding. A formal whose annotation is a reference type retains its existing parameter-annotation enforcement phase after parameter initialization, including intervening default effects before refusal. An earlier distribution check or an unmapped arguments snapshot does not replace the receiving check's required read or prove later liveness. This judgment does not convert or narrow backing, create a temporary location, redirect a borrow to an import's exporter, or publish a failed ref alias.

Where the borrowed location's type is statically known, ref e contributes a reference Type Record whose [[Target]] is that location type. 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 type error if such an argument does not supply a reference, or if its known referent type differs from the parameter's type. A mapping that depends on an optional parameter, a non-final rest, or a possible object spread binding names is deferred where those facts are unknown; indexed array storage alone does not establish an iterator's protocol or count (1.14.8).

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 decays to its referent, so a caller that consumes the call as a value observes the referent. A caller may instead consume it as a location, which is what makes first(a)++ write through to the element; 2.8 gives those positions.

2.4.1 RequireBorrowableReference ( V )

The abstract operation RequireBorrowableReference takes argument V (an ECMAScript language value or a Reference Record) and returns either a normal completion containing a Reference Record or a throw completion. It returns V if V denotes a storage location a borrow may alias, and throws a TypeError exception otherwise. Every borrowing form — the 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:

  1. If V is not a Reference Record, throw a TypeError exception.
  2. If IsUnresolvableReference(V) is true, throw a TypeError exception.
  3. If IsPrivateReference(V) is true, throw a TypeError exception.
  4. If IsSuperReference(V) is true, throw a TypeError exception.
  5. If IsPropertyReference(V) is true and V.[[Base]] is not an Object, throw a TypeError exception.
  6. If V denotes a bit-field member of a laid-out aggregate (9.3), throw a TypeError exception.
  7. Return V.

It is a type error when the syntax or Static Type of an operand to RequireBorrowableReference proves that it cannot denote a borrowable location: a non-location value, a private member, a super property, a property with a known primitive base, or a known bit-field member. This judgment applies to reference bindings, rebinding, reference arguments, and reference returns. A call with a known return type must return a reference; an unknown call or property base retains the runtime checks. A property is not rejected merely because its existence or descriptor is unknown.

For a property selected from a union receiver or a statically established finite set of keys, the bit-field judgment rejects the borrow when every selected storage alternative is proved to be a bit-field without a byte address. A mixture containing an eligible or unknown alternative retains the runtime borrow check. Bit-field provenance comes from the selected class storage declaration, with inheritance and generic arguments resolved; the same small integer type on an ordinary structural property does not establish packed storage. This proof does not change reference liveness or create a temporary location.

What counts as a storage location is decided at the Reference Record and not at the property behind it. A reference to an object property is borrowable whether the property is a data property, an accessor property, absent, or answered by a Proxy: reads through the borrow perform an ordinary Get and writes an ordinary Set, so a borrow of an accessor calls the getter and the setter, a borrow of an absent property reads undefined and creates the property when written, and a borrow of a Proxy-trapped property triggers the traps in the order those operations already specify. This is the only line that can be held, because the shape of a property is not stable for the life of a borrow — a data property can be redefined as an accessor, or deleted, between the borrow and the write — so a rule that admitted only data properties would have to re-check at every use and would still be wrong at the moment of redefinition.

Note

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 bit-field is a run of bits within a byte, which is addressable only by reading and writing the containing scalar and so cannot be aliased as a location of its own. An unresolvable reference is a name that is not bound, and borrowing one would create the binding a plain read refuses.

2.5 Reference Bindings and Rebinding

For a RefRebindingStatement, it is a type error if lexical name resolution establishes that the destination is an ordinary binding or an immutable reference binding. 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 Static Type or its narrowing. A closure observes that same lexical declaration, and an ordinary var redeclaration does not change an existing reference parameter's binding kind. Where name resolution remains unknown or may be intercepted by an object Environment Record, the existing runtime rebinding check decides. This check neither converts an ordinary binding into a reference nor changes the source-location, assignability, storage-boundary or liveness requirements.

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 RequireBorrowableReference (2.4.1), the same rule a ref argument applies, and bind b to it; it is a TypeError exception if e does not denote a location, and it is a Syntax Error for 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 TypeError exception, while a write to a member of the referent is permitted as for any 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 Module Environment Record as well as other lexical Environment Records. Module binding reads first preserve ordinary import indirection and the module binding's initialization check; a read of an initialized direct reference binding then reads its location using the ordinary reference-binding operation. Named imports, re-exports and module namespace reads therefore observe the current referent, including permitted rebinding in the declaring module, with the referent access's effects, abrupt completion and reference-liveness requirements. A reference binding and the binding it aliases retain their independent initialization checks. Export visibility neither copies the referent nor grants an importer permission to assign to or rebind an import binding or to assign a module namespace property.

Note 1

An export of a binding, including export { b as default }, remains live. An expression export such as 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 captured contribution uses the dependency and declaration-environment rules of 1.6.2.13. Availability of the contract does not establish runtime initialization or a current storage origin.

The Static Type of a ref binding is its annotation where it has one, and otherwise the Static Type of the location its initializer denotes: a read of b reads through to that location, so it has the location's type, and a write through b meets that location's boundary. It is not widened, a borrow aliasing the location rather than copying out of it, so a store the location refuses is refused through the alias; where the location's type is not known the binding has none and the checks are the run time's. This is what a 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 denotes, and the location it previously aliased is not written. A binding's type is fixed at its declaration, so the rebinding must be to a location of that type: it is a type error if the Static Type of the location e denotes is not assignable to the Static Type of b, and where either is unknown the judgment is the run time's. Re-typing the binding at each rebinding is not the rule, since that would make a binding's type depend on the control flow that reached it, which no other binding of this specification does. It is a TypeError exception to apply the rebinding form to a name that is not a rebindable ref binding.

Note 2

Assignability on rebinding permits a wider alias to refer to a narrower location. In 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 TypeError exception. The alias's type alone is not proof that the actual location accepts a store, so it does not justify eliding that boundary. This does not change the invariance of a reference Type Record or the separate rules for ref parameters, and every access remains subject to 2.10.

2.6 Known Write Permission through a Reference Alias

A reference binding's value type and the write permission of the location it currently denotes are separate facts. It is a type error to write through an alias where its known origin proves that the selected member is readonly and the actual write lacks that member's initializer or declaring-constructor permission. This includes simple, compound and update assignments and destructuring and iteration targets that store back. Reading the alias is permitted, and readonly remains shallow.

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 value type. A control-flow join retains known readonly alternatives, and loop back edges participate in the judgment. A captured binding that can be redirected must not be judged using a stale definition-time origin. Where its origin cannot be established, runtime checks remain necessary.

Constructor permission is evaluated at the write, not captured at the borrow. A callback invoked by a constructor does not gain its permission. Borrowability is unchanged: a readonly member still denotes a location, and a reference argument or return is not rejected merely because an unknown recipient might write. This rule introduces no public readonly-reference type, callee effect inference, new allocation pin, or exception to reference liveness. It does not permit eliding the actual location's storage or liveness checks.

2.7 Reference Iteration

Reference iteration requires array storage or the SoA storage specified by 2.9. It is a type error if the Static Type of the source proves that it has neither: in particular, a String, Set, Map, or generator is not eligible merely because it is iterable. A union is rejected by this rule only if every possible member is ineligible. An unknown source retains the runtime eligibility check. This judgment is distinct from the ordinary iterable check and introduces no user-defined reference-iterator protocol.

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 iterator protocol does not affect it and no result object is allocated. A 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 TypeError exception if e has neither array nor SoA storage.

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 live, push, pop, shift, unshift, splice, or a store to length, is a TypeError exception, which a ref loop makes easy to reach since it holds a reference across the whole iteration. The rule is the loop half of 2.10, which gives the rule for a reference held outside a loop. A user-defined iterator that yields references is not supported and belongs to the design's extension (Table 39); a reference into an SoA column set is 2.9. The callback idiom needs no support of its own: a container that passes ref a[i] to a callback with a ref parameter composes from the argument and parameter forms above, which is how the design's zip writes through both arrays.

Note

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 2.10 handles, and a reference escaping its location, which the absence of identity (2.3) already forecloses. Both are local checks and neither needs a lifetime system. This specification checks the length rule at run time; the design also permits a static rejection wherever the array's type makes the offending operation visible, which an implementation may add as a diagnostic without changing the meaning of any program this clause accepts.

2.8 Location-Consuming Contexts

A location-consuming context is a position that consumes the storage location an expression denotes rather than the value at it. A call that returns a reference does not decay in such a position, so the reference reaches the operation and the operation reads and writes through it. There are three, and each is a position in which the base language already refuses a call outright, so no existing program changes meaning.

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 CallExpression whose Static Type is a reference type, and for such an operand the update reads the referent, computes the new value as it otherwise would, and stores it back through the same location. The design's element accessor is written this way:

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 RefRebindingStatement. Each re-borrows the location a call returned, as in 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 type error for the operand of ++ or --, a borrow source as described above, or an assignment target to be a call whose Static Type is not a reference type. Where the callee's return type is not known statically the check is deferred to run time, per 1.2.8: it is a TypeError exception if the call's result in such a position is not a Reference Value.

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 typed union of callable alternatives supplies only reference returns, distinguish the possible locations from their value read type. A read joins the referent types. A store must satisfy the destination contract of every reachable known location after narrowing, with the ordinary conversion and fresh-literal judgment applied separately to each. A single reference whose target type is itself a union retains that union as one destination; it is not split into alternative locations. Local reference aliases and known rebinding/flow joins preserve these write obligations separately from the alias's declared read type. Captured rebinding and unknown origins follow the existing provenance-invalidation rules; no fact changes the runtime liveness of a reference. This applies at every store context listed above, including an update's write-back. An unknown callee or explicit any boundary retains runtime enforcement. Known non-reference alternatives remain invalid in a location-consuming context, and the decay rule for heterogeneous inferred reference returns in 1.6.2 is unchanged.

The static semantic AssignmentTargetType of a CallExpression is ~invalid~ in the base language, which is what refuses a call as a target. It is instead ~simple~ when the CallExpression's Static Type is a reference type, and remains ~invalid~ where that Static Type is known and is not one. Where the Static Type is not known — the callee is reached through an unannotated binding, or its signature is otherwise unavailable — AssignmentTargetType is ~simple~ and the check is the deferred run-time one of the paragraph above, which is imprecise rather than wrong: a call that does not yield a Reference Value is a TypeError at the assignment rather than an Early Error at the source text. Every Early Error that governs an assignment target is phrased in terms of AssignmentTargetType, so each of the positions above inherits the rule without being named in it, and a call whose callee is KNOWN not to return a reference stays refused exactly where it is today.

Note 1

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 typed: a callee whose return type is unavailable defers to run time, and the same call is refused before the program runs once that type is known. This is the ordinary gradual-typing direction — more type information turns a run-time failure into an earlier one, never a working program into a broken one — and it is why the two rules must be stated as one rule with a known and an unknown case rather than as a rule and an exception.

Note 2

A returned reference still decays everywhere this clause does not name, so first(a).x = v writes through the referent's property rather than the element's location, and let v = first(a) copies. What remains unspecified is a reference yielded by a user-defined iterator or callback (Table 39).

2.9 References into a Structure of Arrays

A borrow of an element of an SoA (15) denotes a column set and an index rather than a property slot. The element's fields are stored one column each, so the element has no single address a reference could hold, and the location is the storage together with the position within it. Reads and writes through such a reference go to the columns at that index.

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 RequireBorrowableReference (2.4.1). A 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 reference type receives a borrow without its caller stating which layout produced it.

Reading through the reference yields the element, whose field accesses reach the columns; writing a whole value through it stores each of that value's fields into its column at the index. Decay is different from reading, and the difference is observable here: a Reference Value that reaches a position consuming a value decays (2.3) to the gathered element, the same detached copy the expression s[i] produces, so no aliasing survives a boundary that takes a value.

Note

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 field accesses must reach is a live position within the columns, while the value a boundary consumes must be a copy detached from them. The rule is stated in terms of what the position does — a position that goes on to read or write through the result dereferences, and a position that consumes a value decays — and it is only the second that copies.

The liveness rules of 2.10 apply to such a reference however it was taken. The SoA is the container in this specification that relocates, so a borrow into one is invalidated when growth moves the columns, and a use through an invalidated borrow is a TypeError exception; a borrow of an element that has since been removed is invalidated in the same way; and within a reference for-of over an SoA, an operation that changes its length is a TypeError exception at that operation.

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 Reference Value applies the decay rule at its call boundary unless it is in a location-consuming context (2.8). The gathered value, including nested value fields, is detached from the columns. An access through a reference binding or an explicit Reference Value continues to reach the original location, and a nested view retains that location's liveness requirements.

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 replacement value preserves the element's identity, and removal of a different element preserves a surviving reference unless allocation relocation independently invalidates it. These requirements do not require discovering or counting live references.

2.10 Reference Liveness

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 (2.7). Within a reference for-of, an operation that changes the length of the iterated container throws a TypeError exception at that operation. It applies to every container a reference loop can iterate, and it is structural: the loop holds a reference for its whole duration, so the length is pinned for its whole duration, and no accounting of individual references is needed to know that one is live.

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 invalidated reference throws a TypeError exception. A reference to an element of a container is likewise invalidated if that element is removed, since a reference names an element and not merely an address. Relocation is what an implementation does when a growable container outgrows its allocation, and the operations that can cause it include the ones that add elements and the capacity operations reserve and withCapacity.

Note 1

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 live. That is the same reason the loop rule is structural rather than counted — the alternative would make growth depend on discovering every reference into the container.

Note 2

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 ordinary object, including an element of an ordinary Array, aliases that property; properties do not move, so such a reference remains valid, and it continues to denote the same property even if the values around it are rearranged. a.shift() while a reference to a[0] is live is not an error and leaves that reference denoting a[0], whose value the shift has changed — the same thing the expression a[0] has always meant.

Note 3

The alternative design, counting live borrows and refusing a resize while the count is above zero, was not chosen. A reference reachable only from a suspended generator frame that is never resumed would leave a count that no execution decrements, so a container could become permanently unresizable through no fault of any running code. Both rules here are computed from state the container already keeps — its length, and whether its allocation has moved — and neither requires that a reference be discoverable from the container.

The SoA of 15 is the storage for which these rules are presently normative in full, since it is the container in this specification that relocates: a reference into an SoA is a column set and an index (9), growth beyond the current capacity moves every column, and a reference taken before such a growth is invalidated by it. A growable [ ] . < 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 TypeError exception. Reserving the needed capacity before taking a reference is how a program keeps its references valid across growth.

2.11 Capacity Operations

A growable [ ] . < T > has a backing allocation whose capacity is distinct from its length (2.10). Four operations concern it, and each is defined here on an array with an element type; on an array without one they are absent.

2.11.1 get Array.prototype.capacity

Array.prototype.capacity is an accessor property whose set accessor function is undefined. Its get accessor function performs the following steps:

  1. Let O be ? ToObject(this value).
  2. If O does not have a [[TypedElement]] internal slot, throw a TypeError exception.
  3. If O has a [[TypedExtent]] internal slot, return 𝔽(O.[[TypedExtent]])return the value of the index type whose mathematical value is O.[[TypedExtent]].
  4. Let len be ? LengthOfArrayLike(O).
  5. If O has a [[TypedCapacity]] internal slot, return 𝔽(max(O.[[TypedCapacity]], len))return the value of the index type whose mathematical value is max(O.[[TypedCapacity]], len).
  6. Return 𝔽(len)Return the value of the index type whose mathematical value is len.
Note

An accessor and not a method: as a method, a.capacity answered the function object rather than the count, so 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 index type and not 𝔽 of a count. They read 𝔽 while the dfn said the counts a container reports are of that type, and the dfn is the side that is right: it names 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 𝔽 makes exactly that comparison a conversion, so the clause contradicted the reason the type exists. 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 1.15.7 requires that nothing about it change. capacity has no such form: it is reached only through a [[TypedElement]] slot, which this accessor requires before it reports anything, so every array that has a capacity at all is a typed one and there is no untyped reading to preserve.

2.11.2 ToIndexType ( value )

The abstract operation ToIndexType takes argument value (an ECMAScript language value) and returns either a normal completion containing a mathematical value, or a throw completion. It checks that value is a count and answers its mathematical value. It performs the following steps when called:

  1. If value is not a value of the index type, throw a TypeError exception.
  2. Return the mathematical value of value.
Note

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 index type, so a count that could be written as a String and silently converted would make the operations that accept a count disagree with the ones that report one — 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.

2.11.3 Array.prototype.reserve ( n )

This method grows the backing allocation to hold at least n elements without changing the length. It performs the following steps:

  1. Let O be ? ToObject(this value).
  2. If O does not have a [[TypedElement]] internal slot, throw a TypeError exception.
  3. Let wanted be ? ToIndexType(n).
  4. If O has a [[TypedExtent]] internal slot, then
    1. If wanted > O.[[TypedExtent]], throw a TypeError exception.
    2. Return undefined.
  5. Let len be ? LengthOfArrayLike(O).
  6. Let capacity be max(O.[[TypedCapacity]], len), where a [[TypedCapacity]] that is absent is 0.
  7. If wanted ≤ capacity, return undefined.
  8. If wanted > 2*64 - 1, throw a *RangeError exception.
  9. Set O.[[TypedCapacity]] to wanted.
  10. Set O.[[TypedGeneration]] to O.[[TypedGeneration]] + 1.
  11. Return undefined.
Note

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 RangeError is the range of the index type, not a statement about the element type. It is NOT the maximum length of an ordinary Array: that range is allocatable and usable, an array type not being bounded by ArrayCreate's limit. The check is on the growth path only, so a reserve within an existing capacity is unaffected.

An implementation unable to reach a count the index type holds reports it as unimplemented rather than raising here. A RangeError says the LANGUAGE forbids the value; an implementation limit is a different statement, and collapsing the two would make a host's inability read as a rule of the language.

2.11.4 Array.prototype.shrinkToFit ( )

This method releases the backing allocation down to the length. It performs the following steps:

  1. Let O be ? ToObject(this value).
  2. If O does not have a [[TypedElement]] internal slot, throw a TypeError exception.
  3. Let len be ? LengthOfArrayLike(O).
  4. Let capacity be max(O.[[TypedCapacity]], len), where a [[TypedCapacity]] that is absent is 0.
  5. If capacity ≤ len, return undefined.
  6. Set O.[[TypedCapacity]] to len.
  7. Set O.[[TypedGeneration]] to O.[[TypedGeneration]] + 1.
  8. Return undefined.
Note

It is the mirror of reserve and takes its shape from it: asking to release room the array does not have is a no-op rather than an error, so a caller need not know the capacity in order to ask. A fixed [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 live reference into the array including references to elements that remain (2.10). That is what distinguishes it from shortening the length: removing an element invalidates the reference to the element removed and leaves the rest readable, because that storage has not moved, while releasing capacity moves all of it. The distinction is worth stating because the two read as the same operation from outside — both make an array smaller — and they differ in exactly the way the liveness rules are about.

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.

2.11.5 withCapacity ( n )

A call established to select this intrinsic has the index type as its parameter contract and the growable array type [].< T > as its result. A statically established invalid argument or result boundary is a type error, using the same literal admission and 2.11.2 rules as the runtime boundary. The proof follows 1.4.2.8: preserve the selected function's original identity and account for prior mutation and argument effects. A property name alone is insufficient. Unknown calls, accessors, escapes or future invocations that can replace a dependency prevent this proof; no getter or user function is executed to establish it. The property remains replaceable, and a runtime guard cannot justify an early error. Capacity is allocation state, not a fixed extent or a reference-liveness guarantee.

The array type constructor of a growable [ ] . < T > has a withCapacity method that constructs an empty array of that type with room for n elements. It performs the following steps:

  1. Let T be the element type of the array type whose constructor is this value.
  2. Let wanted be ? ToIndexType(n).
  3. If wanted > 2*64 - 1, throw a *RangeError exception.
  4. Let A be ! ArrayCreate(0).
  5. Set A.[[TypedElement]] to T.
  6. Set A.[[TypedCapacity]] to wanted.
  7. Return A.
Note

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.

3 Typed Regular Expressions

A regular expression literal is a compile-time constant whose pattern an engine already parses before the program runs. This clause puts that parse into the type system: the Static Type (1.2.6) of a regular expression literal records the number and kind of its capture groups and the names of its named groups. This is the core of the design's regular expression types (Table 39); the fuller surface it enables, the typed match result and the group-name checking on it, the typed String methods, the flag-dependent result shapes, pattern composition, and string narrowing by pattern, is that extension's to complete and is specified in terms of this core.

The Static Type of a regular expression literal is a nominal Type Record whose [[LibraryName]] is "RegExp" and whose [[Arguments]] are two Type Records, the Captures and the Groups, derived from the pattern:

A non-capturing group, a lookaround, and a backreference add no capture and contribute nothing to Captures or Groups. Because the type is a parameterization of the "RegExp" library type, it is invariant in Captures and Groups (1.2.3.1): a RegExp.<Captures, Groups> annotation whose arguments differ from a literal's inferred shape is a type error. A bare RegExp, the raw library type, is the supertype of every such parameterization, so it holds a literal of any shape while a written parameterization does not hold a value of another.

Note 1
Note 2

An optional capture is 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 undefined and a binding may hold it" (1.5.17). A capture that no matching path entered holds undefined — /(a)?/.exec("")[1] is undefined, not absent — so a union with void has no arm that value belongs to, and the capture would have a declared type nothing could read it into.

An earlier draft said void. It is the same slip this specification records elsewhere, where resolving undefined to void "made undefined unassignable to undefined, which took the whole T | undefined optional idiom with it" — and an optional capture is that idiom.

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 capture count, the group names, and the nesting that decides optionality. The rule for optionality is the design's, that a group is required exactly when every matching path enters it once; a group behind ?, *, 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 capture with the void type.

The empty-capture case is an empty tuple, which is what RegExp.<[], {}> names: a bare [] denotes the empty TUPLE in this grammar, an array being spelled [].<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-capture literal became the thing that refused it. Captures is a tuple in every case, and the no-capture case is the tuple with no elements rather than a case of its own. A pattern with captures is a tuple, so its arity is exact and a capture-count mismatch is caught. The flag-dependent result shapes, in particular the 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.

4 Generics

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 Type Object and its own specialized body. The core relies on this clause in three places already stated: the deferred applications of 1.2.7.2 carry the parameter Type Records this clause substitutes, the computed constraints of 1.4.3.4 bind parameters through the ordering this clause defines, and the invariance step of 1.2.3.1 is relaxed by the variance this clause declares. Named and spread arguments, and variadic parameters, are specified here (4.3, 4.4); the grammar of specialization lists and captures, and the kind of every parameter, are specified here (1.4.3.2, 1.4.3.3), and callable selection and specialized overload contracts are specified in 1.4.3.2.2. Replacement selection for class, alias, and interface families remains an extension hook (Table 39).

4.1 Parameters as Types and as Values

A generic parameter is a parameter Type Record whose [[Declaration]] is its TypeParameter. A type parameter, one whose domain is type (1.4.3.3), denotes that Record in a type position and, in an expression position, the Type Object bound to it, exactly as a type name does. A value parameter, one whose domain is a value domain, is the value generic parameter of the design: its argument is a value of the named type rather than a type, and this specification binds it to the literal type of that value (1.5.19) with the value recoverable as that type's one value. The literal-type binding is primitive and the value is its view, the decision 1.4.3.4 records: a body that reads 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 IdentifierReference in that scope reaches a type parameter after every binding of a scope nested in the body, the body's own top level and its parameter list being unable to bind the name (1.4.3.1), and before every binding of the environment that encloses the declaration, and so before the built-in type names of 1.5.27, which are consulted only when the scope chain is exhausted. A parameter named after a predefined type, 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 IdentifierReference to a value parameter reads its admitted value, including its declared numeric domain; a type parameter reads its Type Object even when that Type Object denotes a literal type. Parenthesizing an argument does not change which binding or view it reads. A value pack uses its specified array view. A nearer ordinary binding shadows an enclosing generic parameter; a generic parameter in its declaration's signature shadows an outer ordinary binding. Apply these rules when a constraint or default is evaluated as well as when the function body is evaluated. Resolving an argument does not initialize a captured ordinary binding or establish reference liveness.

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. Direct eval uses its caller's lexical environment; indirect eval and dynamically constructed functions use their specified global environment and do not inherit generic arguments from a caller. These rules apply equally to a closure after its creating call returns and to a suspended computation when it resumes.

Declaration computations performed before a function activation exists, including argument defaults, inference trials, alias computations and specialization predicates, use the parameter bindings selected for that computation in its declaration environment. Retain the prescribed type, value, pack or metadata view without replaying a producer or adding an ordinary value read. A selected specialization capture, metadata-hook parameter or primitive-operator header parameter belongs to that application's scope. A generic parameter supplies no ordinary mutable storage location: resolving it does not permit assignment or create a location that a ref can borrow, and grants no additional reference permissions or liveness.

Note

Two applications name one specialization when their arguments are pairwise the same: a type argument under SameType, and a value argument under SameValue, which is the identity the literal type already has, so 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 specialization identity would then turn on object identity; the permitted value types are the primitive value types the design lists.

4.2 Constraint and Argument Environments

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 captured generic bindings. Applying it in a type position or through a Type Object in expression position uses the same argument-binding rules, including named and spread arguments, omitted defaults, constraints and value-domain admission. Import renaming, re-exporting, namespace access and forwarding the Type Object do not replace that context. Written argument sources still use the application's context. Obtaining an unapplied interface value does not evaluate its defaults; an alias naming an already completed application does not implicitly apply the declaration again. Independent declaration activations retain independent contexts, required recursive default evaluation joins the containing evaluation budget, and an abrupt application restores the enclosing context. These requirements add no public Type Record identity field and do not initialize a captured binding or establish reference liveness.

  1. Resolve each written type argument, including a named argument or spread operand, in the lexical and generic environments of that argument's source position. Assign the resulting arguments to the applied declaration's parameters by 4.3. The name of a named argument selects a parameter; it does not change the environment of the argument's Type.
  2. Resolve each parameter's effective constraint in its declaration environment. Retain the identities and bindings of free parameters captured from enclosing declarations or supplied by an instantiated class or interface. Apply the bindings established for this application in the order required by 1.4.3.4, preserving the existing rules for F-bounds and forward reads. The applied signature's own parameters remain distinct from enclosing parameters and from parameters of its caller.
  3. If the signature already has a completed description of that effective constraint, apply the remaining substitutions to that description. A description is reusable only for the same declaring environment and captured bindings. Re-reading its Parse Node in the caller's environment is not an alternative resolution rule. Retain the source and its declaration environment for a constraint whose computation is still deferred; a missing result does not discharge the obligation.
  4. Judge each supplied, inferred or defaulted binding against the resulting constraint by the existing admission rules, including the rules for value parameters, literal conversion and variadic collections. Use the same captured constraint when a pack's element bound participates in argument distribution. A statically established refusal is a type error at the application, including an application in an uncalled body. It is not postponed until invocation merely because the signature was returned by another function or obtained through a member.
  5. Resolve an omitted parameter's default and the declaration's where clauses in their original declaration environments under the applicable captured and preceding bindings. Neither publishing a signature nor reflecting it evaluates a default or executes a deferred constraint prematurely. A default supplied at its prescribed application is subject to the same constraint as an explicit argument. Existing compile-time evaluability, initialization and evaluation-limit rules still apply.
  6. When runtime inference is required because an argument's Static Type is any, that static lack of information is not an explicit binding of the generic parameter to the Type Object 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.
  7. Keep distinct applications and captured function values independent. Completing a constraint for one application does not replace the declaration's symbolic constraint, change a different application, initialize a captured binding or prove a reference live. A failed application supplies no bindings to a subsequent application or to its caller.

Callable admission uses the effective constraint descriptions of both signatures under their respective declaring environments and captured bindings. Before a required comparison, complete any constraint description whose determining inputs are available and whose value that comparison requires, subject to the existing evaluability and evaluation-limit rules. A missing description of a written constraint is not an absent constraint or the 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 1.10.2.3 to the resulting descriptions, including its pairing of own binders and contravariant constraint comparison. This requirement applies to published callable returns and runtime callable admission through any, as well as to written callable positions. Completing a bound for one context does not replace the declaration's symbolic description or alter another activation. An implementation may share a completed answer only with the same determining inputs; it must not use the last concrete bound observed for that Parse Node. Abrupt completion or exhausted evaluation supplies no successful admission, and restoration supplies no initialization or reference-liveness fact.

Example 1 (Informative)

The following program evaluates to "ok". At the return from outer, both the returned function and its published callable contract have String as the effective bound of 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 any.

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 published result first restores the free parameter bindings of that function value's creating environment, distinguished by parameter declaration and activation, and then applies the bindings of that function's own parameters when the function is applied. An own parameter shadows ordinary lookup of its name; it does not replace a retained capture of an enclosing declaration with the same name. Retain such captures through further returned signatures even when every visible enclosing parameter name is shadowed. Reflection restores the captured environment without applying the function's own binders or consulting an unrelated caller's generic bindings.

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 captured context. These substitutions neither evaluate a bypassed default nor establish initialization or reference liveness.

Example 2 (Informative)

This program evaluates to "ok". The bound of V retains the T declared by outer, bound to String; the independent T of middle is bound to uint8. The runtime source and published return target agree on that captured String bound. Renaming either 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 site.

At a boundary with an instantiated class result, membership follows IsSubtype from the value's runtime class through its instantiated base types, preserving each base declaration and its arguments. A derived value is not refused merely because its most-derived declaration differs from the result's declaration. Nor does matching a declaration's name or members erase a mismatch in its arguments. This membership judgment does not discharge the separate value-storage restriction of 1.11.4: a subclass that adds storage cannot occupy a base value type position. RequireType applies that restriction to a runtime value reaching an annotated binding, parameter or return through any, just as it does to a field or array element. Reference-class targets keep their existing polymorphic admission and reference-liveness requirements.

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 denotes a generic function type remains generic. A default for a higher-kinded parameter denotes a constructor as a written argument for that parameter does; naming a generic declaration there does not apply it to an empty argument List. At the later application that uses the default, substitute the preceding bindings of that application and validate the resulting argument. Do not execute a computed type merely to obtain a description; retain an unresolved default and its declaring environment until its prescribed use. Supplying an explicit argument bypasses that parameter's default computation. This applies also to a computed type nested within a composite default.

Example 3 (Informative)

The explicit argument in R.<T> occupies U even while T is unavailable to an earlier description pass. This program evaluates to "ok" without evaluating 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 captured declaration environment. Neither a missing description nor repeated source syntax is a missing default, an implicit any binding or proof of a semantic cycle. A required closed default is evaluated under 1.2.7.4, including recursive argument binding, and its completed result is checked against its constraint and every dependent consumer. Exhaustion supplies no completed result and follows the containing checking boundary's existing failure rule. Restoration of the enclosing environment and bindings establishes no initialization or reference-liveness fact.

Note

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 finite default 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 captured parameter. Enclosing substitutions propagate through intermediate generic signatures even when an intermediate signature introduces a same-named parameter.

A generic alias's parameter list does not hide its enclosing dependencies. Retain the free bindings reached through its body, constraints and defaults, while distinguishing the alias's own parameter declarations from captured declarations. Follow further aliases and helpers under the same rule. Retaining a dependency does not evaluate it: bind the alias's own arguments in order, evaluate only defaults required by that application, and keep captured bindings attached to the originating signature when a completed result reaches a consumer.

  1. Retain the source, its declaring environment, the captured parameter bindings and the preceding bindings required by the computation. For a value parameter, use the value admitted by its declared domain, with the conversion required by BindTypeArguments; its numeric domain is not replaced with Number merely because the source argument was a bare numeric literal.
  2. When those inputs are available at the required checking boundary, apply the compile-time evaluability judgment to the source and its transitive dependencies and evaluate the required source under those bindings. Do not enter an enclosing ordinary function body, execute its statements or initialize one of its local bindings in order to manufacture a captured input. Imported inputs retain the initialization and failure ordering of 1.3.8.
  3. An unresolved input retains an obligation; it is not a completed result of any. An abrupt required computation is a type error at this boundary under 1.2.8, and the evaluation-limit rules still take precedence when the computation exhausts its budget. A program-level try statement cannot catch a failure that must be reported before body evaluation.
  4. After a required computation completes normally, apply its resulting type to the owning application and repeat any dependent admission or default-completion judgment that lacked that result. A completed result may be reused only when the source, declaring environment and all determining bindings agree, including captured bindings and bindings established by this application. Distinct parameter declarations remain distinct even if their names agree.

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.

Example 4 (Informative)

The typed parameter of outer makes it participate in result inference. Its published result retains the captured String argument through R, including the computed alias body. The stored application in use completes its default and has a String parameter, so this program has a type error before body evaluation even though 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);
}
Example 5 (Informative)

The omitted default reads the captured uint32 value 16, so its array has two elements. This program evaluates to "ok". Parenthesizing 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";
Example 6 (Informative)

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 site. This program evaluates to "ok".

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";
Example 7 (Informative)

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 "ok". Replacing the heritage argument with 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";
Example 8 (Informative)

Membership in the generic base does not permit losing the subclass's additional storage. The unannotated parameter supplies any at the return, and the return boundary throws a TypeError at runtime. Declaring 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());
Example 9 (Informative)

The helper's captured T is Number; the parameter named T in inner is independent. The shown call is valid. Replacing its second argument with "s" is a type error before script evaluation.

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);
Example 10 (Informative)

The explicit empty argument list requires the omitted argument's default. This program has a type error before script evaluation. Removing the final expression, or replacing it with 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.<>;
Example 11 (Informative)

The first application below retains Number as the bound of U. The final call is a type error before script evaluation. Replacing its argument with 2𝔽 is valid. The explicit application fn.<string> is also a type error, even if its result would never be invoked.

function outer<T: type>(x: T) {
  return function inner<U: type extends T>(y: U): U { return y; };
}
const fn = outer(1);
fn("s");
Example 12 (Informative)

The T in the written argument list belongs to caller; the T in inner's bound belongs to outer. The expression evaluates to 2𝔽. Calling caller("s") instead fails the captured Number constraint when the argument is determined; it cannot replace that constraint with String.

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);
Example 13 (Informative)

The stored specialization below uses Number as the default of U and evaluates to "2". Calling applied("s") instead is a type error before script evaluation. A caller's same-spelled type parameter cannot replace the captured default.

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));
Example 14 (Informative)

The local alias retains the enclosing parameter dependency. The call evaluates to "2". Replacing its argument with "s" is a type error before script evaluation; declaring the alias does not turn 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));
Example 15 (Informative)

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>>();
Example 16 (Informative)

Creating fn and then explicitly supplying Number do not use the computed default. This program evaluates to "2". The same rule applies when 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));

4.3 BindTypeArguments ( params, args, declaration )

The abstract operation BindTypeArguments takes arguments params (a List of Type Parameter Records), args (a List of TypeArgument Parse Nodes), and declaration (a Parse Node) and returns either a normal completion containing a List of Records with fields [[Declaration]] (a TypeParameter Parse Node) and [[Value]] (a Type Record), or a throw completion. It binds an application's arguments to a declaration's type parameters — positional, named, spread, and variadic alike — and is the one operation every application site performs: a type reference, an expression-position application, 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 SubstituteType consumes, and the identity of the specialization.

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 ordered bindings. A statically established failure is a type error; an unresolved application retains its obligation until specialization supplies the missing information.

Expansion or canonicalization of an alias's result does not discharge an unresolved obligation of that alias application. Retain the applied declaration, argument assignment, supplied arguments whose descriptions remain pending, and the declaring environment's captured substitutions independently of the expanded result. When the determining inputs become available, validate the retained application under the existing checking-boundary rules, including when an enclosing generic application supplies those inputs, the alias body omits a constrained parameter, or another alias expands to the same result. A statically closed invalid application in a published callable constraint is refused at that boundary even if the callable is never invoked. An input belonging to an unapplied inner generic declaration remains pending.

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.

Example 1 (Informative)

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 replacement solely because its description is missing, or report it as an omitted argument. Retain its source and argument-to-parameter assignment until the required inputs become available, then complete and validate that argument under the existing checking-boundary rules. An abrupt supplied argument is not replaced by the parameter's default.

If an unresolved spread or argument-dependent distribution prevents establishing the supplied runs, retain the pending assignment; an empty set of completed bindings is not evidence of empty runs. A resolved empty spread follows the ordinary omission rules. Defaults for parameters established to be omitted remain subject to their own demand and input-availability rules, including dependencies on preceding bindings. Retaining dependencies reached through an alias's parameter list does not itself select or evaluate any default. None of these checking states is a completed binding of any, initialization, or evidence of reference liveness.

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 specialization supplies a determining input, complete the retained obligation at the existing required checking boundary. An independent closed default is not suppressed by another parameter's pending description; a default that requires an unavailable input remains pending. Retaining this obligation does not evaluate a bypassed default or execute the ordinary enclosing or applied function body during preparation.

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 specialization supplies the determining inputs. Substitute the caller's source inputs separately from the applied declaration's bindings. Preserve contextual expression judgments, including literal propagation, fresh object and array creation, optional admission and rest positions; an unadapted source shape is not a substitute for such a judgment. A statically determined invalid consumer is refused at the existing required checking boundary even when it occurs in an unused caller. Distinct applications do not replace one another's source descriptions. This completion executes no ordinary body, establishes no initialization or reference-liveness fact, and is not by itself evidence that a runtime conversion can be elided.

Example 2 (Informative)

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);
Example 3 (Informative)

This Script reports a default-evaluation type error before its body is evaluated. 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");
  1. Interpret the type-source judgments below in the argument and declaration environments required by 4.2, retaining the applied signature's captured substitutions.
  2. Let entries be a new empty List.
  3. For each TypeArgument a of args, do
    1. If a is TypeArgument : ... Type , then
      1. Let t be ? the Type Record the Type of a denotes.
      2. If t.[[Kind]] is tuple, let elements be a List of the [[Type]] fields of t.[[Elements]], in order.
      3. Else if t is an array Type Record whose extent is stated, let elements be a List of that many copies of t.[[Element]].
      4. Else, throw a TypeError exception.
      5. For each element e of elements, append the Record { [[Name]]: none, [[Type]]: e, [[Node]]: none } to entries.
    2. Else if a is TypeArgument : BindingIdentifier : Type , then
      1. Append the Record { [[Name]]: the StringValue of BindingIdentifier, [[Type]]: none, [[Node]]: the Type of a } to entries.
    3. Else,
      1. Append the Record { [[Name]]: none, [[Type]]: none, [[Node]]: a } to entries.
  4. Let firstNamed be the least integer idx such that entries[idx].[[Name]] is not none, or the number of elements of entries if there is no such integer.
  5. Let positional be a List of the first firstNamed elements of entries.
  6. Let named be a new empty List.
  7. Let i be firstNamed.
  8. Repeat, while i < the number of elements of entries,
    1. Let e be entries[i].
    2. If e.[[Name]] is none, throw a TypeError exception.
    3. If params contains no element whose [[Name]] is e.[[Name]], throw a TypeError exception.
    4. Let p be the element of params whose [[Name]] is e.[[Name]].
    5. If named contains a Record whose [[Parameter]] is p, throw a TypeError exception.
    6. Let run be « e ».
    7. Set i to i + 1.
    8. If p.[[Variadic]] is true, then
      1. Repeat, while i < the number of elements of entries and entries[i].[[Name]] is none,
        1. Append entries[i] to run.
        2. Set i to i + 1.
    9. Append the Record { [[Parameter]]: p, [[Run]]: run } to named.
  9. Let slots be a new empty List.
  10. For each element p of params, do
    1. If p.[[Variadic]] is true, append a rest slot to slots.
    2. Else if p.[[Default]] is none and named contains no Record whose [[Parameter]] is p, append a fixed slot to slots.
    3. Else, append an optional slot to slots.
  11. Let prefix be the elements of params before the first whose [[Variadic]] is true, or all of params if there is none.
  12. Let admits be a new Abstract Closure with parameters (index, which) that captures named, positional, and prefix and performs the following steps when called:
    1. Let p be the parameter which stands for.
    2. If p.[[Variadic]] is false, return true.
    3. If named contains a Record whose [[Parameter]] is p, return false.
    4. If EffectiveConstraint(p) is none, return true.
    5. If EffectiveConstraint(p) reads any parameter not in prefix, return true.
    6. Let bound be the Type Record EffectiveConstraint(p) denotes, evaluated over the bindings of the elements of prefix to the corresponding leading elements of positional, as the binding loop below binds them.
    7. If bound is not an array or tuple Type Record, return true.
    8. Let element be the element type of bound (1.4.4).
    9. Let v be the Type Record positional[index] denotes.
    10. Return IsSubtype(v, element, a new empty List).
  13. Let counts be SequenceAssignment(slots, the number of elements of positional, admits).
  14. If counts is unmatched, throw a TypeError exception.
  15. Let bindings be a new empty List.
  16. Let cursor be 0.
  17. For each integer j such that 0 ≤ j < the number of elements of params, in ascending order, do
    1. Let p be params[j].
    2. Let supplied be a List of the counts[j] elements of positional beginning at index cursor.
    3. Set cursor to cursor + counts[j].
    4. If named contains a Record whose [[Parameter]] is p, then
      1. Let r be that Record.
      2. If supplied is not empty, throw a TypeError exception.
      3. Set supplied to r.[[Run]].
    5. Let selfBound be false.
    6. If p.[[Kind]] is type, p.[[Variadic]] is false, and EffectiveConstraint(p) contains a permitted reference to p as a type argument under 1.4.3.4, set selfBound to true.
    7. If selfBound is true or EffectiveConstraint(p) is none, let constraint be none; otherwise let constraint be ? the Type Record or Family Constraint Record EffectiveConstraint(p) denotes, evaluated over bindings (1.4.3.4).
    8. If p.[[Variadic]] is false, then
      1. If supplied is empty, then
        1. If p.[[Default]] is none, throw a TypeError exception.
        2. Let v be ? the Type Record the Type of p.[[Default]] denotes, evaluated over bindings, using the declaration role of 4.13 if p.[[Arity]] is greater than 0.
      2. Else,
        1. Let v be ? the Type Record supplied[0] denotes: its [[Type]] where that is not none, and the type its [[Node]] denotes otherwise, using the declaration role of 4.13 if p.[[Arity]] is greater than 0.
      3. If p.[[Arity]] is greater than 0 and v does not denote an unapplied declaration or higher-kinded parameter of that arity as required by 4.13, throw a TypeError exception.
      4. If p.[[Kind]] is value and v.[[Kind]] is literal, set v to the literal type of v.[[Value]] converted to constraint as 1.4.3.4 binds a literal argument; if the conversion fails, throw a TypeError exception.
      5. Else if p.[[Kind]] is value and v.[[Kind]] is not parameter, throw a TypeError exception.
    9. Else,
      1. If supplied is empty and p.[[Default]] is not none, then
        1. Let v be ? the Type Record the Type of p.[[Default]] denotes, evaluated over bindings.
        2. If v.[[Kind]] is not tuple, throw a TypeError exception.
      2. Else,
        1. Let elements be a new empty List.
        2. For each element e of supplied, do
          1. Let ev be ? the Type Record e denotes.
          2. If p.[[Kind]] is value and ev.[[Kind]] is literal and constraint is not none, set ev to the literal type of ev.[[Value]] converted to the element type of constraint; if the conversion fails, throw a TypeError exception.
          3. Else if p.[[Kind]] is value and ev.[[Kind]] is neither literal nor parameter, throw a TypeError exception.
          4. Append ev to elements.
        3. Let v be the tuple Type Record whose [[Elements]] are Records with [[Type]] the elements of elements, in order, none of them a rest and none carrying an [[Initial]].
    10. If selfBound is true, then
      1. Let provisional be a copy of bindings.
      2. Append the Record { [[Declaration]]: p.[[Declaration]], [[Value]]: v } to provisional.
      3. Set constraint to ? the Type Record or Family Constraint Record EffectiveConstraint(p) denotes, evaluated over provisional.
    11. If constraint is not none and ? SatisfiesTypeConstraint(v, constraint) is false, throw a TypeError exception.
    12. Append the Record { [[Declaration]]: p.[[Declaration]], [[Value]]: v } to bindings.
  18. Assert: cursor is the number of elements of positional.
  19. For each where clause of declaration, evaluate it over bindings as 4.10 specifies; if the result is false, throw a TypeError exception.
  20. Return bindings.
Note 1

A value parameter takes a value (4.1): its argument is a literal type, converted to the domain, or a value parameter of an enclosing declaration, bound later. Any other argument denotes a type, and is refused before the constraint is consulted, since a type such as uint8 supplied for N: uint8 would otherwise pass SatisfiesTypeConstraint by being assignable to the domain it was meant to be a value of.

Note 2

An ordinary concrete constraint is checked with IsAssignable and not IsSubtype, which is the one place a gradual rule outranks a structural one. any is "the type of every value ... the type of a binding that carries no annotation" (1.5.15), and it is a subtype of nothing, so a subtype test would refuse every call a binding without an annotation makes to a constrained generic. That would make a constraint a wall between typed and untyped code rather than a claim about types, and untyped code calling typed code is the case this proposal is built to keep working. An argument of any other type is held to the constraint exactly as a subtype test would hold it.

Note 3

The positional half is the rest-parameter rule verbatim: 1.10.4.2 is the operation the design's "greedy, giving back to satisfy the parameters after" names for values, and a pack is a rest slot in it. A run ends where the next argument fails the pack's element bound; a non-variadic slot admits by arity alone, its constraint checked once bound, which keeps the split independent of what fixed parameters would accept — one rule for values and for types, and the same diagnostics. A parameter supplied by name is satisfied by its name, so for the split its slot is optional: 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 — SameType for a type, SameValue for a value — name one specialization whatever their spelling, positional, named, or spread; identity is the ordered bindings and never the text. Where the applied declaration is known statically, the refusals this operation performs for a positional argument after a named one, an unknown name, and a parameter supplied twice are the type errors of 1.4.2.2; a name written twice in one list is a Syntax Error there and never reaches this operation. The operation restates them for an application resolved at run time.

4.4 Variadic Parameters

A variadic parameter (1.4.3) binds a tuple Type Record: the elements are the Type Records its arguments denote, in order, and the tuple's length is the number of arguments. It is a type error if its TypeParameterConstraint does not resolve to an array or tuple type, the rule 1.4.4 states for a rest parameter's annotation; it is a type error if its TypeParameterDefault does not resolve to a tuple type; and it is a type error if two consecutive variadic parameters have constraints either of whose element type is the any type, which completes the early error of 1.4.3.1 for the bounds that need resolving. Two consecutive variadic parameters of one element type are admitted: the first takes what both admit, and the parameter after them says where the run must stop, as it does for two rest parameters of one type.

Within the specialized body, a variadic value parameter — one whose [[Kind]] is value — reads as a frozen fixed-extent array, one per specialization, whose elements are the values of its literal types: I.length, I[k] for a constant k, and the array methods over them are compile-time expressions (1.2.7), and the same array answers every read, so I === I across calls of one specialization. A variadic type parameter reads as the Type Object of its tuple, as any type parameter reads as its type. This is the rule of 4.1 applied pointwise: a value parameter reads as its value, so a tuple of them reads as their array. A where clause over a variadic parameter (4.10) is checked once per specialization, every element being a constant.

A variadic parameter is bound from a call's arguments wherever a scalar parameter is: through a rest parameter typed by it, from constant arguments as every value generic argument is; through a parameter of its tuple type, from one tuple; through a written tuple pattern, pairUp<T: type, ...Rest: [].<type>>(p: [T, ...Rest]), by the assignment BindTypeArguments performs on its positional half; through a callback's signature, structurally; and through a builder that stands between it and the arguments only by that builder's declared inverse (17.3), never by search. The ladder is that of 17 without exception. A spread argument, f(...xs), binds a variadic parameter only where the length of xs is statically known, as the type-argument spread of 1.4.2.2 requires. Where a parameter's binding depends on the variadic parameter's length — N: uint32 = I.length — it is a computed default of 1.4.3.4 and evaluates once the pack is bound.

Note

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 (1.4.2.4) written once and named. The one modifier that distributes rather than maps is ref on a rest parameter (1.4.2.10), which is what lets a callback take a pack directly: (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 Static Type has a static length — a tuple, an array of stated extent, or a value typed by a pack, as ...xs for xs: Ts is when a pack forwards — and a spread of a dynamic array into a rest typed by a pack is a type error, the call-site twin of the spread type argument's rule (1.4.2.2): the check is static, since the run time would bind from whatever values it found. A parameter the call's explicit type arguments bind keeps that binding through inference, and the computed constraints and defaults of the parameters after it evaluate over it, so 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 Type Object for a type parameter, the value for a value parameter, the array for a value pack.

A specialization chain that lengthens a tuple at each step — the reader of the design's binary packet example, read<T: type>(): Reader.<[...Ts, T]> — is finite per call site and costs what any specialization costs. A declaration that applies itself to a longer pack recurses without end and is stopped by the evaluation budget (1.2.7.4), as a recursive type alias is: a diagnostic naming the declaration, never an implementation's stack.

4.5 Specialization as a Value

A generic function applied in expression position, f.<uint8>, denotes a function object: the specialization of f at the bindings BindTypeArguments (4.3) yields for its arguments. Specializations are interned per function object and ordered bindings, so 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 specialization's where clauses are evaluated when it is created, as the last step of BindTypeArguments, and never per call. A specialization declares no type parameters, so applying type arguments to one is a type error. It does not capture a receiver: obj.m.<uint8> is the specialization of the function obj.m denotes, receiver-independent, and a call 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 Static Type of a stored application from its completed bindings, including defaults evaluated in declaration order in the parameter environment of 4.2. A required checking-time default that has not yet completed is a dependency of the application and of judgments consuming its signature; it is neither 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, abrupt completion and evaluation limits remain those of the required default evaluation.

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 Static Type or restore the original generic signature as a new inference candidate. Reconsider dependent argument admission using the original application's completed bindings and each argument's source context, as required by 4.3.

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 captured declaration dependencies. This applies to a selected case, an owner's fallback and an ordinary generic function, and to an evaluation that retrieves an existing specialization. For a generic specialization, use its written return contract when present; otherwise use its published inferred result, closed over the declaration environment and completed application. Written 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 captured outer bindings, so a same-spelled outer parameter cannot replace a selected capture. Apply this rule to published inferred results as to written annotations. An unrelated caller's type-parameter frame neither instantiates an unapplied function value nor changes a stored selected value. Reflection and callable compatibility use the same completed contract. Invocation also preserves the selected captures when restoring the declaration's captured environment, so the body observes the same bindings as its signature. Temporary description and invocation contexts are restored on normal and abrupt exits.

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 1.6.2. Then perform the ordinary initialization admission. A previously completed destination, a written annotation or an unrelated declaration is not replaced by the selected value's contract. Preserve the shared checking publication and the specialization's identity; neither an arbitrary callable's reflected type nor a caller's same-spelled binding supplies this completion evidence. This retention establishes no reference liveness. A transparent implementation wrapper invoking the original specialized function preserves its checked result completion and abrupt outcome under the same result-forwarding rules.

For argument binding, a specialization's own parameter list is the specialized function's list with its completed bindings applied: names, optionality, rest parameters and reference permissions are retained. In the absence of a separately declared callable type in view, named and object-spread arguments use that list and its completed parameter types under 1.10.4. A separately declared callable type in view retains the precedence given there, including its names and initial values. Reading the specialization's call metadata does not evaluate parameter initializers or select type arguments again. A skipped ordinary parameter is left undefined for the original function's initializer, which executes in its original environment at the ordinary call-time point.

An implementation forwarding a specialization call to its original function does not introduce an additional value-consuming boundary for reference arguments. The original function's parameter binding retains the ordinary reference checks and value decay; actual calls through value-consuming built-ins retain their existing behaviour. This forwarding does not capture a receiver, change specialization identity or where-clause timing, or establish reference liveness.

Example 1 (Informative)
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.
Example 2 (Informative)
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.
Note

A call written with type arguments, f.<uint8>(x), creates or finds the specialization and calls it; finding one is a lookup keyed on the bindings, which is the cost of an interned record and not of a call. The design's 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 Type Object throughout, and a function application is the same specialization with a function object as its value.

4.6 Crossing a Boundary with a Generic Function

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 1.10.2.3 infers, the value that crosses is the specialization at them (4.5), its 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 specialization of whichever function the binding holds, by that function's identity and the call's bindings, and a call that infers its type arguments infers them against the held function's signature. This is the one place a specialization is selected by a callee not known where the call is written; the checks remain compiled away, and what is dynamic is the callee, which is dynamic at every indirect call already.

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 specialization as the equivalent explicit application. Perform its declaration checks before publishing the converted value. Evaluate specialization-time declaration expressions in that function's declaration environment, realm, private environment and captured generic context, not in the receiving caller's environment. This applies equally when the function was reached through any. The specialization captures no receiver.

Compatibility and materialization are distinct operations. A successful IsOfType or signature relation does not itself create a specialization or run its 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 specialization identity. The completed signature retains its declared narrowing guarantees with the same substitution. Existing reference modes and liveness requirements remain in force, and an abrupt crossing does not publish a replacement into its destination.

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.

Note

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 slot that wants a concrete signature, arr.map(id) — pays nothing beyond the assignment. An implementation may cache the lookup at the call site as it caches any indirect call's target.

4.7 SubstituteType ( t, bindings )

The abstract operation SubstituteType takes arguments t (a Type Record) and bindings (a List of Records each with a [[Declaration]] and a [[Value]]) and returns a Type Record. It replaces the generic parameters bound by bindings throughout t and evaluates any deferred application thereby closed, the operation full specialization performs at each distinct application. It performs the following steps when called:

  1. If t.[[Kind]] is parameter, then
    1. For each Record b of bindings, do
      1. If b.[[Declaration]] is t.[[Declaration]], return b.[[Value]].
    2. If t has a [[Constraint]] field containing a Type Record, then
      1. Let constraint be SubstituteType(t.[[Constraint]], bindings).
      2. Return a Type Record with the same fields as t, except that [[Constraint]] is constraint.
    3. Return t.
  2. If t.[[Kind]] is deferred, then
    1. Let args be a new empty List.
    2. For each element a of t.[[Operands]], do
      1. If a is a Type Record, append SubstituteType(a, bindings) to args.
      2. Else, append a to args.
    3. If some element of args is a Type Record with a parameter within it, return the Type Record { [[Kind]]: deferred, [[Operator]]: t.[[Operator]], [[Operands]]: args }.
    4. If t.[[Operator]] is keyof, return KeyTypesOf(args[0]).
    5. If t.[[Operator]] is indexed, return IndexedTypeOf(args[0], args[1]).
    6. Return the [[TypeRecord]] of the Type Object that results from evaluating the deferred application of t.[[Operator]] to args by EvaluateBuilderCall, which is a normal completion because args is now closed.
  3. Let u be a Type Record with the same fields as t, except that every Type Record reachable from t through its fields and the elements of any List or structured Record those fields hold, without descending into a nested deferred or parameter, is replaced by the Type Record that substituting bindings into it yields.
  4. Return CanonicalizeType(u).

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 replacement is returned directly, without recursively applying the same bindings to the replacement. For an unbound parameter, substitution may change its effective constraint but preserves its declaration identity. Traverse effective constraint descriptions and the signature's parameter, declared and published result, receiver and narrowing records consistently. Retain unevaluated constraint and default Parse Nodes in their declaring environments; substituting their captured inputs does not evaluate them before the application that requires them. Preserve recursive record graphs when traversing these fields: revisiting the same record under the same bindings reuses its substituted record rather than unfolding the graph.

Note

Substitution and evaluation are one pass: a deferred application becomes evaluable exactly when substitution has closed every parameter it read, at which point 1.2.7.3 runs under the budget and memo already specified, so a builder used at two instantiations evaluates twice under two distinct memo keys and interns each result. A value generic contributes its literal type where a type is substituted and that type's value where the body computes, which is the one mechanism seen from two sides. Where substitution leaves a parameter in place, in the body of a still-generic outer declaration, the result is itself generic and specializes when the outer one does. A tuple element written with ... whose [[Type]] substitutes to a tuple Type Record is spliced element-wise into the containing tuple, so [...Ts, T] under Ts bound to [uint8, string] is [uint8, string, T] and not a tuple holding a tuple; CanonicalizeType performs the same splice, so the two spellings intern as one record.

4.8 Evaluated Class Identity and Generic Families

4.8.1 Declaration Denotation Across Source Texts

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 constructor argument uses that actual constructor's association.

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, constructor and declaration context. A successful declaration remains published after assignment changes its value slot or a later statement completes abruptly. A failed or unreached initialization publishes no completed outer denotation. Keep the class's own inner context and existing self-reference rules distinct from publication of the outer binding. Checking alone does not initialize a class, and symbolic forward references retain their existing completion and availability rules.

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 replacement value, if any, and retain the original declaration's class contract as its type denotation. A replacement does not retarget the declared contract. A partial class declaration reopens an already initialized class and does not initialize or republish the primary declaration's binding. Failed class evaluation or decoration must not leave a completed constructor association that could satisfy an escaped forward receiving dependency. Already performed effects and independently escaped inner values are not rolled back.

A later Script in the same Realm uses the same retained declaration association. An import follows its resolved exporting binding through renaming and re-exporting; a qualified name through an actual module namespace follows the corresponding export. These routes do not read a replacement constructor to choose a type, evaluate an unavailable dependency, or bypass the availability requirements of 1.3.8. A generic class retains its original family and uses that family's argument, default, constraint and specialization rules through each route.

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 denotes a type only if it independently satisfies the existing type-expression rules. Class expressions and their immutable aliases retain the rules below. Do not key a declaration association by its name, source Parse Node alone, or the most recently evaluated constructor: recursive, repeated and suspended activations retain their own binding instances.

Keep the constructor and declaration context reachable for as long as the declaration denotation is reachable. No public property or instance shape is added. Retaining this type denotation grants no reference authority, changes no reference-liveness requirement, and supplies no proof that an ordinary value read still produces the original constructor.

Example (Informative)

Evaluate each block as a successive Script in one Realm. The last initialization succeeds against the original V. In the last Script, type V denotes that declaration's class, while ordinary V and new V() use the replacement.

// 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]] List. Its nominal Type Records retain that [[DeclarationIdentity]], in addition to the source [[Declaration]] and ordered [[Arguments]]. Repeated evaluation of the same source Parse Node, equal captured values, and equal argument lists do not identify two evaluated classes. A forward type reference associated with a particular lexical class binding retains the identity of the family that initializes that binding; it does not allocate a second identity when the declaration is evaluated. An unresolved declaration template alone is not evidence that two runtime classes are the same.

Retain evaluated class identity when a nongeneric class occurs as an argument of another type, including nested application arguments and structural type components. SameType, nominal subtyping, type interning, and specialization lookup use the same identity distinctions. An unresolved declaration template does not acquire the constructor most recently created from that source Parse Node. A forward record can be completed only by the class associated with its own lexical binding identity; completion preserves the argument identity used by an existing specialization. Aliases and repeated uses of the same completed class remain equal, while another evaluation of that declaration remains distinct. For a nominal argument containing an array, tuple, object, callable or other type description, complete corresponding declaration dependencies within that description before exact argument comparison. Retain the target's modes, extents, metadata and other structure. Union and intersection members are unordered; ambiguous correspondence does not select an evaluated identity. This rule concerns type descriptions supplied by independently checked bindings, not observations of composite runtime values. These rules introduce no public property reads and do not change reference liveness.

Within one evaluated family, the same ordered argument bindings identify one specialization. Explicit application, inferred or defaulted construction, defaulted heritage, default-value materialization, conversion-triggered materialization, and reflection retain the same family identity. Applying an already specialized constructor again names another application of its original family. Reflection through a generic base and reconstruction of the same arguments preserve that identity.

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 ordered arguments, metadata and captured heritage. An already completed nominal identity retains its own context and is not rebound to another evaluation of the same declaration. Keep a shared checking result independent of any one runtime activation. This instantiation does not infer a destination from the tested value, read public properties, invoke constructors, or replace declaration identity with a current same-spelled binding's value. Ordinary constraint and value admission still apply: a concrete 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 Module Record and declaration context through renaming and re-exporting. At the importer's pre-body checking boundary, complete that declaration's available evaluated identity, heritage and implemented interface identities in the exporting module's environment. Do not compare a reconstructed symbolic class template against a completed interface application as though the missing identity proved incompatibility. Completion does not read the current value of a mutable exported class binding or establish the current constructor origin of an ordinary expression. It neither evaluates a dependency body nor makes an uninitialized binding available; module ordering and unavailable-input failures remain those of 1.3.8. Constructor signatures, declaration verification, argument binding, value admission and reference liveness retain their existing requirements.

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 replacement value does not establish it. Stable aliases retain the original evaluated identity rather than introducing another one. A failure during class evaluation or binding initialization publishes no dependency for that outer binding. The class expression's own fresh inner context remains available to its established self-reference rules; this does not initialize the outer binding or expose the expression's private name outside that context. Later evaluations, including recursive calls and loop iterations, cannot replace an earlier binding instance's retained dependency. A specialization cannot replace its originating family's association. If a class-expression template has no corresponding retained context, the constructor most recently created from its source cannot establish membership. These rules do not execute the initializer again, admit a value against a target derived from that value, or change reference-liveness requirements.

Evaluate written argument expressions in their caller's context. Resolve declaration defaults, constraints, specialization selection and the selected class body in the family's retained lexical and private environments, captured type and value parameter bindings, Realm, and Script or Module. The application's own parameter bindings extend that declaration context. Preserve the existing order of argument evaluation, inference and defaults. Restore the caller's context on normal and abrupt completion.

Completed-specialization reuse and in-progress recursion recognition both distinguish the evaluated family and its argument bindings. A self-reference to the application under construction denotes that application's constructor once it is available. An application of another family or at different arguments does not become that self-reference. Only a successfully completed specialization establishes a reusable completed result; an abrupt completion clears its in-progress entry without removing another active application's entry. Already performed user effects are not rolled back.

Evaluation of a closed class application in a type position obtains the same completed specialization contract as evaluation of its value-position application. Use that specialization's associated Type Record, including its resolved implemented interfaces, rather than copying the family's unresolved contract. The first evaluated application performs the required specialization initialization; subsequent type and value spellings reuse that result without repeating static initialization. Exact recursive applications retain the in-progress identity under the rules above. These requirements do not authorize source preparation to execute ordinary enclosing bodies or make unavailable declaration inputs available.

Completion of a checked declaration's evaluated identity preserves its parameterized interface obligations while only the family's runtime contract is available. A dependent target omitted from that runtime contract is not an established absence of the target. A completed specialization supplies its resolved interface list. Identity completion itself neither evaluates that specialization nor discharges an unresolved target's admission obligations.

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 specialization case retains the primary's evaluated family identity; the rules determining which source declarations belong to the case group are unchanged. None of these identity rules grants a reference additional permissions or extends its lifetime.

4.9 Constructing a Generic Class

"The class is the type a construction yields" (1.11.2); for a generic class, the specialization is. A construction new C Arguments where C evaluates to the constructor of a generic class declaration, a target-typed construction new . Arguments whose contextual type is an instantiation of one (1.4.6), and a Reflect.construct whose target is such a constructor all construct a SPECIALIZATION of the declaration, never the declaration itself: its parameters bind, in order and left to right, by the ladder below, and the construction proceeds as new C.<…> Arguments would at those bindings. The declaration's own constructor has no body that can run, since its parameters are bound in no frame, and a bare construction that produced an instance of it produced one whose typed fields checked nothing.

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 type error naming that parameter. Unknown argument contributions or an open specialization are not proof of a missing binding, and another viable overload remains eligible. No implicit any binding is invented.

A parameter binds, taking the first source that fixes it:

  1. an EXPLICIT argument, by 4.3, where the construction writes TypeArguments;
  2. the CONTEXTUAL type of the construction (1.6.5), where that type is an instantiation of the same declaration, or a union one of whose members is: each argument of that instantiation binds the parameter in its position, an argument written 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 type error at the argument;
  3. INFERENCE from the value arguments, through the constructor's parameters as a call to a generic function infers through its parameters (1.4.3.4, 17): 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 1.6.6 gives a literal in any other typed position, so 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 contextual type that clause speaks of. A rest parameter's arguments each contribute by the same rule and the parameter binds a tuple of them. Inference is one rung and does not join across rungs: a binding an earlier rung fixed stands, so a seed taken from the call's context is kept and an argument that contradicts it is dropped;
  4. the parameter's TypeParameterDefault, evaluated over the bindings so far;
  5. otherwise it is a TypeError exception naming the parameter and the declaration. A parameter no argument reaches and no default supplies is not bound to any or left open: "a parameter left by all three is an error" (4.3), and a signature "whose parameters cannot be bound is not viable" (1.10.3). The same holds of a call of a generic function.

Within the construction new.target is the specialization, as it is for new C.<…>, and the instance's prototype and typed fields are the specialization's. The declaration's [[Construct]] is reached with newTarget equal to itself by every form above; a super() from a class extending a specialization never reaches it, that heritage having evaluated to the specialization; and Reflect.construct with the declaration as target and an unrelated newTarget is a TypeError exception, since running the declaration's body against a foreign prototype would produce the open instance by another route.

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.

Note

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 Static Type is the specialization so bound (1.2.6). Where the run time is reached and the checker saw nothing — the class through an unresolved or erased alias, Reflect.construct — the run time binds alone, by the same ladder.

Collections are unaffected. Map's constructor declares no type parameters, so there is nothing here to bind, and an unannotated new Map() remains the untyped collection of 1.15.7; let m: Map.<string, uint8> = new Map() remains the adoption of 1.15.5, which reads the boundary AFTER the construction because a collection's arguments live on the instance. A class's live in its prototype, which is why a class must decide before it allocates and a collection may decide after.

4.10 The where Clause

A generic declaration may carry a where clause, written with the WhereClauses of 5.1 where 16 admits them: a compile-time-evaluable Boolean expression over its parameters, checked at each specialization once its parameters are bound. Where the expression is false for an application's bindings, that application is a type error, reported against the clause's source. A where over value generics, as where I.every(i => i < N) on a variadic pack, is the design's compile-time bound on the values a parameter admits; a where that inspects a runtime value rather than a generic parameter is the dependent record type extension's, checked at the boundaries Table 23 enumerates rather than at specialization; and a where naming return on a compile-time-evaluable function is a contract (16). 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 type error at the declaration; a record refinement's this is a different rule with a different subject. A result contract requires a declared return type or a published inferred return type; on a function declared void it is a type error at the declaration, since there is no result to constrain; on a generator it constrains the generator's return value, and on an async function the fulfilled value of its promise. One syntax carries all three because one rule does: a predicate is evaluated where its subject exists, which is at specialization for a parameter, at a boundary for a value, and at evaluation for a result. A method signature in an interface or an object type, and an abstract method in either of its spellings, may state WhereClauses: they are the contract of every member that implements or overrides it, a call through the signature is judged against them as a call of a declaration is, and it is an Early Error for an implementing or overriding member to state a clause they do not imply. A clause is implied by the inherited clauses when its text is one of theirs, or when it is one ordered comparison of a parameter, matched by position, against a constant, whose satisfying values include every value the inherited comparisons of that parameter admit; any other clause is not implied. A member that states no clause keeps the inherited contract. A clause that reads an optional parameter reads undefined for it when the argument is omitted, as the body would; so 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 specialization predicate is a declaration requirement, independent of whether an application is present, reachable or closed. Reject a predicate that names ambient mutable state, including through a helper, import or re-export, under 1.2.7.1. An unbound generic argument or an unavailable immutable input may defer evaluation; it does not waive this evaluability requirement. All syntactic operands must satisfy that requirement, including an unselected operand. A helper may still create and mutate its own local state. These rules do not turn a runtime-subject predicate or result contract into a specialization predicate.

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 specialization predicate whose inputs are available under the compile-time-evaluation rules is checked at the source's checking boundary, including in an uncalled function or an unreachable statement. Nesting depth and declaration placement do not remove this obligation. A predicate that is false produces the clause's type error before the candidate body is entered. Checking retains the clause's original source location, declaration scope and generic parameter identities; a temporary checking environment may not replace an unavailable nested binding with a same-spelled outer value or execute ordinary enclosing statements to obtain it.

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 1.2.7.4, rather than silently treating an unvisited obligation as satisfied.

Note

The clause is evaluated by 1.2.7 like any other compile-time expression, so it may call builders and read a value generic's value, and its determinism and budget are those already fixed. A where is a check, not an inference: it narrows which applications are legal, and never changes what a parameter is bound to.

4.10.1 Specialization Predicate Completion

At a source-checking boundary, a closed specialization predicate is subject to the following rules. These rules apply to function and type alias applications, including applications retained inside enclosing generic declarations. They do not move a runtime-subject predicate or a result contract to that boundary.

  1. Establish the predicate's bound generic inputs and the availability of its external lexical dependencies, following the original declarations through helper calls and imports. Evaluability and availability are separate requirements. An admissible immutable initializer does not itself initialize its ordinary lexical binding. A class declaration's nominal identity does not supply a constructor, decorated members or layout that still require class evaluation. Do not execute ordinary enclosing statements, initialize a runtime binding, or replace a dependency with a same-spelled binding in order to obtain these inputs.
  2. If an explicit deferral rule applies, retain the obligation without evaluating the predicate or recording success. Unbound parameters, an uninstantiated enclosing environment and runtime-dependent inputs retain their existing deferral rules. A required imported input at a module boundary instead follows 1.3.8, including its failure rule for an uninitialized binding. A failed evaluation is not by itself evidence that a deferral rule applies.
  3. Otherwise evaluate the RefinementPredicate with its original lexical scope and the application's bound parameters, using 5.1 and the compile-time-evaluable fragment. Conditional and short-circuit forms evaluate only their selected operands. A throw handled within that evaluation contributes the handler's completion; a finally clause has its ordinary completion-replacement semantics.
  4. If the containing type evaluation exhausts its budget, apply 1.2.7.4. This outcome takes precedence over any incidental throw, caught exception, Boolean result or missing result. Record no successful verification.
  5. If evaluation attempted to read an uninitialized external module binding required at this boundary, apply 1.3.8 even if a catch or finally clause replaced the read's abrupt completion. Record no successful verification. An unselected operand performs no read and establishes no such failure.
  6. If evaluation otherwise completes abruptly, the application has a type-evaluation error reported against this clause's source, before the candidate source body is entered. This is distinct from a predicate that holds or fails normally. It applies regardless of the thrown value's class, properties or message. Do not suppress the error, convert it into a false predicate, or retry it after body entry.
  7. If evaluation completes normally and the predicate does not hold, report the unsatisfied-constraint type error against this clause's source. If it holds, verification may be reused only under the semantic-input identity rule above. An unavailable input, an abrupt completion and budget exhaustion never establish a successful verification.

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 captured binding with a same-spelled consumer binding.

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.

4.11 Class Replacement Contracts

A replacement class preserves its primary's effective public instance contract after substituting the arguments selected by its specialization pattern. It is a type error if an established required public member is absent, including a member with an established Symbol key. Resolve inheritance, own-field precedence and accessor descriptors before comparing capabilities. Private and protected implementation details are not public obligations, and this rule does not introduce static-member overriding.

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 field or accessor write capability must remain available and accept every value the primary accepts. Readonly storage cannot replace required writable storage; a setter's accepted type is compared independently of its getter's result. A replacement may add public members or change private representation without changing these obligations.

Each substituted primary constructor signature must be accepted by a replacement constructor signature under the parameter-sequence compatibility rules, retaining the complete Parameter Records. Preserve accepted argument counts, optional positions, defaults, rest extents and reference permissions. A primary default admits omission; it does not execute before the replacement constructor is selected. The replacement must independently admit that omission. Constructors cannot discard required reference markers or change their invariant target types. An entirely untyped constructor retains the existing gradual treatment for copied arguments, without erasing a primary reference permission.

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 constructor, getter, setter, conversion or default expression is executed to compare contracts. Layout remains determined by the selected implementation, and successful compatibility establishes no additional reference-liveness permission.

4.12 Declaration-Site Variance

This clause states what a variance declaration means; a parameter carrying none is invariant, which is the rule 1.2.3.1 gives and the conservative default. A type parameter is declared covariant by the VarianceModifier out and contravariant by in (1.4.3). Where a parameter is covariant, one instantiation is a subtype of another along that position when the argument in the first is a subtype of the argument in the second; where contravariant, when the reverse holds; where neither is declared, the position is invariant, the default of 1.2.3.1. A covariant parameter is well-formed only where it appears in output positions of the declaration, a method return or a readonly field, and a contravariant parameter only in input positions, a method or constructor parameter; a parameter appearing in a position of the wrong polarity is an early error, since the variance would be unsound there for the reason the invariance note gives of a mutable container.

Table 41: Positions by Polarity
PositionPolarity
A method or function return typeOutput.
A readonly field or property typeOutput.
A method, function, or constructor parameter typeInput.
A non-readonly field or property typeBoth, so only an invariant parameter may appear.
The target type of a mutable reference, including a ref parameter or a ref returnBoth, regardless of the enclosing polarity.
An argument to a covariant parameter of another typeThe enclosing polarity.
An argument to a contravariant parameter of another typeThe enclosing polarity inverted; both remains both.
An argument to an invariant parameter of another typeBoth.

Polarity composes recursively. Within a function type, its parameter types invert the enclosing polarity and its return type preserves it; a writable position imposes both. An occurrence refers to its declaring type parameter, so a nested declaration that binds the same name does not contribute an occurrence of the outer parameter.

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.

Note

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 1.2.3.1 forbids for a 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 contribution is subject to its primary's variance promises. Check added signatures using the primary parameter identities and the captures to which the contribution's pattern maps them, composing polarity through variant nested parameters. A method-local type parameter shadows the outer binding. A fixed closed argument is not an occurrence of an open variant parameter. Loading a partial never changes the primary's declared variance, identity or layout. A forbidden occurrence is the same Syntax Error as it would be in the primary declaration.

4.12.1 Static Semantics: Early Errors

TypeParameter : VarianceModifier BindingIdentifier TypeParameterHolesopt TypeParameterDomain TypeParameterConstraintopt TypeParameterDefaultopt Note

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 field is refused, since Table 41 gives that position both and only an invariant parameter may stand there. A parameter carrying no VarianceModifier is unconstrained by this rule, which is why invariance needs no check.

4.13 Higher-Kinded Parameters

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 (design, higherkindedtypes.md).

A higher-kinded parameter's arity is written and never inferred. Each hole in its own parameter list contributes one:

A parameter is higher-kinded when its BindingIdentifier is followed by a bracketed list of _ tokens, as W<_> for an [[Arity]] of 1 and W<_, _> for 2. The count of tokens is the [[Arity]].

Note 1

_ is the pattern wildcard of the pattern matching extension, and the reuse is positional in the way % already is: % is the remainder operator between two operands and the pipeline topic where an operand is expected, and here a type parameter list is a position where a pattern cannot appear. The token means a hole at both.

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 type error, and the message states the arity the parameter expects. Applied, it is an ordinary application: the parameter is resolved to whatever the enclosing application bound it to, and that is then applied to the given arguments, so 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 type error, at the declaration, for the application to supply another number, counting a spread argument by its statically known length, or to supply a TypeArgument of the named form, since the holes carry no names. The count is judged against the holes and not against a binding, which is what lets it be judged once: every declaration a binding may supply has exactly [[Arity]] parameters, so an application supplying [[Arity]] arguments reaches each of them, and after substitution it is an ordinary application whose arguments are checked against the bound declaration's domains and bounds as any other application's are.

Note 2

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.

Note 3

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 design that needed the exception would be one whose parameter is in the wrong place. Where both forms want names, the second is an alias rather than a second declaration — 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:

  • An argument that is not a generic declaration at all is a type error naming the argument and stating that the parameter expects a declaration taking arguments.
  • An argument that is a generic declaration of the wrong parameter count is a type error naming both counts.

Determine a written argument's role from the receiving parameter after spread expansion and argument assignment. In a higher-kinded slot, a bare generic name denotes the unapplied declaration, preserving its declaration identity and captured environment. It does not apply that declaration to an empty argument List, select its defaults, or expand its body merely to obtain the constructor. A selected higher-kinded default has the same declaration role, evaluated in its prescribed declaring context. Names in written arguments retain their caller context. These rules apply equally to local, imported, qualified and forwarded declarations; unresolved receiver information retains an application obligation rather than selecting an ordinary type role speculatively.

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.

Specialization (4.7) substitutes a bound declaration for the parameter and then applies it, so a specialized type contains no higher-kinded parameter and the resulting application is an ordinary one. Two applications binding different declarations are distinct types on the same terms as any other pair of applications, and interning follows from that with no rule of its own.

A where constraint (4.10) on a higher-kinded parameter constrains its application at a stated argument, as in where W.<T> is Iterable`. A constraint quantified over every argument the parameter could receive is a type error.

Note 4

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 (1.2.7.4): a budget of evaluation steps and type-construction requests bounds it, and exhausting the budget abandons the evaluation and reports. That bounds a computation, which either finishes or does not. A constraint quantified over every argument is not a computation but a search over the types that could be supplied, and truncating a search under a budget yields a WRONG answer rather than no answer — whether the constraint held would depend on how much budget remained when the search stopped. A rule whose truth varies with a host-defined limit is not a rule, so the form is refused rather than metered.

Declared variance (4.12) applies to a higher-kinded parameter on the same terms as to any other, meaning at the applied form what it means for a parameter of arity zero, and a parameter carrying no annotation is invariant. An application binding one declaration is therefore not a subtype of an application binding another, which is what keeps a synchronous iterator from being accepted where an asynchronous one is expected.

A higher-kinded parameter read as a value (4.1) is the unapplied declaration bound to it. Applying that value yields the same Type Object the corresponding type-position application yields, since both reach the same interned record.

A higher-kinded parameter is bound only by explicit application. It is never inferred from an argument's type.

Note 5

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.

5 Dependent Record Types

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 (Table 24) and the boundary checks of Table 23 are what this clause makes real.

5.1 The where Clause

The WhereClauses that 1.8.1 admits after a type alias's Type are given by:

WhereClauses[Yield, Await] : WhereClause[?Yield, ?Await] WhereClauses[?Yield, ?Await] WhereClause[?Yield, ?Await] WhereClause[Yield, Await] : where RefinementPredicate[?Yield, ?Await] RefinementPredicate[Yield, Await] : AssignmentExpression[+In, ?Yield, ?Await] if ( AssignmentExpression[+In, ?Yield, ?Await] ) { RefinementPredicate[?Yield, ?Await] } if ( AssignmentExpression[+In, ?Yield, ?Await] ) { RefinementPredicate[?Yield, ?Await] } else { RefinementPredicate[?Yield, ?Await] }

A RefinementPredicate is evaluated with this bound to the value under test, and it holds when ToBoolean of its result is true. A match expression is an AssignmentExpression and so is a predicate like any other, 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 exhaustive by construction (26.3.1), and an explicit default overrides the implicit one. The conditional form evaluates its AssignmentExpression and then the predicate of the branch taken, and holds vacuously where that expression is falsy and no else is present. It is an early error for a RefinementPredicate not to be compile-time evaluable (1.2.7.1) as a function of 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 Type Record { [[Kind]]: parameterized, [[Base]]: the Type Record T denotes, [[Metadata]]: OrdinaryObjectCreate with a where property whose value is P as a function of this }. Each further clause refines the type the clauses before it produced, so _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 meta type (6) claims the 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.

Note

The refinement is 1.5.20's, not a kind of its own, which is why nothing here restates layout, defaults, or the relations: a predicate changes no representation, and the branding rule of that clause is already the rule this one wants, that a Payment is usable wherever its bare object type is and that going the other way is a check. The design writes a postal code's pattern as string.<{ pattern: … }> in the same document that writes a payment's cross-field rule as where, and the two are one mechanism over two bases.

Because a predicate is a function, the structural equivalence of 1.2.7.2 compares it by SameValue, so a dependent record type's identity is its declaration's identity: two modules that write the same 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.

5.2 The is Operator

v is T is true when the value of v is of the type T denotes and false otherwise. It performs IsOfType (1.7.6.1) and never throws for a value the type rejects, which is the difference between it and the check a boundary performs: a boundary requires, and is asks. Its grammar is given with the conversion operator (1.4.5), whose precedence it shares; the pattern matching extension widens its right operand to a full pattern, of which a type is one form (26.4), changing nothing written before it.

Note

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 1.7.6.1 is the reading here too.

5.3 Discriminated where Chains

A RefinementPredicate is a discriminating chain in either of two forms. In the conditional form, when: every condition is an equality comparison, == or ===, between one member access on this and a compile-time constant, every condition tests the same member; that member's declared type is a union of the literal types of exactly such constants, or an enum over them; and the chain is total, ending in an 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 enum, every clause is unguarded with a pattern that is a constant of that type or an 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 shape assertion of the branch, this is T or this.p is T, applied by NarrowTo at the asserted path; a branch predicate that is not a shape assertion refines its member as a where of its own and contributes no members.

The denoted union serves the exhaustiveness check of 26.3.1 and nothing else: the dependent record type remains one parameterized Type Record, 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.

Note

The design's 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 design asserts rather than inventing one. The qualification is syntactic so that no predicate reasoning enters the checker; the alternative was a prover with a budget, and no other rule here asks for one (design, patternmatching.md).

5.4 Narrowing Through a Refinement

Where the Static Type of 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 AssignmentExpression, the branch so taken contributes its narrowing facts at that position, reading this as v: a branch predicate that is a narrowing form of Table 24 over a path rooted at this contributes what that form contributes over the corresponding path on 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 compile-time evaluable given them, reading this as v and reading a path whose narrowed type is a literal type as that type's [[Value]]. No other means of deciding it is specified: a predicate the facts leave open contributes nothing, and the boundary check stands.

Note

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 design contrasts its where against. The chain is short and each link is already specified: the literal-equality row of Table 24 gives addr.country the Static Type '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 design's own account of the case where a '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 (1.5.20), not narrowing's.

6 The Metadata Protocol

A metadata parameterization carries a value that 1.5.20 gives one default meaning: a brand, shed upward freely and crossed downward only at a construction boundary. This clause is what lets that value mean more. A meta type teaches the checker the semantics of the fields it claims, so 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.

6.1 Meta Declarations

The production below restates the Declaration of 1.8.1 and carries the declaration forms the extensions add (1.1).

Declaration[Yield, Await] : HoistableDeclaration[?Yield, ?Await, ~Default] ClassDeclaration[?Yield, ?Await, ~Default] LexicalDeclaration[+In, ?Yield, ?Await] TypeAliasDeclaration[?Yield, ?Await] MetaDeclaration[?Yield, ?Await] PrimitiveOperatorDeclaration[?Yield, ?Await] InterfaceDeclaration[?Yield, ?Await] EnumDeclaration[?Yield, ?Await] MetaDeclaration[Yield, Await] : meta [no LineTerminator here] TypeName[?Yield, ?Await] TypeParameters[?Yield, ?Await]opt { MetaHookList[?Yield, ?Await]opt } MetaHookList[Yield, Await] : MetaHook[?Yield, ?Await] MetaHookList[?Yield, ?Await] MetaHook[?Yield, ?Await] MetaHook[Yield, Await] : default = AssignmentExpression[+In, ?Yield, ?Await] ; MethodDefinition[?Yield, ?Await]

A MetaDeclaration names an object type, the meta type's constraint shape, and supplies hooks for it. The MethodDefinitions it may contain are exactly those named in Table 42; a method of any other name is an early error, as is a second declaration for one type, a missing default or subtype, a default whose value is not of the constraint shape, or a hook whose signature does not match the table.

Table 42: The Meta Protocol
HookRequiredMeaning
defaultYesThe unconstrained constraint: what a value carries where it has no field of this meta type.
subtype(sub, sup)YesWhether a value constrained by sub may be received where sup is required. This is the meta type's half of the metadata subtype judgment.
validate(value, constraint)NoWhether a value of the base satisfies constraint. This is the meta type's half of the validation judgment.
narrow(current, op, value)NoThe constraint that a comparison op against value establishes, given current (6.4).
conversionFactor(from, to)NoA finite real numeric scalar by which the value is scaled when its constraint is converted from from to to. Number, BigInt, sized integer, binary floating-point, decimal, and rational results are admitted. A rational expresses an exact unit ratio (6.3).
rescale(constraint, factor)NoThe constraint constraint denotes once its value has been multiplied by factor, so that a bound in metres survives a conversion into kilometres.
quantize(value, constraint)NoThe value that value becomes under the representation constraint requires, such as a decimal rounded onto a scale.
describe(constraint)NoA 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, null where the meta type can prove that nothing satisfies both, and undefined where it cannot decide. A meta type declaring no meet is read as declining, since the two answers mean the same thing to the caller: the intersection stands. null is reserved for a PROOF — a meta type constraining by pattern cannot generally decide whether two patterns share a string, and one forced to choose between a meet and an emptiness would have to answer one of them wrongly. A returned CONSTRAINT is the type: the intersection reduces to that parameterization (1.2.2.8), so uint8<{ bounds: 1..=10 }> & uint8<{ bounds: 5..=20 }> and uint8<{ bounds: 5..=10 }> are one type and one Type Object. A null is the type error of 1.4.2.1.

Every hook is required to be a pure function of its arguments, compile-time evaluable (1.2.7.1), and deterministic, the obligation 1.5.20 states of the judgments they compose. It is an early error for a hook not to be compile-time evaluable.

A type violation the checker can decide is likewise an EARLY ERROR, and the error is a StaticTypeError, whose prototype object inherits from %Error.prototype% and whose constructor is a global (1.3.11). It does not inherit from SyntaxError: the syntax of a program this rejects is well formed, and a host that catches a SyntaxError to mean "this source did not parse" must not be made to catch a type failure by doing so. Nor does it inherit from TypeError, which is reserved for the dynamic boundary; the two are disjoint, and a program wanting either phase tests both.

The distinction it draws is WHEN, and it is observable: a StaticTypeError is thrown before any of the Script or Module runs, and a TypeError is thrown by a boundary during evaluation. 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 early errors exist to prevent.

Note

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 metadata subtype judgment: the checking pass (1.2.8) invokes it to decide a pair of portions, the decision is a pure function of its interned operands and is remembered, and the run-time operations that reach the judgment — a conversion between two parameterizations, an any-typed value arriving at a parameterized position — consult the remembered relation, invoking the hook only for a pair no check has yet decided. A hook that read the clock or a global would make a program's types depend on when it ran, which 1.2.7.4 already forbids of everything else in type position.

meta is a contextual keyword under the restriction is takes (5.2) and for the same reason: meta T on one line is a Syntax Error today, and meta alone on a line is an expression statement that must stay one.

6.2 Decomposition and the Judgments

Before interning or comparing metadata, normalize every origin against the same instantiated constraint shape: the declaration's default, a written record, a builder result, a capture, and a reflected description. A written numeric literal leaf is read from its exact source digits in its declared numeric domain under literal representability rules; a runtime Number leaf denotes its actual Number value. Normalization never wraps an out-of-range integer or invokes a user conversion. An already typed numeric atom retains its domain and exact value. Normalize nested records and lists recursively, using their field, tuple-position, or element shapes; an optional numeric field supplies its numeric domain when present. A union with several numeric alternatives does not choose an alternative by order. Reject a completed claimed portion that does not satisfy its shape. Range endpoints use the declared endpoint-domain rule below. The unclaimed-key rule and base-form exception remain in force.

Normalization is idempotent and independent of origin. Equivalent metadata must have the same interning key, and reconstructing a reflected type must produce the same Type Object. A typed view of a capture preserves its declared field types. Numeric atoms use the domain's SameValue rule, not composite SameValueZero: neither negative zero nor the distinction between an explicit default-pinned requirement and a bare type is erased.

A metadata value is drawn from a closed language, given by Table 43. The top level is a flat record whose keys are claimed keys; the values under them may nest freely. Two metadata values are equivalent when they are equivalent by the comparison that table gives, and it is that comparison, and not object identity, that interning uses.

The outer value supplied for a metadata record or claimed portion must be a record input under the admission rules below. A list, range, pattern or scalar is not such a record, even when it has no enumerable properties or when the base carries a base-form meta declaration. Determine this outer kind before reading its entries or replacing it with a snapshot. Nested lists remain metadata values; their indexed traversal is unchanged. An ordinary record does not become a list merely because its prototype is an Array.

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 type error at the required source-checking boundary, including when another member remains dependent. An unresolved member retains its declaration and application dependencies until its value is available; no partial projection is a completed metadata record. An optional constraint member permits absence but does not admit a supplied undefined, and any in the constraint shape does not extend the metadata value language.

A metadata argument denoting a meta-type family denotes the registered constraint shape itself. Possession of one of that type's claimed keys, or gradual mutual assignability through any, does not establish this identity. Keep this family interpretation distinct from supplied nested values and from ordinary type arguments to a generic declaration. A completed supplied value undergoes the same admission rules whether written directly, reached through an alias, computed, captured or reflected.

Table 43: Metadata Values
FormEquivalence
A Number, a String, a Boolean, a BigInt, or nullSameValue, so a NaN is equivalent to a NaN and a negative zero is not equivalent to a positive zero, as literal types already compare.
A numeric value of a sized integer, binary floating-point, decimal, rational, or complex typeThe numeric domain and its SameValue relation, including that domain's rules for NaN, signed zero, and decimal cohorts. Its complete value is preserved, independently of the engine's physical encoding.
A pattern, carried as its source and its flagsIdentical source and identical flags.
A SymbolSameValue. A registered Symbol is therefore equivalent to itself in every agent; an unregistered one is equivalent only to itself.
A record of metadata valuesThe same keys, and equivalent values at each, without regard to order.
A list of metadata valuesThe same length, and equivalent values at each index, in order.
A range, of one of the four shapes of 21The same shape, the same bound at each endpoint the shape has, and SameValue at each endpoint's value.

Nothing else is a metadata value. A function, an object other than the forms above, and undefined are not: absence is the absence of a key rather than a key holding nothing.

Admission precedes any copying that could erase the input's kind. Recognize supported numeric atoms, patterns and ranges by their actual kind, not by public tag properties or a user coercion. A record input is an ordinary Object with ordinary property access, without construction identity, private elements or additional opaque payload slots. Its prototype contributes no fields. Functions, class instances, boxed primitives, collection objects, typed-array objects and Proxies are not record inputs, even after a prototype change. Snapshot all metadata origins under these same admission rules before their completed values are normalized, interned or published.

Composite record and tuple values and Span values are not metadata record or list inputs. Their admission does not depend on whether an implementation represents their special kind or backing state using object slots, value tags or side tables. Reject these kinds before entry reads, including empty values and values whose prototype has changed. A program may explicitly project contents to an ordinary record or Array before supplying metadata; that new input and every nested value still undergo the same admission checks. Typed owning Arrays remain list inputs.

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 Get once and complete the resulting value's snapshot before proceeding to the next key. For a list input, capture the Array's length once and perform Get at each index from zero through that length minus one, in order, completing each element before reading the next. Index enumerability does not change list membership; named properties do not become elements. An indexed read yielding undefined, including an unfilled hole, fails admission. An inherited indexed value is the result of the ordinary Get. Getter effects and abrupt completions obey the existing type-position fragment rules; validation must not add a separate preliminary traversal that duplicates these reads.

Record keys are String or Symbol property keys. Preserve their identity through every metadata origin, normalization, constraint-shape lookup, key claiming, default completion, capture, hook portion, computed result and reflected description. Distinct Symbols with the same description are distinct keys; no String spelling substitutes for a Symbol. Enumerable Symbol properties participate in record snapshotting in the own-key order described above, with the same reads and admission checks as String properties. Symbol properties of a list are not indexed elements. Record equivalence compares actual key sets and corresponding values without regard to insertion order. Canonical ordering distinguishes Symbol identities by the existing per-agent first-interning order, for both keys and Symbol values. Materialization exposes the original property keys.

A cycle along the active record/list containment path is not a metadata value. Repeated references to an acyclic child are distinct snapshot occurrences, with the prescribed reads performed for each occurrence. An implementation may use an explicit worklist rather than host recursion. Snapshotting joins the surrounding type-evaluation budget, or begins one where none is active, and charges one logical step for each value visit and each container advance or completion. Getter and helper work also pays its existing charges. An exhausted or failed snapshot produces no completed metadata value and does not publish a declaration; earlier ordinary effects and completed declarations are preserved as specified above.

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 Type Object identity.

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 expansion artifact, and would give interning no answer, which are the three reasons the language is closed at all. A meta type's constraint shape may still DECLARE such a field as a RangeBounds.<T>, since that is what a hook receives and what a hook returns; what this table closes is the set of values that may appear under it.

Note 1

Admission to the metadata value language does not bypass the declared field type. A Range.<T, S, E> field requires both endpoints at every origin, while a RangeBounds.<T> field admits all four range shapes. A written 0..<10 and a default .. have different shapes; comparing their admission does not test origin independence. The generic argument rules of 1.5.20 still apply: the required element type T is not supplied by writing a bare 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 Table 43. The range's ordinary ordering requirements still apply. A built-in range carrying a live class instance, container or unsupported object as an endpoint is not a metadata value, even when it is a valid ordinary range. This restriction does not change range admission outside metadata. Check the start endpoint before the end using internal endpoint data, without public property reads, coercion or ordering calls. Each present endpoint consumes a value-visit step, and completion of the range consumes a container-completion step under the surrounding snapshot budget. Publish the range snapshot only after both present endpoints pass; omitted endpoints remain absent. Written range projection applies the same atom admission before completing its metadata value.

Each present endpoint is a compile-time constant and is normalized against the endpoint type of the field's instantiated range constraint shape, using the same numeric-literal and runtime-value rules as other metadata leaves. The base supplies that endpoint type only through a declared meta parameter: a RangeBounds.<T> field uses the substituted T, while a RangeBounds.<uint16> field keeps uint16 endpoints on any host. A present optional range field supplies the same context. Where several union alternatives supply different possible range domains, do not choose by their order: there is no single contextual endpoint domain, and the supplied endpoints and range shape must satisfy one complete alternative. An unconstrained field supplies no host-derived endpoint domain. Already typed endpoints retain their domains and must satisfy the field type. A generic default is still evaluated and snapshotted once. An unbound endpoint parameter postpones its domain membership judgment until a concrete host is available; it does not establish completed membership. Normalize and check a completed default portion against the instantiated shape before a capture or hook reads it, and compare default participation against that same instantiated default, without reevaluating the default expression. SameValue retains domain distinctions and signed zero; omitted endpoints remain absent.

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 field on 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 meta type whose key means an unconstrained bound says so in its 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.

Note 2

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 expansion artifact (24) is a serialization of the interning table, so a form that cannot be written down and read back could not appear in one. And a hook receives the metadata, so every form must be presentable to a program.

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 agent that asks for the key, and is the form to reach for where two libraries must agree. A type whose metadata carries an unregistered Symbol cannot appear in an expansion artifact and loses nothing by it: an artifact carries the concrete types a module graph's public surface produces, and a Symbol deliberately not exported is not part of one. An artifact is an optimization and never a semantics, so a consumer evaluates such a type as it evaluates any other.

A MetaDeclaration may instead name a PRIMITIVE type rather than an object type, declaring a base-form meta type for it. Such a declaration has no constraint shape and so claims no keys: its hooks receive the WHOLE metadata of a parameterization of that base rather than a portion of it, and the judgments and conversions of this clause consult it in addition to the meta types that claim keys. Because it speaks for every key of such a parameterization, a metadata object whose base carries a base-form meta type is exempt from the unclaimed-key error below. This is how a brand is written: a meta type that constrains a base without naming any field of it.

A MetaDeclaration may declare ONE type parameter, written with its domain as every parameter is, meta NumberBounds<T: type extends Ordered.<T>>, which is bound to the base at each parameterization the meta type governs. It is the name of what the base is, and it exists because a constraint shape whose fields live in the base's own value space cannot otherwise mention it: the shape is a type of its own that other code may name, so it is parameterized like any other. The parameter is never written at a use — uint8.<{ bounds: 1..=6 }> binds it to uint8 because uint8 is what is being parameterized — and it is the constraint shape that fixes how tightly it must be bounded, a shape holding a RangeBounds.<T> requiring an T that is ordered. A meta type that needs the base only within a hook declares no parameter, since a hook already receives values of the base; a bound expressed in the base's values is what the parameter is for. It is an early error for a MetaDeclaration to declare more than one type parameter, or for its constraint shape to take a number of type parameters other than the number the declaration takes.

A meta type claims the property keys of its constraint shape. Claiming is global and flat, and is a property of the declaration rather than of an instantiation, so a parameterized meta type claims its keys once however many bases it governs: it is an early error, reported at the second MetaDeclaration rather than at any use, for two meta types to claim one key. A metadata object whose own key no meta type claims is a type error at the parameterization that writes it.

The unclaimed-key judgment applies at every completed parameterization construction, including a reflected description. A published base-form meta type supplies the stated exemption for both String and Symbol keys; an incomplete or failed declaration supplies none. A construction reached at runtime reports an ordinary type error at that boundary. This does not defer a failure already required at a source-checking boundary.

Declaration-time key claiming resolves a generic constraint shape in its declaring environment with its type parameters retained as binders. It does not apply the alias to fictitious arguments or require a self-dependent bound to hold of an unbound parameter. Key claiming does not discharge the constraints of a later application: the governing base must satisfy the applicable parameter constraints, and completed portions and defaults remain subject to their constraint-shape admission rules.

Prepare a meta declaration's owner, constraint shape, keys, default snapshot and hooks before publishing that declaration's protocol. Check owner uniqueness, parameter count and key conflicts, evaluate and normalize the default once, and check its membership and the required hooks. Publish only after these operations complete successfully. A failed or incomplete declaration supplies no registered owner, key claims, shape, default, hooks or base-form exemption. Previously completed declarations retain their registrations; a later declaration or source-check failure does not roll them back. Effects of ordinary declaration instantiation and evaluation retain their existing rules.

Conflict detection is distinct from protocol publication. Within one source check, an established owner and constraint shape already determine which later meta declarations would duplicate that owner or claim its keys, even if its default awaits permitted local initialization. Retain that information for checking subsequent declarations in source order, but do not expose it as a governing protocol or retain an incomplete declaration's reservation after the check ends. Revalidate ownership before publication if evaluating the default completed another declaration.

At the checking boundary, a completed declaration failure is a StaticTypeError before source-body entry. An actually unavailable input and a failed computation using available inputs are distinct. Only a still-uninitialized direct binding belonging to the source being checked can justify deferring a local initialization dependency under the existing preparation rules. An exception from within a helper's own temporal dead zone, a previously retained exception, or an exception with the same class or message does not establish such a dependency. An uninitialized import needed at the module's required checking boundary remains a failure under 1.3.8.

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 published declarations and actual property-key identities.

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.

6.2.1 MetadataPortion ( m, M )

The abstract operation MetadataPortion takes arguments m (an ECMAScript language value) and M (a meta type) and returns an Object. It returns the part of the metadata m that M governs. It performs the following steps when called:

  1. Let p be a copy of M's default.
  2. If m is an Object, then
    1. For each own property key k of m that M claims, set p's k to m's k.
  3. Return p.

Throughout this clause, a portion is M's default when the two are equivalent as Table 43 defines, not when they are the same object: MetadataPortion returns a fresh copy, so identity would make every meta type take part in every judgment and a brand refuse its own default.

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 meta type's metadata. This is distinct from the constraint denoted by a type. A bare base type leaves M unpinned and admits parameterizations of that base; an explicitly written default portion pins M to that requirement. The latter remains a distinct Type Object. Admitting a particular bare value at that requirement does not make the bare base a subtype of the requirement.

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-typed or any-typed binding.

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.

Note 3

Canonical metadata and the value presented to a program are separate views. A capture declared 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 constraint shape before identity is established: defaults, written records, builder results and captured records. In particular, erasing the numeric tag of a typed default alone does not establish this invariant for an integer represented by a BigInt. Range endpoints retain the declared endpoint-domain normalization specified above, and an untyped Number and BigInt are not made equivalent.

The metadata subtype judgment holds of two metadata values s and t when, for every meta type M declared in the surrounding realm for which MetadataPortion(s, M) or MetadataPortion(t, M) is not M's default, subtype of M holds of MetadataPortion(s, M) and MetadataPortion(t, M). The meta types declared in the surrounding realm are those declared as of the judgment's application: when the checking pass applies it, that is after the pass has processed the source text's own meta declarations (1.2.8) and after the evaluation of every source text evaluated before it.

The validation judgment holds of a value v and a metadata value m when, for every meta type M whose MetadataPortion(m, M) is not M's default, M defines validate and it holds of v and that portion. A meta type that constrains and defines no validate therefore admits no bare value of the base at an annotation - the construction boundary, the cast or the Type Object call, is the way in, as it is for a brand - which is what makes a brand a brand and what makes const v: Velocity = 10; a type error until 6.5 gives number a way in.

Note 4

Decomposition is why one flat object serves every meta type at once: float32.<{ m: 1, s: -2, bounds: 0.. }> is read by the dimensions meta type as an acceleration and by the bounds meta type as a non-negative number, each seeing its own keys and the defaults of the rest, and neither seeing the other's. It also fixes what an omitted key means, since a portion is completed from 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 meta type's default at once, which is what makes it dimensionless and unbounded rather than untyped. A meta type both of whose portions are its default takes no part in a judgment or a conversion, so declaring a meta type cannot change the relation between types that carry none of its metadata, and no hook is required to be reflexive at its own default.

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 meta type cannot be extended by a third party: an extension ships as its own meta type with its own keys, and the Symbol keys of 1.5.19 are what keep two libraries from colliding at all.

The where of a dependent record type (5), the brand, and the string pattern are meta types of this protocol declared by this specification rather than by a program, which is why nothing about them is special-cased above: a brand's 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 1.5.20 sanctions, and a where's validate runs its predicate while its subtype holds only where the required side is unconstrained or carries the same predicate.

6.3 Conversion at a Boundary

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 metadata record pins each meta type for which it writes a claimed key, including a portion explicitly equal to that meta type's default. Pinning is independent of whether the completed portion governs a value. Unwritten portions do not constrain an argument and do not contribute a conversion factor.

Two parameterizations of one base that differ in their metadata convert by this clause, as specified in Table 13, at every boundary of Table 23, an operator's argument among them.

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: IsAssignable, 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 owner's default instantiated for the shared base. Determine participating owners and destination quantizers in meta-declaration establishment order; metadata property order does not order them. Selected cast bodies instead retain their own declaration-establishment order. All admission judgments and capture projections use the original source and requested destination, independently of the advancing raw payload.

6.3.1 ConvertParameterization ( value, from, to )

The abstract operation ConvertParameterization takes arguments value (an ECMAScript language value), from (an ECMAScript language value), and to (an ECMAScript language value) and returns either a normal completion containing an ECMAScript language value or a throw completion. It converts value, whose metadata is from, to the metadata to. It performs the following steps when called:

  1. Let v be value.
  2. Let base be RuntimeTypeOf(value).
  3. If base.[[Kind]] is parameterized, set base to base.[[Base]].
  4. Let pinned be the meta types pinned by to, in their declaration-establishment order.
  5. Let participating be a new empty List.
  6. Let required be a new empty List.
  7. For each meta type M of pinned, do
    1. If MetadataPortion(from, M) or MetadataPortion(to, M) is not equivalent to M's default, append M to participating.
  8. For each meta type M of participating, do
    1. Let fp be MetadataPortion(from, M).
    2. Let tp be MetadataPortion(to, M).
    3. Let admits be ? the result of applying subtype of M to fp and tp.
    4. If admits is not true, then
      1. If fp is not equivalent to M's default, throw a TypeError exception whose message names M and, where M defines describe, its descriptions of fp and tp.
      2. Append M to required.
  9. If base has a base-form meta type, then
    1. Let admits be ? the result of applying its subtype hook to from and to.
    2. If admits is not true, throw a TypeError exception.
  10. Let casts be the distinct implicit cast declarations selected for required as described below, in their declaration-establishment order.
  11. If any required provider is missing or selection is ambiguous, throw a TypeError exception before invoking any cast body.
  12. If casts is not empty and operator-block re-entry is suppressed, throw a TypeError exception.
  13. For each declaration cast of casts, do
    1. Set v to ? the result of invoking cast on the raw value of v with its own capture bindings projected from the original source and requested destination (6.5).
    2. Set v to the result of admitting v at the actual destination base under the raw-body rule, keeping operator-block re-entry suppressed through the body and this admission. Propagate an abrupt completion before invoking a later body, and restore temporary invocation state on every exit.
    3. For each meta type M of required supplied by cast, do
      1. Let tp be MetadataPortion(to, M).
      2. If M defines validate, then
        1. Let valid be ? the result of applying that hook to v and tp.
        2. If valid is not true, throw a TypeError exception whose message names M and, where M defines describe, its description of tp.
  14. Let f be the exact rational number 1.
  15. For each meta type M of participating that defines conversionFactor, do
    1. Let factor be ? the result of applying that hook to MetadataPortion(from, M) and MetadataPortion(to, M).
    2. If factor is not a finite real numeric scalar, throw a TypeError naming M and conversionFactor.
    3. Set f to the exact product of f and the mathematical value of factor, retaining any zero sign separately for the base's multiplication rule.
  16. Set v to ? ScaleMetadataValue(v, f, base).
  17. For each meta type M declared in the surrounding realm, in declaration-establishment order, that defines quantize and whose MetadataPortion(to, M) is not equivalent to M's default, set v to ? the result of quantize of M applied to v and MetadataPortion(to, M).
  18. Carry each unpinned source portion as described below, and retain every pinned target portion.
  19. If any meta type governing the resulting metadata defines a validation hook that rejects the resulting value and carried portion after scaling and quantization, throw a TypeError exception.
  20. Return v carrying the resulting metadata.

For each required owner, select a provider by the coverage, specificity and ambiguity rules of 6.5, using the shared base's fixed primitive and then its family. A 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 owner admitted by subtype does not require or independently select a cast. If no owner requires one, the cast sequence is empty.

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 owner it supplies. A later body cannot repair an earlier failed stage; conversely, a successful stage cannot waive validation of the final value. A failure publishes no converted result and does not roll back effects already performed. Do not attach partial destination metadata between bodies.

Where the required metadata leaves a meta type unpinned, its source portion is preserved when f is one. Otherwise it is the normalized result of 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 constraint shape; a quantize result must belong to the numeric base. These hooks propagate abrupt completions. Validation and quantization receive the raw numeric value.

6.3.2 ScaleMetadataValue ( value, factor, base )

The abstract operation ScaleMetadataValue takes arguments value (an ECMAScript language value), factor (a rational number), and base (a Type Record) and returns either a normal completion containing an ECMAScript language value or a throw completion. It scales in the base's numeric domain, without invoking user-language mixed-type multiplication or an operator definition. It performs the following steps when called:

  1. If factor is 1, return value.
  2. If base is not a numeric primitive type, throw a TypeError exception.
  3. If value is finite and real, form the exact rational product of its value and factor.
  4. For a rational base, retain the exact product. For an integer base, truncate toward zero and apply the base's conversion overflow rule. For a binary floating-point or decimal base, round once to that base using its conversion rule.
  5. For a complex base, apply the corresponding real-component operation separately to its components.
  6. Preserve signed-zero and special-value behaviour under the base's multiplication rules. A special value of the input does not authorize a non-finite conversion factor.
  7. Return the resulting value of base.

Each meta type gates the crossing independently, and there are exactly two ways through: subtype admits it, or the value carries nothing of that meta type and a cast supplies what it lacks. 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 design says that without a cast on number the assignment is a type error, and why a brand, for which no cast is declared, is reachable only by construction. 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 meta type checks it.

The rest runs in the order above: the factor once, as the product over every meta type that contributes one; 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 1.7.9. Constant operands alone do not authorize executing an ordinary cast body during source checking or removing its effects.

Note

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 unit system, which is the point of doing all of this before the body runs. Had the bound not been rescaled, a sum of bounds would have added a metre-space number to a kilometre-space one and produced a bound that means nothing.

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 design leaves an order: sequential application would make the result depend on the order meta types were declared in whenever a rescale is not linear, and an order-dependent type is the one thing the determinism invariant cannot have. Only the dimensions meta type contributes a factor in the design's own examples, so the two readings agree there and differ only where a second one appears.

6.4 Narrowing a Constraint

A comparison against a compile-time constant narrows the metadata of a parameterized value, as Table 24 records. The metadata it narrows to is NarrowMetadata(m, op, c), where m is the value's metadata, op the comparison, and c the constant: the metadata whose portion for each meta type M defining narrow is the result of narrow of M applied to MetadataPortion(m, M), the String naming op, and c, and whose portion for each other meta type is unchanged. The false branch narrows by the negation of op, pairing >= with <, > with <=, and == with !=, in both directions.

Note

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 meta type that defines no narrow learns nothing from a comparison and keeps the constraint it had, which costs a check at the next boundary and nothing else.

6.5 Operators on a Parameterized Primitive

A declaration of the form primitive T P { … }, where T names a primitive type and P is up to two specialization lists (1.4.3.2), declares operators on T for each parameterization its parameters admit, and is the form that 1.9.9 allows an extension to supply. Its grammar, and that of the operator definitions within it, is 18's, which declares the same definitions on a class. What this clause fixes is what those definitions mean for metadata, which is this specification's to say because the metadata is.

Each list of P is decided as a TypeArguments following a type is (1.4.2.2), by the type it follows: where that type declares type parameters the list matches them, the primitive's COMPONENTS; otherwise it is a pattern over METADATA, whose captures each bind the portion of the receiver's metadata claimed by the meta type its written domain names. 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 capture that takes its slot's domain, as the type 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 parameter list.

These captures use MetadataPortion, including its default when the receiver carries none. An operator's own metadata parameters bind from its operand. A capture in a cast's destination is bound from the requested destination, not from the source's default. The projection rule is shared by specialization lists; repeating a capture compares completed portions, while admitting an operator argument uses the metadata subtype and conversion judgments for its pinned portions.

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 Static Type is not proof of a plain runtime value: the binding may retain metadata admitted upward from a parameterization. The checker must not substitute defaults for these unknown portions. Where applicable definitions or their computed result metadata depend on such portions, the result is widened to a type admitting every possible result and the required boundary checks remain. The runtime fast path above is applicable statically only with a proof about the values, not from their bare annotations alone.

The checking pass distinguishes an exact known portion, a known plain value whose projection is the default, an unknown portion, and a pattern that does not match. Unknown is not the default and is not failure. Its required local proof rules are: a numeric literal proves a plain value of its contextual base; parentheses preserve a fact; a conversion to a base proves plainness only from a proven plain numeric source; a parameterization supplies its written pinned portions but proves nothing about unpinned portions; ordinary unary and same-base plain arithmetic preserve plainness. A direct local initializer may copy these facts to a binding, and a direct assignment replaces its mutable fact. Calls, borrowing or possible aliasing, updates, control-flow boundaries, and entry to or exit from a nested function discard mutable facts. No fact is inferred from a bare parameter annotation or from an unknown property read. The proof walk is bounded to twenty nested expression steps, after which the fact is unknown.

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 path when a plain pair remains possible; an unknown return requires an unknown result, rather than assuming the receiver's base. These rules determine the checking pass's baseline acceptance. Further optimization may elide checks already implied by that judgment; it may not introduce additional Early Errors.

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-captured preference applies. For different parameterizations that mutually admit one another and are otherwise tied, an exact match of every pinned portion beats one requiring a metadata crossing. Exactness compares descriptors, never the numeric payload or a computed factor; zero values and factor-one conversions do not affect ranking. Incomparable requirements and remaining ties are ambiguous.

Convert the selected operand once before evaluating any metadata-only return builder. Rebind fresh operand captures from this effective operand, then check contribution applicability without selecting a different value body. With no value body, compatible metadata-only requirements combine into one conversion target; conflicting pinned portions are ambiguous. Independently captured portions already equal to their required portions impose no conversion. A result builder observes the converted and rescaled portions. Compare the completed, normalized portions from every applicable contribution, including the selected value definition and all definitions without a body. Equivalent repeated contributions are permitted; conflicting contributions to one result portion are a type error, independent of declaration order. The same rule applies to vector lane metadata. Detect a conflict before invoking the selected value body or storing a compound result. The raw value body receives this same effective operand with metadata removed.

Note 1

The rule is the one a type position already applies, so a block's header reads as the type it covers. A component slot is not a metadata position, so a capture naming a meta type there, primitive complex<const T: P>, restates the component's domain type as P and is refused by 1.4.3.2.3; its diagnostic offers 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 capture (1.4.3.2), and a definition of the more specific block is chosen over one of the less specific whatever their order. Among the definitions of the most specific block that has a matching one, the definition with a body whose operand type is the most specific of those that match is chosen, whatever their order: one whose operand type is a subtype of every other matching one's, where of two equal operand types the one written without the block's captures, 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 TypeError, as a call no overload is most specific for is refused; any number of definitions without a body may match, each contributing its own meta type's portion of the result through its return type; where no definition with a body matches, the primitive operation runs; and the portions the matching return types evaluate to are merged into one flat metadata object, each meta type contributing its 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 undefined on normal completion without a return, against that raw result type by the ordinary return checking rules. An unknown metadata capture does not obscure an independently known base. A statically decidable failure is an Early Error even if the operator is never invoked; unresolved obligations remain runtime checks.

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 checking pass does not execute ordinary value bodies or initializers to supply them. Only the selected value definition and applicable bodyless contributions require their result computations, under the existing evaluation budget.

After the selected value body completes normally, apply RequireType to its raw result and the raw result type of its resolved declared return. Perform this admission with operator-block re-entry still suppressed and the selected capture bindings still active. Use the declaration's captured environment, not same-spelled bindings at the invocation. Only after successful admission attach the already-composed result metadata. An abrupt body or admission completion propagates without publishing a result; restore the capture and operator-body state on every completion. Effects already performed by the body are not rolled back. An annotation such as any or a union describes admissible results; it does not replace the admitted value's primitive representation with an abstract contract tag. A definition without a declared result does not acquire the receiver's base as a return obligation.

A String receiver consults its primitive string definitions before the built-in fallback, including when its representation carries a literal or refined String type. Literal refinement alone does not change the underlying primitive for protection of a pair of plain values: a broad operand definition cannot replace ordinary String/String operations merely because either value carries a literal type. Actual metadata parameterizations retain their dispatch eligibility. Boxed Strings remain Objects; do not perform ToPrimitive to discover a String receiver for block lookup. The raw-body and result-composition rules apply to parameterized String values too: remove operand metadata for the body and attach the merged declared metadata to its String result.

A registered block that may apply prevents a checking pass from proving the built-in fallback solely from the absence of locally reconstructed declaration syntax. Recover a signature in its declaration and captured environment, including across source texts; where that proof remains unresolved, retain an unknown result and the required runtime result checks. A dynamically constructed function's own declarations participate in the source judgment as ordinary source declarations do.

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 captured enclosing generic substitutions. This applies to definitions with and without a value body. Instantiate the definition's component and metadata captures from the established operand inputs. Resolve remaining type syntax in that declaration context, not in same-spelled consumer bindings or an unrelated active generic frame. Restore the consumer's evaluation context on normal and abrupt completion.

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-contribution rules above.

A required dependent result whose inputs are established is evaluated by the existing pre-evaluation fragment in the captured declaration context with those inputs substituted. Do not replay an earlier source or execute the ordinary operator body to recover its signature. Keep distinct queries for distinct consumer sites, declaring definitions, captured contexts or determining substitutions; reusing a completed result requires all its determining inputs to agree. Existing fragment resource requirements apply. Failure to evaluate a required type and an assignability failure established by the resulting type are distinct failures, both before consumer body entry. Where required inputs remain unresolved, preserve the existing deferred judgment and runtime checks. This signature recovery supplies neither reference permission nor proof of reference liveness.

Two definitions declared for one operand are found at their declarations: it is an early error for two blocks of one level of specificity to define one operator with operand types that are the same, where a capture of either header is read by the role it plays, the component position it stands for or the meta type it captures, rather than by its name. So 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 18 that an operator a type already defines may not be redeclared for it refuses a definition with a body whose operand type is the receiver's own type without metadata: primitive uint8 { operator +(rhs: uint8) … } redeclares uint8 addition and is an early error. Over a primitive that declares type parameters, a block covers every member, so an operand of the same family, 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 metadata subtype judgment, and the metadata of every other meta type it carries is not the block's to judge. Any required conversion is performed after selection and before the raw body; this is argument admission, not the equality test for repeating a capture in a structural pattern. A block for one meta type therefore applies to a value that also carries another, and the result's metadata is merged per meta type as above, the portions the matching definitions give and the default of each meta type none mentions.

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 Table 13 from the bare primitive into a parameterization. At a boundary, one is invoked for each meta type the required metadata constrains and the supplied value's does not, and its absence is why such a boundary is otherwise a type error: const v: Velocity = 10; compiles exactly where number declares a cast into the dimensions meta type, and a bare number reaches a bounded type only through a cast whose boundary runs validate.

For an implicit crossing from a bare numeric value, determine complete cast coverage before invoking a cast body. Each meta type whose completed destination portion differs from its default requires a selected provider. An exact-target declaration covers that entire destination, and a meta-type-wide declaration covers only its owning meta type; an exact declaration for a different destination does not cover extra portions. Unclaimed metadata admitted by a base-form meta type requires an exact-target declaration. A portion equal to its completed default requires no per-meta-type cast; the existing exact-target route remains available.

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 type error before any cast body runs, at the checking boundary where established and otherwise at the runtime boundary. Preparing the same declaration in the same lexical environment again does not create another candidate or change its original order.

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. Capture projection retains the original crossing's source and requested destination, independently of this advancing payload. Keep operator-block re-entry suppressed during each body and its raw-result admission. After all bodies complete, attach the destination metadata and perform every validation required by the receiving boundary on the final value. An abrupt body, raw admission or validation completion prevents publication; do not run later bodies after an abrupt body or admission completion. Restore temporary capture and operator-body state on every completion, without rolling back effects already performed. This bare crossing does not replace the subtype, factor, quantization or source-portion transport rules of 6.3.1.

For a source already parameterized on the same numeric base, 6.3.1 determines which owners require casts before using these provider-selection, invocation and capture rules. Its additional cast-stage validation, factor, quantization and source-portion transport rules still apply.

Before invoking a selected implicit cast, establish its own binding instance for each header capture. A capture referenced by the cast's destination type obtains its argument from the requested destination: a component capture reads the corresponding destination component argument, and a metadata capture reads MetadataPortion for its owning meta type, including completed defaults and the owning shape at that base. A header capture not referenced by the destination retains its receiver projection. Retain the capture declaration and its domain context; a same-spelled caller binding or another invocation supplies no argument. Read these arguments through the prescribed type or value views, without re-evaluating a producer or performing an ordinary getter read to recover them. Closures retain the binding instance of their creating invocation. Restore the surrounding argument context on normal and abrupt completion. These bindings are read-only and create no ordinary storage location, reference permission or proof of reference liveness.

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 capture is bound, for each invocation, from the receiver's own argument at that position, as its metadata captures are bound from the receiver's metadata: in 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 (1.4.2.2), so where the primitive declares parameters the name supplies them before its metadata: complex.<T> names a complex whose component is T, never a metadata parameterization. A capture of the block's header in a cast's name stands for every value of its domain, so the name describes a family of parameterizations and the cast covers each of them: in 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.

Note 2

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 meta type therefore still refuses an out-of-range value a cast produced, which is why const v: Velocity = 10; and a bounded type behave differently though both cross through one.

Note 3

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 6.3 happens before the body and not within it: a body sees operands already converted, quantized, and in one unit system, and needs to know nothing about metadata to be correct.

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 meta type that has nothing to say about an operator says nothing, and its portion of that result is its default, which is the correct answer for a constraint that the operation does not preserve.

7 Declaration Reflection and Decorators

The reflection of 1.12.4 reads a type. This clause reads a declaration: the fields a class declares, the parameters a function takes, the enumerators an enum lists, and what a decorator has recorded about any of them. The two are one facility under one name, selected by a first type argument, and they answer different questions: a type is shared by everything of that shape, and a declaration is the one place a program wrote it down.

7.1 Token Records

A Token Record is one token of source, as the lexical grammar produced it. It has the fields of Table 44.

Tokens are what a decorator reads of the syntax it decorates, and what a replacement decorator returns. They are deliberately below a syntax tree: the lexical grammar of this specification is already normative and changes far more slowly than the syntactic grammar, so a token vocabulary is a surface that can be exposed without inventing one.

Table 44: Token Record Fields
FieldValueMeaning
[[Kind]]identifier, punctuator, numeric, string, template, regexp, or groupWhich lexical form the token is.
[[Value]]a StringThe token's source text. For a group, the opening delimiter.
[[Span]]a Span RecordWhere the token came from.
[[Tokens]]a List of Token Records or emptyFor a group, the tokens it delimits. empty otherwise.
[[LineTerminatorBefore]]a BooleanWhether a LineTerminator preceded the token in its source.
Note 1

[[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.

Note 2

regexp and punctuator are distinct kinds because the lexical grammar of this specification is not context-free: InputElementDiv and InputElementRegExp resolve / differently, so a token stream is parse-informed rather than purely lexical. A token a replacement decorator PRODUCES must therefore say which it means, and the kind carries it — the ambiguity is resolved by construction rather than by re-scanning the output.

Note 3

Delimited runs are grouped rather than flat, so a macro cannot produce unbalanced output: the delimiters of a group are the record's, not two tokens a producer must remember to pair. The engine already matches delimiters to find a decoration's boundary, so the grouping costs nothing it was not doing.

A Span Record says where a token came from. It has the fields of Table 45.

Table 45: Span Record Fields
FieldValueMeaning
[[Source]]a Source Reference RecordWhich buffer the token's text is in.
[[Start]]a non-negative integerThe index of the token's first code unit in that buffer.
[[End]]a non-negative integerThe index after the token's last code unit.

A Source Reference Record names a buffer. It has the fields of Table 46.

Table 46: Source Reference Record Fields
FieldValueMeaning
[[URL]]a String or emptyThe module's URL, for a token from written source.
[[Macro]]a String or emptyThe replacement decorator that produced the token, or empty for one that was written.
[[Generation]]a non-negative integer0 for written source; n for the nth expansion that produced the token.
Note 4

A Span Record is not a source map. It says where ONE token came from, and it travels with that token wherever a replacement decorator moves it, so a chain of expansions needs no composition step: a token copied by one decorator and relocated by another still names the position a program was written at. The alternative — an offset map per expansion, composed across a chain — is where source-map correctness usually fails, and it fails silently.

7.2 TokensOf ( node )

The abstract operation TokensOf takes argument node (a Parse Node) and returns a List of Token Records. It returns the tokens of node's source text, with each token's [[Span]] naming the position it was written at. Delimited runs are returned as group tokens rather than as separate delimiters. It performs the following steps when called:

  1. Let text be the source text matched by node.
  2. Let source be the Source Reference Record for the buffer containing text.
  3. Let tokens be a new empty List.
  4. For each token t matched by the lexical grammar over text, in order, do
    1. Let span be a Span Record with [[Source]] source, [[Start]] the index of t's first code unit in that buffer, and [[End]] the index after t's last code unit.
    2. If t opens a run delimited by {, [, or (, then
      1. Let inner be the result of performing this operation over the tokens the run delimits.
      2. Append a Token Record with [[Kind]] group, [[Value]] t's source text, [[Span]] span, and [[Tokens]] inner to tokens.
      3. Advance past the run.
    3. Else,
      1. Append a Token Record with [[Kind]] the lexical form of t, [[Value]] t's source text, [[Span]] span, and [[Tokens]] empty to tokens.
  5. Return tokens.
Note

The lexical goal symbol at each position is the one the enclosing parse used, so / is already resolved to punctuator or regexp and no ambiguity reaches the caller.

7.3 TokenStream Objects

A TokenStream is an Array exotic object whose elements are Token Records exposed as objects, and whose prototype is %TokenStream.prototype%. It is what a reflection's syntax-valued fields hold and what a replacement decorator exchanges.

7.3.1 TokenStream.prototype.toString ( )

  1. Let stream be the this value.
  2. Return the source text the tokens of stream were taken from, in order, including the trivia between adjacent tokens of one buffer.
Note

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 field exists on a reflection — a second field would be a second way to say one thing, and the two would have to agree forever.

%TokenStream% is the intrinsic object holding the operation below; %TokenStream.prototype% is the prototype of every TokenStream.

7.3.2 TokenStream.prototype.parse ( start, end, goal )

Parses a range of this stream's source text and answers its tokens.

  1. Let stream be the this value.
  2. If stream is not a TokenStream, throw a TypeError exception.
  3. Let source be the [[Source]] of the [[Span]] of the first element of stream's [[TokenRecords]]; if there is none, throw a TypeError exception.
  4. Let text be source.[[Text]].
  5. Let from be ? ToIntegerOrInfinity(start).
  6. Let to be ? ToIntegerOrInfinity(end).
  7. If from < 0, to > the length of text, or from > to, throw a RangeError exception.
  8. If goal is neither "expression" nor "statements", throw a TypeError exception.
  9. Let parsed be ParseText(the substring of text from from to to, AssignmentExpression if goal is "expression" and StatementList otherwise), with the [Yield], [Await], and [Return] parameters and the strictness of the region this stream came from.
  10. If parsed is a List of errors, throw a SyntaxError exception whose position is from within source.
  11. Return a TokenStream over TokensOf(parsed).
Note

This is what lets a replacement decorator define a syntax the implementation does not know. A macro captures its region, reads whatever it likes, and delegates the ranges that are ECMAScript - and what it gets back is threaded from THAT parse, so a RegularExpressionLiteral is one token and a TemplateLiteral is one token.

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 synchronous function.

"module" is not a goal: a range of a region is never one, and admitting it would put an ImportDeclaration inside a macro's region.

7.3.3 TokenStream.gensym ( hint )

The abstract operation TokenStream.gensym takes argument hint (a String) and returns a Token Record. It returns an identifier Token Record that cannot collide with any identifier in any source text or in any other stream. It performs the following steps when called:

  1. Return a Token Record with [[Kind]] identifier, a [[Value]] derived from hint that is distinct from every identifier produced by any other call and from every IdentifierName in any source text, and a [[Span]] whose [[Source]] names the caller.
Note

This is the hygiene mechanism. A replacement decorator uses it for every identifier it INTRODUCES, so a macro's temporaries cannot capture a binding at the position it is spliced into. It does not address a macro's REFERENCE to an identifier it did not create — a macro naming 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.

7.4 Preprocessor Modules

"preprocessor" is a supported import attribute key: 16.2.1.12.1 returns a List containing it. A module imported with an ImportAttribute Record whose [[Key]] is "preprocessor" and whose [[Value]] is "true" is a preprocessor module. Its exports may be used as replacement decorators in the importing module.

A decoration whose IdentifierReference is a name a preprocessor import bound, and which is followed by {, 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.

Note 1

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 replacement decorator's region is captured, always, because it is a replacement decorator: what such a decorator RECEIVES is a TokenStream (7.9), and the text of what it decorates is the only thing that can be. Capture is not a mode a macro selects; it follows from what a replacement decorator is. A decoration whose imported binding is NOT one — a function that does not take a TokenStream and return a token sequence — takes a Block, parsed, and its tokens are those of that parse. Its extent is found by matching delimiters, and its text reaches the decorator as tokens of the ordinary lexical grammar. A macro that wants a range of its region READ AS ECMASCRIPT asks for it, through TokenStream.prototype.parse (7.3), which is how a macro delegates the parts of its own syntax that are ECMAScript.

Note 2
Note 3

The declaration is what is read, rather than a property set on the exported value. A macro that takes a TokenStream and returns a token sequence has SAID what it is, and its region's text is what such a decorator receives — so the signature already carries the answer, and a marker beside it could only agree or disagree with what the program wrote. The same reasoning decides which names a preprocessor import contributes (7.6.1): read the declaration, not an artifact of running it.

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 preprocessor module before parsing the module that imports it, so nothing turns on this today, but the rule no longer depends on it.

A macro that annotates no context takes a parsed Block, which is what a macro declaring no 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 7.3.2. That is what lets a variant of such a language be written without the implementation changing, which naming a grammar could never do.

A preprocessor module is fetched and evaluated before the importing module is parsed. The loader blocks on it as it blocks for a module with top-level await; the preprocessor module itself cannot await, because 1.2.7 gives it nothing asynchronous to name.

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 "default" — a default import binds a name like any other, and the export it binds is the one a decoration spelled with that name resolves to. A NAMESPACE import and a bare ModuleSpecifier introduce no such name: the first binds one name to every export rather than to any one of them, and the second binds none at all, so a preprocessor module imported either of those ways provides no replacement decorators.

The load is synchronous: an implementation completes it before the parse that is waiting on it, and a host that cannot answer in time is reported at the import rather than waited for. The specification says the loader blocks; what a host unable to block is owed is this.

Note 4

Blocking here does not require asynchrony. The loading operations call the host and the host calls back, re-entering the graph - a callback arrangement rather than an asynchronous one - so a host that answers before returning resolves the whole graph in time.

A preprocessor module may itself import preprocessor modules, and they are loaded and evaluated before it is parsed, recursively. A preprocessor cycle - a preprocessor module that imports, directly or transitively, the module importing it - is a Syntax Error, reported at the import that closes the cycle.

Note 5

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 shape for the same reason.

A preprocessor module is evaluated in the realm of the module importing it.

Note 6

Expansion is per-realm, a constant expression's cache is keyed per-realm (40), and Type Objects are interned per-realm. Evaluating a preprocessor elsewhere would put half of that somewhere else, and two realms importing one preprocessor would share a macro object across them.

A preprocessor module and every export of it used as a replacement decorator must be compile-time evaluable. It is a Syntax Error otherwise, reported at the declaration that is not evaluable.

Note 7

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 realm. Expansion is therefore a pure function of its input, which is what lets an implementation cache an expansion beside the code it compiled to, and what lets a build tool perform an expansion ahead of time and get the answer an implementation would.

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.

Note 8

Local mutation is evaluable — a Set of seen keys, an accumulator — and shared module-level mutable state is not, which is the same line 1.2.7 already draws for type builders and where clauses. A replacement decorator and a type builder are both user code an implementation runs while compiling, and they are one discipline.

7.5 Captured Regions

A captured region is the region a decoration on a REPLACEMENT decorator takes (7.4). The region begins at the { that follows the decoration, optionally preceded by do, and ends at the matching } as 7.5.1 finds it. Its contents are not parsed; its text reaches the replacement decorator as tokens of the ordinary lexical grammar, and the decorator reads it however its own syntax requires.

Note 1

7.5.1 counts brackets it does not interpret, so a captured region's text must BALANCE its brackets outside comments and literals. @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 region, so @m do { ... } and @m { ... } deliver the same tokens. Both spellings are available wherever the other is.

Note 2

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 region, so the decorated object literal form - @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 region is read otherwise: a capture governs the region, not the decoration.

A captured region is never evaluated. It is replaced by whatever its decorator returns, and what a decorator returns is ECMAScript however its region was scanned: a capture governs ingestion only. The grammar outside a captured region is unaffected, so a < b and a type argument list are read as they always were.

Note 3

Without this a region must be lexable as ECMAScript to reach a decorator at all, which forecloses most of what a syntax replacement is for. < 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>/.

7.6 settles the outgoing direction of that ambiguity, by having a decorator return a token whose kind resolves it. The incoming direction is what a capture settles, and the two are not the same problem: outgoing, the engine is told; incoming, it must decide before it has anything to be told by.

Because a captured region is found rather than tokenized, an implementation records its source range and the tokens the region produced. Expansion replaces a decoration's target by source range (7.7), so a region that is scanned but not recorded cannot be replaced.

A captured region's text reaches the replacement decorator as written. No entity is decoded and no whitespace is normalized, because both are conventions of the embedded language rather than facts about it: a whitespace run that one dialect drops another preserves, and two implementations of the same dialect ship different entity tables. A decorator applies whichever its language defines.

7.5.1 ScanBalancedRun ( source, start )

The abstract operation ScanBalancedRun takes arguments source (a String) and start (a non-negative integer) and returns an integer or unbalanced. It finds the end of a delimited run whose contents it does not interpret. It is used to find the extent of a captured region, whose contents are not ECMAScript and so cannot be found by scanning tokens. It performs the following steps when called:

  1. Assert: The code unit at start within source is one of (, [, or {.
  2. Let stack be a new empty List.
  3. Let index be start.
  4. Repeat, while index < the length of source,
    1. Let c be the code unit at index within source.
    2. If c begins a single-line comment, a multi-line comment, a string literal, or a template literal, then
      1. Set index to the index just past that comment or literal. Within a template literal, each substitution is itself a delimited run and balances.
      2. If no such end exists, return unbalanced.
    3. Else if c is one of (, [, or {, then
      1. Append the matching close delimiter to stack.
      2. Set index to index + 1.
    4. Else if c is one of ), ], or }, then
      1. If stack is empty or the last element of stack is not c, return unbalanced.
      2. Remove the last element of stack.
      3. Set index to index + 1.
      4. If stack is empty, return index.
    5. Else,
      1. Set index to index + 1.
  5. Return unbalanced.
Note

Recognising comments and string forms is not optional: a } within one is not a delimiter, and counting braces without them ends a region in the wrong place.

A regular expression is deliberately not recognised, and this is not an omission. Within a captured region the contents are not ECMAScript, so / 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 region the opposite holds, which is why a </{/ scans a regular expression and swallows the brace. No single scanner serves both readings, which is the reason a region declares its mode rather than being guessed at.

7.6 Replacement Decorators

A replacement decorator is a decorator whose name was introduced by a preprocessor module's import clause. It receives the tokens of what it decorates and returns tokens that replace them, before the importing module is parsed.

The name is decided syntactically

A decoration names a replacement decorator if and only if its IdentifierReference is an element of ReplacementDecoratorNames of the Module.

7.6.1 Static Semantics: ReplacementDecoratorNames

The syntax-directed operation ReplacementDecoratorNames takes no arguments and returns a List of Strings. It returns the names a module's preprocessor imports introduce. It is computed from the ImportDeclarations alone, so it is available before the module is expanded and cannot be changed by expanding it.

  1. Let names be a new empty List.
  2. For each ImportDeclaration d of the ModuleItemList of this Module, do
    1. If d has a WithClause with an ImportAttribute Record whose [[Key]] is "preprocessor" and whose [[Value]] is "true", then
      1. If d has an ImportedDefaultBinding, append the StringValue of that ImportedDefaultBinding to names.
      2. For each ImportSpecifier i of d's NamedImports, append the StringValue of i's ImportedBinding to names.
  3. Return names.
Note

A namespace import and a bare ModuleSpecifier do not contribute: the first introduces only a name reached through a member access, which is not an IdentifierReference and so cannot appear in a decoration at all, and the second introduces no name. A DEFAULT import does contribute, because it introduces exactly the kind of name a decoration is spelled with. import jsx from "./jsx.js" with { preprocessor: "true" }; followed by @jsx { ... } is the shape a module with one macro takes, and it is the common one - a preprocessor module usually provides a single replacement decorator, which is what a default export is for.

Static Semantics: Early Errors

Module : ModuleBodyopt Note 1

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.

Note 2

The rule is syntactic rather than scoped because a scope query would be circular: a replacement decorator may introduce declarations, so the scope to resolve against is not complete until expansion has finished, which is the thing the query would decide. Fixing the names by inspection breaks the cycle, and the cost is that a replacement decorator cannot be aliased or locally rebound.

Note 3

A consequence for the decorator's own output: a replacement decorator cannot emit a call to the name it is spelled with. A preprocessor import is an ordinary import declaration carrying an attribute, so the name it introduces is a live binding whose value is the macro, and the first rule above forbids the module binding it to anything else. Emitted code naming it therefore calls the MACRO at run time, and no runtime helper can take the name back - a global of that name is shadowed by the import as it would be by any other. A macro pairing itself with a runtime dispatch gives the two different names, which is what React's automatic runtime does from the other direction when it imports its dispatch as _jsx.

Placement

Note 4

Replacement decorators are written outermost because they run first, so source order agrees with execution order between the two kinds. It also gives them the capability the arrangement exists for: a replacement encloses the runtime decorations, so it can rewrite them, or remove them together with what it replaces.

Arguments

The arguments of a replacement decorator are Token Records and are not evaluated. @f(0) passes a numeric token and @f(Serialize) passes an identifier token.

Note 5

This is an exception to the rule that decorator expressions are evaluated in document order (7.16): a replacement decorator runs before the module is parsed, so there is no scope to evaluate an argument against.

One replacement decorator per name

A name denotes one REPLACEMENT decorator. Selection among replacement decorators is not overload resolution: the arguments of every one of them are Token Records, so there is nothing for a type-directed rule to select on.

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 replacement decorator is the overload that takes a TokenStream and returns a token sequence (7.4), and an ordinary decorator is any other. Where a name carries both, the replacement runs at expansion and the ordinary one at decoration, in that order, and a decoration whose position the ordinary overload does not accept is a type error at that decoration. One name may therefore transform a construct and then decorate what it produced.

Note 6

Overload resolution selects on argument types, and the arguments of a replacement decorator are all Token Records. There is nothing for a type-directed rule to select on, and a macro that wishes to distinguish its arguments inspects them.

Frequency

A replacement decorator runs once for each decoration, at expansion. This includes a decoration on a block, which a runtime block decorator would run on every entry to that block.

Note 7

A replacement block decorator rewrites the block once and may emit whatever per-entry work is wanted inline, so the work happens per entry where the decorator does not. The per-iteration cost a runtime block decorator carries does not arise.

7.7 Expansion

Expansion is the phase in which replacement decorators run. It occurs after a module's source text has been scanned far enough to find its decoration boundaries, and before the module is parsed and checked.

  1. Let names be the replacement decorator names of the module (7.6).
  2. Let depth be 0.
  3. Repeat, while some decoration in the module names an element of names,
    1. If depth > the implementation's expansion limit, throw a SyntaxError exception naming the decoration and depth.
    2. Let d be the outermost such decoration in source order.
    3. Let result be ? ApplyReplacementDecorator(d).
    4. Replace the tokens d decorated, and d itself, with result.
    5. Set depth to depth + 1.
  4. Return the module's tokens.
Note 1

Expansion is a fixpoint: the tokens a replacement decorator returns may themselves contain decorations, and those are expanded in turn. The order is OUTERMOST first, so a replacement decorator receives the decorations it encloses UNEXPANDED and may rewrite or remove them. The alternative — innermost first — would make an outer decorator unable to remove an inner one, because the inner one would already have run, which is the capability conditional compilation depends on.

Note 2

Nothing is re-scanned. What a replacement decorator returns is already tokens, so the implementation walks the returned stream for further decorations rather than lexing it. This is why the phase costs one pass over what was produced rather than a re-lex of the region per expansion.

Note 3

names is fixed before the loop and nothing in the loop changes it, so a decoration that an expansion introduces cannot name a replacement decorator no import brought in. Such a decoration is an ordinary decorator and is resolved at run time, or is an error if nothing of that name exists. Re-resolving imports mid-expansion would be the alternative, and it is unavailable: 7.4 leaves an expansion nothing to fetch with.

Note 4

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.

7.7.1 When Expansion Happens

ParseModule is modified: after a module's source text has been scanned and before it is parsed as a Module, if ReplacementDecoratorNames of the module is not empty, Expansion is performed and the module is parsed from the resulting token stream rather than from its source text.

A module whose ReplacementDecoratorNames is empty is parsed unchanged, so a program that uses no replacement decorator observes no phase.

Note 1

ReplacementDecoratorNames is a static semantic of the ImportDeclarations, so deciding whether to expand at all needs the module's imports and nothing else — which is what lets an implementation skip the phase entirely for the modules that do not use it, and what lets a loader know from the import clauses alone which modules it must fetch first.

Note 2

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 replacement decorator was about to produce.

7.8 ApplyReplacementDecorator ( d )

The abstract operation ApplyReplacementDecorator takes argument d (a decoration) and returns either a normal completion containing a List of Token Records, or a throw completion. It calls the replacement decorator d names with the tokens d decorates and its own arguments, and returns the tokens the call produced. It performs the following steps when called:

  1. Let f be the function d's name denotes.
  2. Let target be the Token Records of the source text from the end of d to the end of the declaration d decorates.
  3. Let args be a List of the Token Records of d's arguments, unevaluated.
  4. Let result be Completion(Call(f, undefined, « target » followed by args)).
  5. If result is an abrupt completion, throw a SyntaxError exception whose message is result's value and whose position is d.
  6. If result is not a List of Token Records, throw a SyntaxError exception.
  7. If result does not match the grammar the position of d requires (7.9), throw a SyntaxError exception whose position is given by the [[Span]] of the first token of result that does not.
  8. For each Token Record t of result whose [[Span]].[[Source]].[[Generation]] is 0 and which did not come from target, set t.[[Span]].[[Source]] to a Source Reference Record with [[URL]] empty, [[Macro]] d's name, and [[Generation]] the current expansion depth.
  9. Return result.
Note 1

target is everything the decoration ENCLOSES, not the declaration alone. 7.7 replaces d together with what it decorates, so any decoration written between them is inside the replaced range - and handing the macro only the declaration would drop those decorations silently, which contradicts a replacement decorator being able to rewrite or remove the runtime decorations it encloses.

Note 2

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.

Note 3

A replacement decorator rejects its input by throwing, which is what a function does to reject its arguments and which cannot be ignored. The exception becomes a Syntax Error at the decoration site carrying the macro's own message, so a decorator applied to something it does not handle says so where it was written.

Note 4

A replacement decorator receives tokens and no reflection context. Every field of a context — a type, metadata, an initializer to add — needs either a resolved type or a running program, and expansion has neither.

7.9 Syntax Replacement

A replacement decorator is called with the tokens it decorates, a CONTEXT, and its own arguments where it has any - (tokens, context, args). Its TYPE is that reflection context: a macro may write 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 replacement decorators and none of them accepts the position a decoration is written in, there is no replacement to apply there: the decoration is left as written, for an ordinary decorator of that name at decoration time. This is not a decorator declining — no decorator ran — and it is not an error. It is the case a name carrying both a replacement decorator and an ordinary one exists for: @jsx { … } expands and @jsx class C {} decorates.

Note 1

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 replacement decorator does not accept where it is written is an error, as it would be for any other call: there is no other declaration for the decoration to fall through to.

The context is an ordinary object with one own property, kind, whose value names the position - the same name its reflection context has, "Class", "ClassMethod", "IfBlock" and the rest, with "Region" for a captured region. It is frozen, and it carries one other property only where the decorated statement is LABELLED, label, which is absent otherwise.

Note 2

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 replacement decorator, though a runtime decorator reaches them: a preprocessor name's { always begins a region, so a decoration on a block reports "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 region rather than an ObjectLiteral.

Note 3

Nothing else, because a replacement decorator receives the TOKENS OF WHAT IT DECORATES. A name, static, a for's binding, a match arm's pattern are all in those tokens already, and a field beside a token stream is two ways to say one thing. A runtime decorator needs them in its context because it is handed no tokens.

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 Records. Anything else is a Syntax Error, undefined included - a decorator ALWAYS replaces, and one that has nothing to change returns the stream it was given, which is a token sequence by this rule.

An empty sequence is a replacement like any other, and is legal exactly where an empty token sequence parses: it removes a statement or a class member, and does not remove a formal parameter from the middle of a list or a region whose value is wanted.

Note 4

What a decorator RECEIVES is a TokenStream and what it RETURNS is a token sequence, and the asymmetry is deliberate. A TokenStream carries spans this specification assigns, and 7.3 refuses construction for that reason: a stream a caller built would carry spans locating nothing. So a decorator that rewrites assembles ordinary records, and one that does not returns what it was handed.

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 undefined is refused rather than being read as one: a decorator that fell off the end of a branch would silently erase what it decorated.

The tokens a replacement decorator receives are those of the region it decorates, produced as ECMAScript unless that region is captured (7.5). The tokens it returns must parse as the grammatical form the position it decorated requires. It is a Syntax Error otherwise, reported at the returned token whose [[Span]] locates the failure.

Where the decorated position was a Statement, the replacement text is TERMINATED: a semicolon is appended unless what the decorator returned already ends in one, or in a block. A decorator may therefore return an expression in either position, which is what allows one decorator to serve both without being told which it is in.

Note 5

This is a rule about TEXT rather than about how the returned tokens are read, and it has to be. Expansion replaces a source range and the result is parsed again, so a decorator returning 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 replacement: a block produces no value but has syntax, and so do a statement, a parameter, a return annotation, an enum, a tuple, a record, and a lexical declaration.

Note 6

This is a grammatical constraint where value replacement's is a typed one, and the two are different axes rather than one rule with exceptions. It is a Syntax Error for a decoration on a statement that declares nothing to name anything other than a replacement decorator: a statement has syntax to replace, but a runtime decorator runs when the declaration it decorates is evaluated and there is no declaration there to run at. A position may admit both: a do block's value may be replaced by a runtime decorator and its syntax by a replacement decorator, and which a return means is decided by which kind of decorator returned it.

Note 7

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.

7.10 Reflection Contexts

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 4: the first argument selects among the signatures and the second is the declaration reflected, which is why one name serves every context with no any in it.

Table 47: Reflection Contexts by Family
FamilyContextsReflects
TypeTypeA type's structure (1.12.4). This is the one context this specification defines; the rest are the decorators extension's.
LayoutClassFieldLayoutA field's placement in its class - offset, widths, alignment, and bit position (9). Like Type, a reflection context that is not a decorator context: nothing decorates a placement. Shape: 7.11.3.
ClassClass, ClassField, ClassAccessor, ClassGetter, ClassGetterReturn, ClassSetter, ClassSetterParameter, ClassMethod, ClassMethodParameter, ClassMethodReturn, ClassOperator, ClassOperatorParameter, ClassOperatorReturnA class and each part of one. A constructor is a ClassMethod whose name is "constructor", and its parameters are that method's, so a construct signature needs no context of its own. A constructor MAY be overloaded (1.11.3), so its signatures has one entry per declared constructor. The arms agree on the instance they initialise and on the super call that initialises it, because the class builds the instance and only the body that runs is chosen. Shape: 7.11.2.
FunctionFunction, FunctionParameter, FunctionReturnA function declaration, its parameters, and its return. Shape: 7.11.4.
BindingLet, ConstA declared binding. Shape: 7.11.5.
ObjectObject, ObjectField, ObjectGetter, ObjectGetterReturn, ObjectSetter, ObjectSetterParameter, ObjectMethod, ObjectMethodParameter, ObjectMethodReturnAn object literal and its members, keyed on the instance rather than on a declaration, since that is where an object's metadata lives. Shape: 7.11.6.
BlockBlock, IfBlock, ElseIfBlock, ElseBlock, WhileBlock, DoWhileBlock, ForBlock, ForInBlock, ForOfBlock, MatchArmBlock, DoBlock, DoGeneratorBlockA statement's block, and the block of a match clause's arm (26.1). These are the one per-evaluation position of this extension: a block decorator runs on every entry to its block rather than once where the declaration is evaluated, which is what makes a block in a loop, or an arm of a match, countable. Shape: 7.11.7.
EnumEnum, EnumEnumeratorAn enum and its enumerators, parameterized by the underlying type. Shape: 7.11.8.
StructuralTuple, RecordA tuple or record declaration. Shape: 7.11.9.

A context that names a set of members has two signatures: one taking no name, returning an object keyed by member name, and one taking a name, returning that member alone. The forms and the options they take are given by 7.14.

Note

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 design's own demonstration of specialized overloads, one signature per context, which is why 4 records that form as the thing this extension needs from it.

7.11 Reflection Shapes

7.10 says which contexts there are. This clause says what each one answers, field by field. The shape of the one context the core defines, Reflect.Type, is given by Table 33 instead and is not repeated here.

The rules of this clause hold of every reflection; the family clauses that follow give the fields.

7.11.1 Rules Common to Every Reflection

Every reflection has a kind property whose value is a String naming the context that produced it, "ClassField" for Reflect.ClassField and so on for each context of Table 47. A reader handed a reflection dispatches on it rather than inferring the context from which fields are present, which is what a decorator whose parameter is a union of contexts would otherwise have to do.

The fields a family clause lists are all of a reflection's properties, and every one is present. An implementation may not omit a field, and it may not add a property beside them: anything an implementation exposes of its own goes under 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.

Note 1

A field an implementation may leave out is a field every reader tests for before using, and the tests are not written: a program is written against the implementation in front of it, and its portability is discovered later. Fixing the shape completely is what java.lang.reflect, System.Reflection, and inspect each do, and what makes a reflection worth reading at all.

Reflect.getReflection returns a new ordinary object each time it is called. Two calls naming the same declaration return two objects, which are not the same value; a reflection is extensible and is not frozen. Type Objects are interned and a reflection is not (1.2.2.9), the difference being that a Type Object is the type while a reflection is a report about a declaration.

A metadata object is the exception and is stable: reflecting one declaration twice reaches the same metadata object, and every decorator applied to one declaration is given that object. That identity is what makes a decorator's write visible to a later reader, and what lets a derived class's metadata inherit from its base's prototypically (7.15).

Note 2

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 metadata object even where nothing has written to it; it is never absent and never null. The contexts that carry no metadata at all, which are Reflect.Type and the Binding, Block, and Structural families of Table 47 as 7.15 gives them, have no metadata property, which is a different thing from an empty one.

Note 3

Present-and-empty is what keeps a read of a key that nothing set a missing key rather than a TypeError. The undecorated declaration is the ordinary input and the decorated one is the tested input, so a shape that is absent on the ordinary input fails where it is least likely to have been tried. It costs one object per declaration rather than one per reflection, since an undecorated derived class inherits its base's metadata object rather than being given a new one.

An initial property holds a declared default only where that default is a constant, and undefined otherwise. Evaluating a non-constant initializer would run the program's code where the declaration is evaluated rather than where the value is wanted, so the declaration is reported through initializer as a TokenStream (7.3) instead. The two are a value and the expression that produced it rather than two spellings of one thing, and a reflection may carry one, the other, or both.

7.11.2 The Class Family

Every reflection of this family has the fields Table 48 gives it, together with the kind of 7.11.1. A type field holds a Type Object, an initial field is governed by the rule of that clause, and an initializer field holds a TokenStream (7.3) or undefined.

Table 48: Class Family Reflections
ContextFields
Classname, a String or undefined for an anonymous class; type, the class itself; abstract; metadata.
ClassFieldtype; name, a String or Symbol; static; private; protected; readonly; initial; initializer; offset, a signed integer count of bytes from the start of the instance or undefined where the field has no laid-out placement, a negative offset being one that overlaps a base (9); byteLength, a non-negative integer or undefined; metadata.
ClassAccessortype; name; static; private; protected; readonly; initial; initializer; access, an object with get and set methods; metadata.
ClassGettertype, the getter's function type; name; static; private; protected; metadata.
ClassGetterReturntype; metadata.
ClassSettertype, the setter's function type; name; static; private; protected; metadata.
ClassSetterParametertype; name, a String; initial; initializer; metadata.
ClassMethodtype; name; static; private; protected; abstract, true for a signature with no implementation; signatures, a List of signature reflections in declaration order, of length 1 where the method is not overloaded, and absent where the method declares no type at all - a member that annotates nothing reports nothing rather than a signature of all-any parameters, an unannotated declaration remaining unannotated for the same reason an unannotated binding remains any; metadata.
ClassMethodParametertype; name, a String; index, a non-negative integer; initial; initializer; metadata.
ClassMethodReturntype; metadata.
ClassOperatortype; operator, the operator the declaration overloads; static; signatures, as a method's, a return-type overload appearing as a further entry; metadata.
ClassOperatorParametertype; name, a String; index; initial; initializer; metadata.
ClassOperatorReturntype; metadata.

A signature reflection, the element of a signatures List, has parameters, a List of parameter reflections of the family the declaration belongs to, and return, the return reflection of that family. Where the signature declares type parameters it has typeParameters, a List in declaration order of type parameter reflections, each reporting name, kind ('type' or 'value'), variadic, variance, arity, and constraint and default as the type objects the declared Types denote, or a deferred application where they read an earlier parameter — the declared data of the Type Parameter Record (Table 29), which is what lets a tool complete a named type argument at an application site. A parameter reflection is the same shape wherever it appears: 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 constructor is reflected as a ClassMethod whose name is "constructor", and its parameters as that method's, so a construct signature needs no context of its own (7.10).

Note 1

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 7.10 gives it, and a reference to the enclosing declaration's context, so a field's decorator can reach its class and a parameter's can reach its method. Everything a context reports about the declaration itself is this clause's, which is why the two are specified in one place and not two.

Note 2

offset and byteLength are undefined rather than absent where a field has no laid-out placement, since a field of a class that is not a value type has no offset to report and the shape does not vary with the class it came from. A reader wanting the full placement of a laid-out field, including its bit width and alignment, has 9; these two are here because a decorator that reorders or packs fields needs them at the declaration and should not have to reach for a second facility to get them.

7.11.3 The Layout Family

A ClassFieldLayout reflection reports where a field sits in its class, and is reached by Reflect.getReflection.<Reflect.ClassFieldLayout, T>(name). It is a TypeError exception where T has no layout: asking a class with none for a field's placement is the mistake, as reading byteLength from a string is (9). A program that does not know should ask hasLayout first.

Table 49: Layout Family Reflections
ContextFields
ClassFieldLayoutname, the name the slot was declared under, a String or Symbol; offset, a signed integer count of bytes from the start of the instance; 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 (7.11.2), which this one deliberately does not duplicate.

Note

The two are separate contexts because they answer different questions and are wanted by different readers: a decorator asks what a field was declared as, and a serializer or a vertex-attribute descriptor asks where its bytes are. Every system that offers both keeps the same seam - .NET has FieldInfo beside Marshal.OffsetOf, C's offsetof connects to nothing else, and Rust had no stable field-offset reflection at all until offset_of!.

An accessor reports the name it was declared under rather than the private field it desugars to: the backing is unnameable, and a slot no program can name would leave a hole in a layout a program is trying to read.

The two also answer differently for a class with no layout, and both are right. ClassField reports offset and byteLength as undefined, because a reflection's shape does not vary with what it reflects (7.11.1). This one throws, because it exists only to answer a question that has no answer there.

7.11.4 The Function Family

A decorated function literal receives a Function reflection of its created closure. Its signature reflection uses the effective parameter and result contracts, including adopted contracts where annotations were omitted, resolved under the closure's own or adopted binders and captured generic context. It does not resolve those names in an unrelated surrounding scope, execute the body, or evaluate parameter initializers merely for reflection. Public Type Object equivalence does not replace this source's declaration context.

Table 50: Function Family Reflections
ContextFields
Functiontype; name, a String, a Symbol, or undefined where the function is anonymous; signatures, a List of signature reflections (7.11.2) in declaration order, of length 1 where the function is not overloaded; metadata, whose inverse is the declared inverse where the function is a builder that declares one (17.3).
FunctionParametertype; name, a String; index, a non-negative integer; initial; initializer; metadata.
FunctionReturntype; metadata.
Note

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 set, so a reader wanting every overload takes it from there and one wanting the decorated position takes the parameter's own.

7.11.5 The Binding Family

Table 51: Binding Family Reflections
ContextFields
Lettype; name, a String; initial; initializer.
Consttype; name, a String; initial; initializer.

Neither has a metadata property, this family being one of those 7.15 gives none.

Note

A const initialized with a constant always reports an initial, where a let's may be undefined; the rule is the same one (7.11.1) and it is the declarations that differ, a const with a non-constant initializer being as possible as a let with one. The two reflections have one shape rather than two because a reader of a binding wants the same four answers either way.

7.11.6 The Object Family

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 (7.10).

Table 52: Object Family Reflections
ContextFields
Objecttype; metadata.
ObjectFieldtype; name, a String or Symbol; metadata.
ObjectGettertype, the getter's function type; name; metadata.
ObjectGetterReturntype; metadata.
ObjectSettertype, the setter's function type; name; metadata.
ObjectSetterParametertype; name, a String; initial; initializer; metadata.
ObjectMethodtype; name; signatures, as a class method's (7.11.2); metadata.
ObjectMethodParametertype; name, a String; index; initial; initializer; metadata.
ObjectMethodReturntype; metadata.
Note 1

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.

Note 2

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.

7.11.7 The Block Family

A block reflection reports the syntax around the block rather than a type: every field but index and async holds a TokenStream (7.3) or undefined. A block has no type to report, and what a decorator on one wants is what the block is and what governs its running.

Every reflection of this family has label, a String naming the block's label or undefined where it has none, and block, the tokens the block delimits. A field a particular block form does not have is present and undefined rather than absent, as 7.11.1 requires of every reflection: a for without an update clause reports update as undefined, so a reader walks one shape rather than testing which form it was handed.

Table 53: Block Family Reflections
ContextFields beyond label and block
BlockNone.
IfBlockcondition.
ElseIfBlockcondition.
ElseBlockNone.
WhileBlockcondition.
DoWhileBlockcondition.
ForBlockinitializer, condition, update, each undefined where the header omits that clause.
ForInBlockbinding, the tokens of the binding the head introduces.
ForOfBlockbinding.
DoBlockNone.
DoGeneratorBlockasync, true for async do * and false for do *.
MatchArmBlocksubject, the tokens of the match's argument; pattern, undefined for a default clause; guard, undefined where the clause is unguarded; index, a non-negative integer giving the clause's position among its siblings.

No reflection of this family has a metadata property, the Block family being one of those 7.15 gives none.

Note

A block reflection is produced on every entry to its block (7.16), so a block in a loop produces one per iteration. That is what a reflection being fresh per call amounts to here, and it is why the family carries no metadata: metadata is written once where a declaration is, and a block has no once.

7.11.8 The Enum Family

Table 54: Enum Family Reflections
ContextFields
Enumtype, the enum type; name, a String; valueType, the Type Object of the type the enumerators take their values in; size, a non-negative integer count of enumerators; metadata.
EnumEnumeratorname, a String; value, the enumerator's value, of the enum's valueType; index, a non-negative integer giving its position in declaration order; metadata.
Note 1

An enum reflection reports both a 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 7.11.6 says by omitting one.

Note 2

An enumerator reflection reports its value and no type: the type is the enum's and is reached through the enum's reflection, where reporting it per enumerator would be the same Type Object repeated once per member. index is the declaration order rather than the value, which are different whenever a program assigns values explicitly.

7.11.9 The Structural Family

Table 55: Structural Family Reflections
ContextFields
Tupletype.
Recordtype.

Neither has a metadata property, this family being one of those 7.15 gives none.

Note

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 List selects Tuple and one built over a set of keyed members selects Record, the same division a composite's own kind already makes. A reader wanting the structure walks type with Reflect.Type (1.12.4), which is why one field is the whole shape.

7.12 Replacement Values

For a decorated function literal, check each returned replacement against the original function contract before exposing it to another decorator; an incompatible replacement throws a TypeError exception. After an accepted replacement, perform CreateDataPropertyOrThrow on the Function context for "type" and that replacement, so the next outer application receives it; propagate an abrupt completion. Returning undefined retains the current function. The final result still undergoes the ordinary receiving check. An unchanged producing completion retains its eligible construction associations; a replacement contribution receives none of the original literal's selected-construction associations merely because its value or type is equal, including when the decorator returns the original function itself. Abrupt completion retains earlier effects and does not publish a successful initializer result or extend any reference's lifetime.

A decorator may return a value that replaces what it decorated. Returning undefined replaces nothing, so a decorator that only reads or only writes metadata returns nothing and is done. Where a value is returned, it replaces the decorated target and must be of the type Table 56 gives that context; a value that is not is a TypeError.

Table 56: What a Context Admits as a Replacement
ContextReplacesType admitted
ClassThe classThe class or a subclass of it
ClassFieldThe field's initial valueThe field's type
ClassAccessorThe accessor's get and set pairAn object with get and set methods over the accessor's type
ClassGetterThe getter functionA function of no parameters returning the getter's type
ClassSetterThe setter functionA function of one parameter of the setter's type returning undefined
ClassMethodThe methodThe method's type, signature for signature
ClassOperatorThe operator functionThe operator's type, signature for signature
FunctionThe functionThe function's type, signature for signature
ObjectGetterThe getter functionAs ClassGetter
ObjectSetterThe setter functionAs ClassSetter
ObjectMethodThe methodAs ClassMethod
DoBlockThe do expression's valueThe type of that expression
DoGeneratorBlockThe do * expression's generatorA Generator of the expression's yield, return, and next types

Every other context admits no replacement, and a decorator on one that returns a value other than undefined is a TypeError. Those are the sub-target contexts, which name a part of a declaration rather than a thing that exists on its own - a parameter, a return - and the structural and binding contexts, and the blocks other than a do's.

Note 1

The Class row admits a subclass because a class type is nominal - a nominal Type Record (Table 1) is a subtype along its declared inheritance and not by its shape: a structurally identical class of another declaration is not a value of the decorated class's type, so "the same shape" would not have been a rule an implementation could check, where "the class or something extending it" is.

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 replacement could be.

Note 2

This table and 7.6 are different axes, and a position may appear in both. A decorator of this clause runs when the declaration is evaluated and returns a VALUE; a replacement decorator runs at expansion and returns SYNTAX. The reasoning that excludes a block here does not carry there: a block produces no value, but it has syntax, and a replacement decorator on one rewrites it.

7.13 addInitializer

addInitializer(callback) registers callback to run once the declaration it was registered from is fully decorated. It is a TypeError if callback is not callable. The registered callbacks run after every decorator of that declaration has been applied, in the order they were added (7.16), and their return values are discarded.

Only the contexts of Table 57 have an 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.

Table 57: Contexts Carrying addInitializer
Carries itDoes not
Class, ClassField, ClassAccessor, ClassGetter, ClassSetter, ClassMethod, ClassOperator, ObjectGetter, ObjectSetter, ObjectMethodEvery other context of Table 47: the sub-target contexts naming a parameter or a return, Function, ObjectField, the Binding, Block, Enum, and Structural families, and Type.
Note 1

The list is closed rather than derivable, and Function is the case that makes it so: a function declaration admits a replacement (7.12) and carries no addInitializer, so no rule of the form "whatever can be replaced can be initialized" holds. What the ten have in common is a declaration site inside a class or an object literal that something else constructs - an initializer runs at construction, and a free function has no construction to run at.

ObjectField is the other case worth naming: it is a member of an object literal, and the literal is finished when the field's decorator runs, so there is no later moment for a callback to be later than.

Note 2

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 (7.16) and registration is not application. Running them in the order added rather than in reverse is the ordinary reading of a queue, and the two orders together mean a stack of decorators applies outward and initializes forward.

7.14 Retrieving a Reflection

7.14.1 Reflect.getReflectionByIndex

Reflect.getReflectionByIndex.<C, T>(…) answers the same reflections Reflect.getReflection of the same context and arguments would, as a List in declaration order rather than as an object keyed by member name. It exists for the positions a name does not order: a parameter list is read by position, and an object keyed by parameter name loses the order the positions have.

It is defined for the contexts that name a set of members, and for a context naming exactly one thing it is not defined, there being no order to answer.

7.14.2 The own Option

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 owner, a static member's home object being the constructor and an instance member's the prototype whose 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 (Table 47), a class always having one whether or not its body writes it. A program listing the methods it means to expose skips that name.

Note

Inherited by default because a decorator asking what fields a class has usually means all of them: an inherited field is a field of the instance, and a serializer or a validator that skipped it would be wrong in a way its author would not see until a subclass appeared. The option is for the reader that wants the declaration rather than the shape - a documentation generator listing what this class adds, say.

7.14.3 Overloaded Targets

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 TypeError where the target is overloaded; the parameters are read through the signatures of the declaration's own reflection, each entry of which carries its own parameter and return reflections.

Note

This is the rule an ambiguous overloaded reference already follows at a call site, applied to reflection: answering one signature's parameter would be answering a question the caller did not ask, and answering all of them would not be a parameter reflection. Where the target is not overloaded the flat forms are defined and are the shorter way to write the common case.

7.14.4 Reflect.getMetadata

Reflect.getMetadata.<C, T>(…) performs the following steps when called:

  1. Let reflection be ? Reflect.getReflection.<C, T> of the same arguments.
  2. If reflection has no metadata property, throw a TypeError exception.
  3. Return reflection.metadata.

It is defined only for the contexts whose reflection carries a metadata property (7.15), which is the reading half of what a decorator writes. Where the reflection answers a set of members rather than one, the result is an object keyed as that reflection is, holding each member's metadata object.

Note

Sugar over Reflect.getReflection, and specified as such rather than independently, so the two cannot answer differently about the same declaration. The metadata object it returns is the stable one of 7.11.1, so a write through a decorator's context is visible through this and the reverse.

7.15 The Metadata Channel

A decorator may be written before a TYPE only in a position that has a reflection context of its own, which is a RETURN: a method's, a getter's, an operator's, an abstract method's, and a function's. 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 field, a parameter, or an interface member is a Syntax Error rather than a decoration that names nothing.

Note 1

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 reflection context of the Class, Function, Object, and Enum families has 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 metadata object is an instance of the intrinsic interface corresponding to its context, %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 (1.5.27), which is what a partial declaration over one resolves. A program adds to one by declaring a partial interface over it whose members are typed and Symbol-keyed, and the members it adds are the only ones there are: the intrinsic interfaces declare none. An interface rather than a class, because a metadata object must be an ORDINARY OBJECT: the decorators extension has a subclass's metadata inherit its superclass's PROTOTYPICALLY, falling through for a key it does not set and shadowing without mutating for one it does, and an instance of a class with a typed field is not extensible (1.11.9) and so cannot be prototypically linked at all. An interface declares the shape without adding instance state, which keeps the whole set of keys and their types known at compile time - what an implementation needs to specialize access - while leaving a metadata object with only the members something set.

Reflect.getMetadata.<C, T>(…) returns the metadata object that Reflect.getReflection of the same context and arguments would report, and is the reading half of what a decorator writes.

A metadata object is inherited through the prototype chain. Where a derived class does not redeclare an inherited member, the member's metadata object is the base's, so a Symbol lookup finds what the base's decorators wrote. Where it redeclares the member and decorates it, the member's metadata object is a new one whose prototype is the base's, so what the derived class's decorators write shadows the base's without mutating it.

Note 2

Typed Symbol-keyed members on a partial interface are what make this a channel rather than a property bag: a key cannot collide, because a Symbol cannot; a value cannot be of the wrong type, because the member is declared; and nothing is reachable that was not declared, so a reader of %ClassFieldMetadata% sees exactly the vocabulary the program's decorators established. This is the same discipline the metadata protocol takes for a meta type's keys (6.2), reached from the other direction.

Prototypal inheritance of the metadata object is what makes @tag('base') on a field of 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.

7.16 Applying a Decorator

A decorator is an ordinary function whose last parameter is annotated with a reflection context. Which function a decoration applies is decided by the overload resolution of 1.10.3.1 over the arguments written and the context of the position decorated, so @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 TypeError is thrown where the decorated declaration is evaluated.

It is a type error if an ordinary decorator's known application contract proves that it cannot receive the written arguments and the implicit context. The context is an Object supplied by value; it cannot satisfy a required reference parameter or a definitely incompatible scalar boundary where it is received. Bare and parenthesized applications use the same context-last placement, default and optional gaps, and rest rules. A missing prefix value is an error when the required parameter cannot accept undefined; untyped parameters retain their ordinary missing-argument behaviour. An extra context argument may be ignored. Within an overload set eliminate only demonstrably impossible candidates, preserving context-only preference and runtime resolution where context details remain unknown. Reachable union alternatives retain the per-alternative call judgment. Unknown context structure does not imply a closed empty-object type, an overload tie, or a selected reflection host. This judgment executes no decorator or getter, creates no borrow, and does not alter replacement-decorator expansion, context allocation, or liveness.

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 replacement for what it decorated. The replacement a context admits is given by 7.12, and the initializer registration by 7.13. So is the ORDER, which that extension now fixes in two phases: decorator expressions are evaluated in document order, and decorators are applied INNERMOST FIRST AND IN REVERSE SOURCE ORDER - several on one declaration apply closest-first, a declaration's parameters and return apply before the declaration itself, and members apply before their container, so a class decorator sees a class whose members are already decorated. Registered initializers run after every decorator of that declaration has been applied, in the order they were added (Table 39).

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.

Note 1

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 replacement decorator on a block runs once, at expansion, and can emit the same instrumentation inline - so the per-iteration work happens where the per-iteration decorator does not.

This is also why a block reflection carries no metadata and cannot be interned (7.11.1): both would need a once for the block to have, and it has none.

Note 2

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 proposal resolves it by the annotation on the last parameter. So a decorator looks the same whether or not it takes arguments, and giving one an argument is editing its parameter list rather than rewriting it into a factory.

Both rules above are rules for decorators that run when the declaration they decorate is evaluated. A replacement decorator runs at expansion, and neither applies to it: its arguments are Token Records and are not evaluated, and its name denotes one function rather than a set of overloads.

8 Declarative Checker Facts

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 (6) and keeps them outside the boundary 1.2.7 draws: nothing evaluates, and each is consumed by machinery that already exists.

8.1 The Expected this Type

The receiver-presence rule also applies on the live path of an optional call. Thus 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 path. A signature without [[ThisType]] remains callable without a receiver; a method's self [[ThisType]] does not disappear merely because its body does not read 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 type error; an unresolved receiver is checked at entry before the body executes. Parentheses, explicit type arguments, optional calls and tagged calls preserve the receiver supplied by their member reference. This check preserves the receiver identity and does not convert it into a different object. The self [[ThisType]] continues to mean the receiver through which the method was reached, not the nominal class that declared it.

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 (1.10.2.3). A method extracted from its class and called free of it is the case this decides: its this is not of the type its body assumes, and the extraction is a type error at the boundary that took it, or at the call that supplies it no this, rather than a TypeError inside it.

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 type error for a call to supply no 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 contextual type (Table 17) is a function type whose applicable signature has a [[ThisType]], the literal adopts it: 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 typed where it was written.

Note

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 design's 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 typed as the finished object. Nothing about that reaches into inference; the type is built first and the literal is checked against it, which is the order 1.2.7 requires of everything.

8.2 Declared Narrowing

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 1.8.5.1. Ordinary assignment and effect invalidation still apply.

A Narrowing Record has a [[Target]], a String naming a parameter of the signature or "this", and a [[Type]], a Type Record. A signature's [[Narrows]] is a List of them, and it says what a call establishes: that the argument passed at [[Target]] is of [[Type]]. Two forms follow from the signature's [[Return]], and no third is defined.

Where the [[Return]] is boolean, the call is a test and its narrowings apply where its result is true, exactly as v is T applies (Table 24), so if (isFish(pet)) narrows pet in the branch it guards and narrows it away in the other. Where the [[Return]] is void, the call establishes its narrowings by returning at all, since there is no result to condition on, so they apply in every position the call dominates: the argument's Static Type there is NarrowTo(s, [[Type]]), which keeps a narrower s narrower. It is a type error where that is empty under the stability condition of 1.8.5.4, since the call can then never return normally; a void assertion that can never fail decides nothing, having no false branch, and is not an error. Where the [[Return]] is any other type, a non-empty [[Narrows]] is a type error: nothing would consume it.

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 (Table 23), so a narrowing that lied costs a check that fails rather than a value that was never what it claimed.

A fresh function literal whose return position is unannotated may adopt the contextual signature's narrowing declaration under 1.6.5, with parameter targets translated to its parameter names by position. This declares a contract on that new function; it does not prove its body. A pre-existing function value gains no narrowing declaration merely by crossing a guard-typed boundary. An explicitly written return contract is not replaced by contextual narrowing.

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 false for a String, while the advertised predicate would remove String from that branch. A void assertion has only its successful continuation and may establish a stronger fact. These judgments compare declared contracts, not the truth of the body. An override cannot acquire or discard an advertised narrowing merely by returning a Boolean.

Note 1

The two forms are the type predicate and the assertion function of an erased system, and they are one field here rather than two spellings, because the difference between them is not in the fact declared but in what the declaration returns. A function that answers a question returns the answer, and its narrowing applies where the answer was yes; a function that establishes a fact returns nothing, and its narrowing applies once it has returned. Deriving the two from [[Return]] is why no asserts operator appears here.

[[Narrows]] has a source spelling: the second form of TypeAnnotation (1.4.4), written in a return position. function isFish(pet: Pet): pet is Fish declares a signature whose [[Narrows]] holds one Narrowing Record, its [[Target]] the named parameter and its [[Type]] the type after is. The signature's own [[Return]] is boolean: the annotation says what a true answer PROVES, not what the function returns, so the body returns a boolean and a caller reads one. It is a type error if the BindingIdentifier is not the name of a parameter of that signature, since the narrowing lands on the argument in that position and a claim about anything else is one no caller could act on.

A signature may also acquire [[Narrows]] and [[ThisType]] by construction (1.12.5), which is how the design's 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.

Note 2

A spelling is what makes the [[Narrows]] step of 1.10.2.3 hold anything. A narrowing "is a claim the declaration makes and the body is not required to honour", so it is sound only while it is the DECLARATION's own: a rule that let any function of the right shape satisfy a guard type would hand the claim to code that never made it. Without a spelling the step had the opposite fault, since no written function could carry a Narrowing Record and so none was assignable to a guard type at all — the feature's own stated use, a guard a program writes, was unreachable.

The lookahead is one token: a Type is never a BindingIdentifier followed by 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 (5.2), and for the same reason.

9 Memory Layout

A value type has a defined layout in memory, and this clause defines it. It is what makes a [N].<T> a contiguous buffer rather than a list of references, what gives 1.8.3's zero-fill something to fill, and what a serializer, a placement construction over existing bytes, or a vertex descriptor reads. The default is the rule C, C++, and Rust follow, so a value type class is laid out as the same declaration is in those languages, and the controls below are their repr attributes together with the field offsets those languages keep to themselves.

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.

Note

They are typed rather than Numbers because a length and a stride are the operands of exactly the arithmetic the type system exists to check, and because a surface half of which was typed could not be combined with the other half at all.

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 (6.1.6), and a program counting in a narrower type writes the conversion. Typing these quantities makes them combinable with each other; it does not and should not make the numeric family permissive.

9.1 Layout Properties

Install these fixed own data properties when the type's layout is complete, including on an applicable class or array constructor and on each completed specialization. A declaration or decorator cannot replace one with a user property: a conflicting own declaration fails before the completed type is published. An attempted write to an established fixed layout property is a type error, in every language mode, including a compound assignment or update. A logical assignment with an established skipped store remains valid. Immutable aliases preserve this permission; a value known only as 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 Type Object whose type has a layout has three own properties. bitLength is the size in bits, which is what an arbitrary-width integer needs in order to describe itself; 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]]: false, [[Enumerable]]: false, [[Configurable]]: false }, and each is compile-time evaluable (1.2.7), so [Vertex.byteLength * 1024].<uint8> is an array extent and T.byteLength inside a generic is a constant once T is specialized.

Table 58: Which Types Have a Layout
TypeLayout
A numeric type, boolean, or a SIMD vectorYes. An int.<N> or uint.<N> has bitLength N; a named type has the bitLength its width states.
An enumYes, its underlying type's.
A value type class (1.11.2)Yes, by 9.2.
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 field of 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 typeYes, its [[Base]]'s exactly (1.5.20).
bigint, string, anyNo. The size is a property of the value and not of the type.
A reference type, and a nullable union of oneNo. A reference's width is the implementation's business.
_T_ | *null* where T is a value type with a layoutYes, the inline optional of 10.
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 (11.2), so it is a reference and its width is the implementation's business.
A class with an untyped field, or one declared dynamicNo.
A union of value typesNo. It has no one layout.

A Type Object whose type has no layout has none of the three fixed own data properties. Reading one of these layout members through its layout-checking accessor throws a TypeError exception. The 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 value type class takes the alignment 9.2 gives it rather than this rule, since its size is a consequence of its fields rather than a width of its own.

A Type Object has a family, a String naming the kind of value the type describes. Table 25 is organised by this concept already, deciding what an operator does by the family of its operands; family is how a program asks the same question.

Table 59: Type Families
familyTypes
"integer"uint8 through uint128, int8 through int128, the general uint.<N> and int.<N> forms, and boolean1, which is a one-bit unsigned integer
"float"float16, float32, float64, float128
"decimal"decimal32, decimal64, decimal128
"rational"rational.<T>
"complex"complex.<T>
"vector"every SIMD type, vector.<T, N>, and the bit-vector booleanN forms for N above one, boolean8 being vector.<boolean1, 8>
"bigint"bigint
"string"string
"boolean"boolean, the ordinary two-valued type
"type"the type type
"class"a class or an interface
"enum"an enum
"any", "void", "never"each of those types

A type carrying metadata reports the family of the type it parameterizes, so float32.<{ m: 1 }> is "float" and uint8.<{ bounds: 1..=6 }> is "integer". A parameterization refines the type it parameterizes rather than replacing it (1.5.20), and reporting otherwise would put family at odds with instanceof, with the operator table, and with bitLength beside it, each of which already answers about the parameterized type.

Note 1

family names the family and nothing further. For a class it is "class", which is the name of the family and not of the declaration: a nominal type never exposes its declaration (1.12.2), and this exposes none of its fields, its name, or its shape.

A program wanting the metadata a type carries rather than the family it belongs to reflects, where the "parameterized" node gives the base and the metadata (Table 33). The two are different questions - what kind of value this is, and what is known about it besides - and are answered in the two places that already describe them.

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 proposal performs no implicit numeric widening, so two types of one family are no more assignable to each other than two of different families are.

A Type Object has 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.

  1. If the type carries bounds metadata (6), let r be that range. 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 integer type has a max of 9 where 0 ..= 10 has 10.
  2. Else if the type is an integer type, let w be the type's width in bits. 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 integer having values no Number holds exactly.
  3. Else if the type is a binary floating-point type, min and max are the finite extremes of its format: min is the MOST NEGATIVE finite value and not the smallest positive one, and max is its negation.
  4. Else, reading either throws a TypeError exception, as reading 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 integer type has neither, rather than having them as undefined.

Note 2

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 integer and floating-point families would disagree about what the one name said. The lowercase names also match the members already on a Type Object, which are facts about the type rather than constants of a namespace, and 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.

Note 3

A Type Object also has an isPlainData property, true where the type has a layout and no position within it at any depth holds a reference, and false otherwise. Only such a type may be reinterpreted as bytes: viewed through a buffer (11), copied wholesale, overlapped at an explicit offset, or written to a wire format by offset. Like 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 typed string is a value type class with no layout, and a class holding a field typed D | null for a dynamic class D has a layout - a reference's - and is no value type at all. Reading "contains no references" off 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 Type Object property answers have matching types in the type universe, so a generic may CONSTRAIN on them: value is the type every value type (1.5.1) is assignable to, and plain the type every plain-data type is assignable to. plain is the narrower: every plain-data type is a value type, and a class every one of whose fields is typed string is a value type that is not plain data. They exist as types rather than only as properties because a generic class constrains its type parameter through 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.

Note 4

A Type Object has a hasLayout property, true where the type has a layout and false where it has none. Reading byteLength, bitLength, or alignment from a type with no layout throws a TypeError exception, which is right for a program that believes it has one; hasLayout is for the program that is asking rather than asserting, and answers without throwing.

Note 5

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 true for every type, the property being present and throwing when read.

hasLayout sits beside the three it guards rather than in a function of its own, so it is compile-time evaluable for the same reason they are - which is what a serializer wants, the branch being one it would rather have folded away than taken. Whether a FIELD has a placement needs nothing new: its offset is undefined where it has none (7.11.2).

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 integer aligned at 8 beside a 16-byte SIMD vector aligned at 16 - and would need the vector to be stated as an exception. Uncapped, the vector is not an exception: a width that is already a power of two rounds up to itself. Raising an alignment past the natural one is what align is for (9.3), as #[repr(align(N))] is in Rust, and lowering it to 1 is what packed is for.

Note 6

These are properties of an interned Type Object rather than a 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 9.3 are accounted for in it: 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 design fixes only 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 covers, and it is uncapped for the reason that rule gives: an 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.

9.2 Natural Alignment and Padding

The layout of a value type class places its fields in declaration order. Let a bit cursor begin at 0, at the end of the layout of the class it extends where it extends one and at 0 otherwise. For each field in order:

  • Where the field's type has 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.
  • Otherwise the cursor is first rounded up to a byte. The field is placed at the byte its 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 field.

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 type error for a field to be placed outside the size a size fixes, or for an inherited field to be redeclared, since the redeclaration would have no defined offset.

A field's offset is read through the reflection of its declaration rather than from the type, because it belongs to the field: Reflect.getReflection.<Reflect.ClassFieldLayout, T>(name) reports its placement, compile-time evaluable for the reason the layout is (7.11.3). The ClassField context beside it reports what the field was DECLARED as and carries 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.

Note

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, field order is a performance decision the program makes: { 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.

9.3 Explicit Control

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.

Table 60: Layout Controls
NameApplies toEffect
packedA classEach field is placed immediately after the one before it and the class's alignment is 1. A field may then be unaligned, which costs a little on every access and buys exact offsets, which is the trade a wire format wants.
alignAllA classThe alignment of the instance as a whole. It composes with packed, which decides the fields' placement while this decides the instance's.
sizeA classThe allocated size, zero-padded to it.
alignA fieldRequires the field's address to be a multiple of its value, in place of its type's alignment and in either direction: a field may be given a stricter alignment than its type needs, for a cache line or a wide move, or a weaker one, which is what a packed wire format does to a wide field.
offsetA fieldPlaces the field that many bytes from the start of the allocation. The origin is 0 for each class, so in a subclass a positive offset lands after the base and a negative one reaches back into it.
offsetBitA bit-fieldPlaces it that many bits from the start of the allocation, which is what fixes bit order exactly.
endianA multi-byte field"little" or "big", fixing the field's byte order. The default is the platform's, which is what a TypedArray already uses, so a class may mix native fields with network-order ones.

These apply only where every field of the class and of its whole prototype chain is typed, since a class with an untyped field has no fixed layout to place a field within. A dynamic class may gain a typed field later through Object.defineProperty with a type key, and that field is part of no layout; on any other class such a call is a TypeError, because the layout closed at the declaration.

Note

An align replaces its field's alignment rather than strengthening it, which is the design's own reading: a 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 field's natural alignment to be a precondition of the instruction it would otherwise use.

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 (1.11.8).

Overlapping fields are the one place a typed read can produce a value the type system did not construct, and this specification permits it knowingly. The store check of Table 23 applies PER FIELD, against the type that field declares: a store through 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 field and reads a 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 field ACCEPTS, not over what the bytes beneath it came to hold; a program that overlaps two fields of different types has taken responsibility for the correspondence, in the same way that a program writing through a DataView over a typed array has. Nothing else in this specification has this property, and nothing else needs it: it follows from offset placing a field outright, which is the point of offset.

Reading or writing a bit-field is a shift and a mask, and taking a reference to one is a type error, since it has no byte address to refer to. A bit-field is also not a SIMD lane mask: boolean8 and its family are one bit per lane and belong to the vector types, where a bit-field is a sub-byte integer member of a class.

Automatic packing reaches only a field under 8 bits, so 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 design states the rule for the sub-byte case and leaves the wider one to the explicit control, and whether automatic packing should follow bitLength rather than a byte is a question it should answer.

9.4 Finiteness

The layout of a value type class is defined exactly where the rule of 1.11.2 holds, that a value type class may not contain itself, directly or through a cycle of value type fields. The two are one condition. Computing a layout recurses from a class into the type of each of its fields; the recursion terminates exactly where that field relation is acyclic; and every field contributes a size of at least zero and at least one field of a cycle contributes more, so a cycle admits no finite fixpoint and every acyclic graph reaches one in as many steps as it has edges. The rule is therefore not a restriction placed on top of the layout but a statement of when the layout exists, and the diagnostic a program gets names the cycle rather than a size that could not be computed.

A field whose type is a reference (1.11.10) closes a cycle, which Table 58 gives no layout and whose width is the implementation's; the recursion stops there rather than descending, which is why a linked list is expressible and a class containing itself by value is not.

A field written T | null for a value type class T does NOT close a cycle, being laid out inline (10). A class that contains itself through one therefore has no finite layout, and is a type error like any other cycle of value type fields; the class is declared reference to close it.

10 Optional Values

A field or an element whose type is _T_ | *null* for a T that is a value type with a layout is laid out INLINE: a discriminant of one byte, placed so that the payload begins at T's alignment, followed by T's layout. The alignment of such a type is T's, and its byte length is T's byte length plus the discriminant, rounded up to that alignment. No storage is allocated for it separately and it is reached without indirection.

null is the discriminant's zero, so a zero-filled allocation is a run of empty optionals and a typed declaration without an initializer needs no fill pass of its own.

The discriminant is a declared byte rather than a spare bit pattern of T, EXCEPT where T's declaration excludes a pattern: an integer primitive whose bounds metadata (6) refuses a value its base could otherwise hold, or a type that is a reference and so cannot hold null. Where such a pattern exists it is the discriminant, and the optional is the width of T.

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 integer base is read: a floating-point type has no next representable value to reason about without rounding, so it declares no niche whatever its bounds.

Note

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 field copies, reading out of it copies, === 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 compile-time constant this specification lets a program compute with, place in an array extent, and assert; a size that depended on whether some nested field's value range happened to have a hole would let a new enum member resize a structure declared elsewhere. A DECLARED exclusion does not have that property, being written in the type and checked at every boundary the value crosses, which is why it is the half taken - the same half Rust guarantees through 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 field that completes a cycle would find a structure elsewhere quietly grow. Rust and C++ both refuse the recursive declaration and require the author to name the indirection, and 1.11.10 is where it is named here.

11 The Span Type

A window's length is a non-writable, non-configurable own property. It is a type error to store through that known member, even if the assigned value equals its current length. The permission applies to updates, compound stores, destructuring and writes through reference aliases with known origins. It follows the window contract through array-window calls, buffer views and column projections; it does not make an owned array's length non-writable. Element writes remain permitted, with the ordinary bounds and reference-liveness checks.

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 (14) is a Span.<T> over the bytes of a buffer, and a projection of an SoA column (15) is a 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 (2.11) — those describe an allocation, and a window has none. It is also why new Span.<T>() is a TypeError exception and a class may not derive from it: there is nothing for such a construction to allocate. 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 (1.4.2.7): a Span.<uint8> used as a Span.<number> would admit a Number into storage typed 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 element type at run time (Table 23).

Note 1

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 element type it does not get to choose.

Note 2

An access outside the window's length is a RangeError exception, for a READ and for a WRITE alike. The write is the part worth stating: a window's length is fixed, so a write past the end has nowhere to go. The three containers differ here, and the difference is the design rather than an accident — a write past a [N] . < T > is attempted GROWTH and a TypeError exception (1.14.10), a write past a [ ] . < T > GROWS the array, and only an array with no element type answers undefined to a read.

11.1 Coercion to a Span

[].<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.

Note

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 14 takes a count as an ARGUMENT — while a window taken over a known run of elements can, and something has to carry it for a bounds check to be elided.

std::span<T, Extent> and &[T; N] are the same shape reached from the same pressure.

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.

11.2 Liveness of a Span

A window names a run of elements in storage it does not own, so it is a reference in the sense of 2.10 and takes that clause's rules. A 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 invalidated window throws a TypeError exception. An operation that does not relocate does not invalidate: a 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 invalidated, a fixed extent having nothing that relocates. A window over a buffer follows that buffer's rules instead: it tracks a resizable buffer's length, or is out of bounds while a fixed view's bytes are absent (14).

Note

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.

12 Resizable-buffer view lifetime

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 covers that extent. Failure throws a TypeError exception. A fixed Span reports length +0𝔽 while detached or out of bounds. Shrinkage does not detach the buffer or permanently invalidate these views: regrowth covering the extent restores access and the fixed Span's original length, even if an intervening access failed. The restored bytes have the values supplied by the ArrayBuffer resizing rules, not their discarded former values. Actual buffer detachment is permanent and is checked independently of extent, including an empty extent.

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 field retain their enclosing source checks as well as their own extent checks, before reading, writing or copying. Retaining an earlier field cannot bypass a missing later part of the source's fixed extent. A completed value copy is independent. An explicitly constructed independent byte view has its own extent. These rules do not remove owned-source generation checks, revive an owned element removed and later replaced, or revive a window invalidated by relocation of its owned source.

Construction rejects an actually detached buffer with a TypeError exception, including a zero-count or length-tracking Span. Preserve the existing argument evaluation and check order. Use the current bounds again after intervening user code and before accessing bytes. If that code shrinks and regrows the buffer before the next check, recovery is permitted; if it completes abruptly, propagate that completion. Earlier effects and completed writes are not rolled back when a later check fails. Failure does not poison a view or initialize a failed destination. Failed resizing has no resizing effect of its own, but effects of argument evaluation remain. SharedArrayBuffer growth and the existing shared access-order rules are unchanged; shared buffers neither shrink nor detach.

13 Derived-view source validity

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 captured generation with the owner's current generation. Apply this transitively, including a viewed SoA and its column projections: a fixed extent does not remove an obligation inherited from a movable owned source. Reads, writes, value copying and further storage acquisition retain that obligation. A newly acquired view of the owner's current storage can establish a new obligation.

Before publishing a derived view, validate the source under its existing liveness and current-bounds rules. An invalid source throws a TypeError exception even when the result would be empty or would fit in the remaining bytes. An accepted native TypedArray source must likewise be currently valid. Preserve existing expression evaluation, property lookup, argument conversions and applicable type, layout and count checks; this source check does not precede or replace an earlier abrupt completion. Effects during argument processing can invalidate an established source. Failure publishes no new view, does not complete an assignment to the result destination, and retains earlier effects.

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 covered: a two-byte child of an eight-byte source can remain usable when only four bytes remain. Its own bounds recover under 12. This is distinct from retaining an element or nested placed field through an enclosing value, which keeps the enclosing checks specified there. Derivation does not change element contracts, reference permission, count arithmetic or the set of accepted source categories. Copying into independent owned storage remains a separate operation.

Acquiring the buffer of a proposal Span validates that view before exposing its storage. A raw ArrayBuffer successfully obtained while the view was valid retains its own identity and storage lifetime; later owner relocation does not detach it or make an explicitly constructed view of those old bytes follow the owner. Do not attach the original owner's obligation to every use of that raw buffer. Existing native buffer accessors are unchanged. Metadata queries do not establish source liveness.

14 Array Views

Span.<T>(buffer, byteOffset, count, byteElementLength) is a VIEW over bytes that already exist, as distinct from the allocations of 1.8.3. A view IS a Span.<T> (11) and this clause states the buffer-specific half of that type: how its length is derived, and what becomes of it when the bytes beneath it change. It was spelled [].<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 typed array including another view, or a FIXED-extent owned array [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 floor of their length over it and the trailing partial element is not part of the view. count decides whether the view is FIXED or LENGTH-TRACKING and is described below. byteElementLength defaults to T's byteLength and may be smaller or larger than it, so a view may read one field out of a larger element by giving the element's stride; an element is addressed at byteOffset plus its index times that stride. It comes last because no other view constructor in the language has it: %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 +0𝔽, as a %TypedArray% in the same position does, and refuses every access; it is a RangeError exception if the buffer does not hold that extent past byteOffset, and while the buffer is smaller than that extent the view is out of bounds and access throws a TypeError exception. Regrowth covering the extent restores the original length and access. Actual buffer detachment is permanent. Growth never invalidates a view, and a SharedArrayBuffer neither shrinks nor detaches.

A store through a view is checked against T as any store to a typed element is (Table 23), and the converted value is what reaches the bytes.

Note

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 covered again, while a tracking view promised only to describe whatever is there.

15 Structure of Arrays

SoA.<T, Length> stores its elements as one array per field of T rather than as a run of T. It is a built-in exotic in the same way [N].<T> is: no user-defined class can express it, because declaring one array per field of a type is a mapped type this specification does not have, and because a reference into one denotes a column set and an index rather than a byte offset.

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 value type class or a primitive. A primitive T degenerates to a single column, so a generic that may be handed one needs no special case; a class with a field of no layout has nothing to split and is a type error.

The layout is the row Table 58 gives it: each field of T is a column of Length elements, padded and aligned on its own, in declaration order, and the type's byteLength is the sum of the columns rounded up to its alignment. The split is ONE LEVEL: a field that is itself a value type stays a single column, interleaved within itself, so a consumer wanting that field as a contiguous run of its own type has one and a consumer wanting each of its leaves contiguous flattens the class. Which is wanted is a property of the data's use, and this specification does not decide it.

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 Type Object of this type additionally has an 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 element type's alignment, and to nothing wider. A vector width is a property of the host rather than of the type, so folding one into the layout would make the same SoA a different size on different implementations and would cost byteLength its place among the compile-time constants of 9.1. Where an 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> (11) over that field's column, and the views alias: a write through one is visible through the element operations and the reverse. A projection of a growable SoA is INVALIDATED by a growth that reallocates, and an access through it afterwards throws a TypeError exception, exactly as a reference into the same 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 invalidated, since a fixed extent does not grow.

Note

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.

16 Checked Contracts

A function declaration may carry where clauses, the WhereClauses of 5.1, between its return annotation and its body. On an ordinary function they are the compile-time bound over its generic parameters that 4.10 describes. On a compile-time-evaluable function they are more, because such a function returns a type and a predicate over that type is a checked contract: a fact about the type the builder returns, stated at the declaration and checked at every evaluation.

The production below restates the one 1.4.4 gives, with WhereClauses added (1.1).

FunctionDeclaration[Yield, Await, Default] : function BindingIdentifier[?Yield, ?Await] TypeParameters[?Yield, ?Await]opt ( FormalParameters[~Yield, ~Await] ) TypeAnnotation[?Yield, ?Await]opt WhereClauses[?Yield, ?Await]opt { FunctionBody[~Yield, ~Await] } [+Default] function TypeParameters[?Yield, ?Await]opt ( FormalParameters[~Yield, ~Await] ) TypeAnnotation[?Yield, ?Await]opt WhereClauses[?Yield, ?Await]opt { FunctionBody[~Yield, ~Await] }

Within a WhereClause of a function declaration, return is a PrimaryExpression denoting the value the function returns. It is a Syntax Error for 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 1.2.7.3 has a result, each clause is evaluated with return bound to it, and a clause that is falsy is a type error naming the builder, the arguments it was given, and the clause. A contract is never trusted. It is assumed: before specialization, where the application is deferred and no result exists, the checker takes each clause as a known fact about the deferred Type Record. The second is sound because of the first: any specialization that would falsify an assumption is stopped at the builder, before the code that relied on it runs.

Note

This is the trade C++ concepts make, recovered inside a specialization model: the body of a generic is checked against declared bounds at its declaration, and per-instantiation verification is the backstop that makes the bounds safe to believe. 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 specialization; after specialization the types are simply evaluated and compared, so nothing is lost where a program actually needs it. And a contract is checked only where the builder is evaluated, so one on a builder that nothing applies is checked nowhere: the ASSUMED reading can travel to a consumer while the VERIFIED one has not run anywhere. What closes that is a test that calls the builder, which is a library author's ordinary obligation rather than a mechanism this specification adds - and the gap is narrow, because a contract only misleads a consumer whose own code is never specialized, and code that is never specialized is never evaluated. Beneath contracts sits the cast, which is legal with a deferred application as its target and resolves at specialization into the ordinary checked conversion of Table 23; that is the honest division, with contracts stating what an author can promise and a checked cast bounding what an author merely asserts.

17 Inference Through Computed Positions

Inference reads plain parameter positions (1.4.3.4): a parameter under a deferred application is opaque, because binding it from the application's result would require running the builder backwards, and a builder is an opaque function. Where a computed type sits in a return position nothing is needed, since the argument infers plainly and the result is a function of it. This clause is the remainder, for a parameter that occurs only in computed positions, and it adds two rules that propose a binding and verify it forward. Neither runs a builder backwards and neither admits anything forward checking would not.

17.1 Literal Freshness

Known primitive object-spread contributions participate in the literal's final member facts. Null, undefined and non-String primitives contribute no enumerable own properties. A known String contributes its indexed UTF-16 code units; String length is not an enumerable contribution. Respect property replacement order and retain uncertainty for unknown Strings, Objects, getters or other unavailable contributions. A known empty spread does not conceal a missing required member.

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 any retain runtime checking. Format a Symbol diagnostic without coercing the Symbol to a String value or executing user code.

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 type error, reported against the property. Freshness is a property of the literal and not of its type, so it is lost the moment the value is bound to a name and read back: f({ a: 1, b: 2 }) is checked freshly against f's parameter, and f(o) is not.

Note

Without this an all-optional shape is a supertype of nearly everything, and width subtyping admits any literal against it, which is correct for a value that reached the position through a binding and useless for one written at the position. A value type class already imposes the rule, since a literal converted to one fills a layout that has no room for a property it did not declare (9); freshness is that rule stated for the structural case, and it is what makes the trials below discriminate at all.

17.2 Trial Specialization

A generic parameter that ordinary inference leaves unbound and whose constraint, or for a value parameter whose domain, denotes a closed candidate set is inferred by trial. A value parameter has a domain and no bound, and M: 'fast' | 'safe' names the same candidates as M: type extends 'fast' | 'safe', so the two spellings infer alike. A constraint denotes one when it is a union of types, an enum of types, or a sealed class, whose candidates are the members in declaration order, the enumerators in declaration order, and the class with its direct subclasses transitively, the set that is fixed once the declaring module finishes evaluating (1.11.8). So do boolean, whose candidates are its two literals; a literal type, whose one candidate is itself; and an array of stated extent whose element denotes a closed set, whose candidates are the tuples of that length over the element's candidates in order — the form a variadic parameter (4.4) proposes through, [2].<boolean> proposing its four tuples — each tuple counted against the budget as a constructed Type Record. Any other constraint denotes none, and a parameter so constrained does not trial.

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 (4.7), which evaluates each deferred application under the memo already specified, and each argument is checked against the result, freshly where it is a literal. A candidate every argument satisfies survives. Exactly one survivor binds the parameter; zero or more than one is a type error asking for explicit arguments and naming the constraint.

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 host-defined budget of 1.2.7.4. Exceeding it is a type error asking for explicit arguments, and because the ceiling is fixed rather than host-tunable, whether a program's inference succeeds is a fact about the program.

Note 1

A fixed number here and a host-defined one at 1.2.7.4 is not an inconsistency but a difference in what is being bounded. That budget meters arbitrary evaluation, and the clause warns that "a limit must be one the implementation can actually reach"; a number fixed here would oblige every host to reach it for a computation whose cost the specification cannot see. A trial count is the opposite: it is a sum of candidate-set sizes, and each of those is fixed by a declaration the program contains — a union's members, an enum's enumerators, a class's subclasses — so the count can be read off the source before a single trial runs, it is small, and every implementation can honour it. A limit a reader can compute and a host can always meet is worth fixing; one that is neither is not.

Note 2

This is overload resolution's logic pointed at specialization: enumerate, evaluate forward, keep what checks. It terminates in as many memoized evaluations as the constraint has members and is sound because nothing is accepted that forward checking did not accept. 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 site whose argument mentions both, as pair(A, B) does, asks for explicit arguments rather than searching a space whose size is the product of two constraints.

17.3 Declared Inverses

A compile-time-evaluable function may carry an inverse decorator naming another, which the checker consults for a parameter that ordinary inference and trial specialization both leave unbound. The declared inverse is applied once per site, memoized and counted against the budget, to the type of the argument whose computed type mentions the parameter, and what it returns is a proposal: for a builder with one inferable slot, a type; for one with several, a record keyed by parameter name, whose proposals are verified together.

A proposal binds nothing until it is verified. It is substituted for the parameter, the deferred applications are evaluated forward, and the arguments are checked against the results, which is the check an explicitly specialized call would face and is performed by the same operations. Where it holds, the proposal binds. Where it does not, the parameter stays unbound and the site is a type error asking for explicit arguments. A declared inverse that does not invert can therefore only make inference fail, never make it succeed wrongly.

Four details complete the rule. The argument's type, for a parameter reached through a REST (4.4), is the tuple of the collected arguments' types, a 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 proposal is a Type Object, or an object whose properties are Type Objects keyed by parameter name; anything else is a type error naming the builder. Where the builder is a member of an overload set, the inverse consulted is the one declared on the member the call resolves to (1.10.3), since resolution precedes binding. The two diagnostics name what they refuse: a builder with no inverse - "f declares no inverse, so T cannot be inferred through it; supply explicit type arguments" - and a proposal that fails forward verification - "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 live decoration context of the declaration being decorated, so it cannot be called after the fact or on another declaration's behalf, which is what makes it a fact of the declaration rather than a registration. Because the checker consults the association before the source text runs, the checking pass processes an inverse decoration of a function declared in the source text with its other type declarations (1.2.8), applying it to the declaration's live decoration context there; a decoration the pass has processed is not applied again when evaluation reaches it. A builder that carries no inverse decoration declares no inverse, which the pass knows from the declaration alone.

Note

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 proposal is what user code contributes, and it contributes it as an oracle whose answer is checked, which is why letting user code propose here does not reopen the boundary 1.2.7 draws.

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.

18 Operator Declarations

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 2.8.

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 (6.5), where the receiver is the primitive. This clause gives the form; 1.9.9 gives what an operator declaration means for the operators of the language, and 6.5 what it means for metadata.

PrimitiveOperatorDeclaration[Yield, Await] : primitive [no LineTerminator here] TypeName[?Yield, ?Await] TypeParameters[?Yield, ?Await]opt TypeParameters[?Yield, ?Await]opt { OperatorDefinitionList[?Yield, ?Await]opt } OperatorDefinitionList[Yield, Await] : OperatorDefinition[?Yield, ?Await] OperatorDefinitionList[?Yield, ?Await] OperatorDefinition[?Yield, ?Await] OperatorDefinition[Yield, Await] : staticopt operator OperatorName OperatorTypeParameters[?Yield, ?Await]opt ( FormalParameters[~Yield, ~Await] ) TypeAnnotation[?Yield, ?Await]opt { FunctionBody[~Yield, ~Await] } staticopt operator OperatorName OperatorTypeParameters[?Yield, ?Await]opt ( FormalParameters[~Yield, ~Await] ) TypeAnnotation[?Yield, ?Await]opt ; operator Type[?Yield, ?Await] ( ) TypeAnnotation[?Yield, ?Await]opt { FunctionBody[~Yield, ~Await] } * operator ... ( ) TypeAnnotation[?Yield, ?Await]opt { GeneratorBody } * operator ... ( ) TypeAnnotation[?Yield, ?Await]opt ; OperatorTypeParameters[Yield, Await] : .< GenericEntryList[?Yield, ?Await] ,opt > OperatorName :: one of + - * / % ** == < > <= >= & | ^ ~ << >> >>>

An OperatorTypeParameters is classified and checked as the TypeParameters of a method definition is (1.4.3.1): 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 PrimitiveOperatorDeclaration is a Declaration; 6.1 restates that production with it added. An OperatorDefinition is a ClassElement, restated below, and an interface member, so a class declares operators among its methods and an interface declares the operators a type must have, which is what makes an operator expressible as a constraint. The form without a body is the declaration-only one an interface and a metadata-only definition take. The cast production is the implicit cast operator of 6.5.

The ... productions are the typed iteration protocol: a generator operator whose declared return type types the elements it yields, and whose declaration defines the class's 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.

ClassElement[Yield, Await] : MethodDefinition[?Yield, ?Await] static MethodDefinition[?Yield, ?Await] FieldDefinition[?Yield, ?Await] ; static FieldDefinition[?Yield, ?Await] ; ClassStaticBlock ; OperatorDefinition[?Yield, ?Await] abstractopt ClassElementName[?Yield, ?Await] ( UniqueFormalParameters[?Yield, ?Await] ) TypeAnnotation[?Yield, ?Await]opt ;

The last alternative is the abstract method of 1.11.6, restated here because a later restatement carries a production's complete form (1.1). The abstract keyword is optional: what makes the member abstract is the absence of a body, and the class being declared abstract is what permits that absence. Writing the keyword is accepted and changes nothing.

The alternative is written out rather than expressed as a MethodDefinition with its body omitted so that the forms it does not reach are visible in the grammar: it takes a ClassElementName and a parameter list and nothing else, so static, a constructor, and the get, set, async and generator forms of MethodDefinition are outside it. 1.11.6 gives the reason for each.

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 site uses the application spelling, and the reason is lexical rather than principled.

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 (1.10.5), and two applicable definitions with the same parameter types and no expected return type is an ambiguity and a type error. A primitive block adds to a global operator table; two definitions for one pair of types is a type error at the second declaration. The pair is the type the block names and the type of the definition's parameter, and the return type does not distinguish two entries of the table, since dispatch through it selects by the right operand alone. The second declaration may be in a later source text: the checking pass of a Module runs after its dependencies have evaluated (1.2.8), and their blocks are in the table by then. 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 (41). Resolution reads the left operand's type and searches it for a signature describing the operation, and a pipeline has no operation 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, and is why the exclusion is stated here, where resolution is defined, rather than only where the operator is.

Note

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 } covers every type that defines scalar multiplication, including ones declared later. 2 * v reaches it because viability filtering precedes ranking (1.10.3.1): a 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 type error, which is correct, since it is not defined. The rule this states is general. It is a type error to apply an arithmetic, bitwise or ordered operator, unary or binary, to an instance of a typed class whose class declares neither that operator nor a primitive conversion of its own, a valueOf, toString or Symbol.toPrimitive other than those of %Object.prototype%: the conversion those inherited methods perform would succeed and produce NaN or a String, which is not the operation the program wrote. A + 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 typed is unaffected.

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 design draws is to alias for storage and accessors and to declare a class where the type carries operators.

19 Vector Comparisons

A comparison between two vectors of one shape yields one lane per input lane, and what it yields for each is decided by the expected type, since the comparison is overloaded on its return type (1.10.5). Three results are defined: the wide mask, a vector of the boolean type of the same width as the compared element, so a float32x4 comparison yields a boolean32x4 of lanes all-set or all-clear; the compact mask, a bit vector of one bit per lane; and the compared vector type itself, its matching lanes all-ones. Left with no expected type the expression is ambiguous among them and is a type error, so the result's type is written.

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 false when either operand is NaN and an unordered one is true, and a signaling comparison raises the invalid-operation flag where a non-signaling one does not. <, <=, >, >=, 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 false and NaN != NaN is true. All are non-signaling, this specification having no floating-point exception surface to signal into. A comparison of integer lanes has no such choice, integers having no NaN. The predicates with no operator are not exposed; a program requiring the ordered test alone writes 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.

Note

The three forms are three instructions, which is the point of the overloading rather than a consequence of it: the wide mask is what a comparison natively produces on SSE and NEON, the compact mask is an AVX-512 mask register, and the all-ones vector is what a program blends with. Choosing among them by the annotation lets one spelling reach each without an intrinsic per form, and makes the ambiguity rule load-bearing rather than pedantic: an unannotated 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 1.5.8 rather than here: a bit vector converts to and from an integer whether or not a comparison produced it. 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 9, which are sub-byte integer members of a class.

20 Complex Numbers

complex.<T> is a value type holding a real and an imaginary component of the real numeric type T. The width-named shorthands count total bits, following the convention of the numeric libraries the design cites, so 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 type error, written _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.

ToBoolean of a complex is false where both components are zero and true otherwise, so complex(0, 0) is falsy as every other numeric zero is. A complex is not one of the existing language types, and ToBoolean of an object is otherwise always true, so this is stated rather than inherited: without it the origin would be the one numeric zero that is truthy.

An imaginary literal (1.4.1.3) has the type 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 (1.5.19), so an imaginary literal in a 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 Table 22's as it is for that component.

Note

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 value type, a complex copies on assignment and lies inline, so a [].<complex128> is the interleaved buffer an FFT expects and 9 gives it the layout to be one.

21 Ranges

It is a type error if a present endpoint has a participating Static Type restricted to symbol, null or undefined, whose intrinsic domains supply no range ordering. A union establishes this failure only when every alternative is excluded. An omitted endpoint is not a present undefined value. Unknown types and unresolved class or metadata ordering capabilities do not establish this proof. No ordering operator is evaluated by this judgment.

A range is a value type class over an ordered element type and a bound at each endpoint it has. There are four shapes: 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 shape has, so the four intervals of a two-endpoint range, the closed, the two half-open, and the open, are the four pairs and nothing further is expressible. The four-way name of a pair is an Interval, which a range exposes and a diagnostic prefers over the parameterization, and which is never itself a parameter: a shape with one endpoint would otherwise carry a bound for an endpoint it does not have. A range is a value, so it allocates nothing and copies; and it is an expression, so it appears wherever one does, including a case label and an argument list, without further rule.

21.1 Range Endpoint Domains and Membership

The type parameter of a concrete range shape is its stored endpoint domain. Each present endpoint, including an excluded endpoint, is a value admitted by that domain. A fresh range expression in a range-family construction context reads its present endpoints at that context's domain using ordinary literal propagation and checked admission, in source order and once each. A statically established failure is an early type error, including in an unused body. A runtime-only failure occurs when that endpoint is evaluated; no range is published and contextual state is restored. A range obtained through a binding or call retains its domain and is not silently rebuilt by a target annotation.

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 ordered range endpoints.

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 any when none exists. Reflection reports the corresponding concrete range shape, this domain, and its actual bound arguments; it never derives the domain from only the first of two endpoints.

Membership in a concrete range shape checks the shape, specified bound arguments, and invariant endpoint-domain identity. A bare shape or an any domain is an erased query. Membership in RangeBounds.<T> instead checks that the value is a range and that each present endpoint already satisfies T; it does not change the concrete specialization. A full range satisfies this view vacuously. These judgments perform no endpoint conversion, ordering comparison, iteration, or public endpoint property read, and grant no reference capability. Empty and descending ranges retain their endpoints and receive no exemption from these rules.

Note

(0..<5) is Range.<uint8> is false, while a fresh const r: Range.<uint8> = 0..<5 constructs typed endpoints. const r: Range.<uint8> = 0..<256 is a type error because the exposed end is not 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 set in another, so an empty range is contained in every range and the full range contains every range. intersect is the point-set intersection of two ranges. It is commutative and associative with the full range as its identity; where two ranges share an endpoint the exclusive bound is the one the result carries; and where they are disjoint the result is the greater start with the lesser end, which is descending and therefore empty, so an empty intersection has no separate representation. The shape of an intersection follows from its operands rather than from either one, which is why the result is described by a 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 set as that range.

21.2 Domain-Preserving Range Selection

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 integer values; no intermediate Number conversion is permitted. A pair of class endpoints is compared using the left endpoint's declared < first and, if that is false, the right endpoint's declared < with exchanged operands. When both are false, the total-order contract makes the endpoints equivalent. An incomparable pair throws a TypeError. Abrupt comparison completion propagates immediately, before any result range is published. These operations do not invoke ordinary coercion methods, mutate the inputs, or grant reference capabilities.

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 any contributes no domain constraint and is neutral in this calculation; a full range with a specific domain participates normally. Thus equal domains remain equal, declared union domains remain present, and two RangeFull.<T> values intersect to RangeFull.<T>. The concrete shape and bounds follow the selected endpoints. The operation can be described as intersect<U>(other: RangeBounds.<U>): RangeBounds.<T | U>, subject to mutual ordering and the full-any identity rule. This does not change either input's concrete specialization.

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 true. Otherwise test lower-bound coverage and then upper-bound coverage, stopping at the first failure. At equivalent bounds, an open outer bound does not cover a closed inner bound. A present endpoint is never treated as absent merely because its type lacks numeric conversion. A scalar NaN is not contained.

Note

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 element type defines scalar multiplication, which an ordering does not imply: a range over 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.

21.3 Numeric Range Scaling and Reflection

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 number, bigint, sized signed and unsigned integers, binary float16, float32, float64 and float128, decimal32, decimal64 and decimal128, and rational types, including their numeric metadata parameterizations. This interval operation does not select an iterator item carrier.

A call performs these steps in order. The ordinary evaluation of the receiver and argument expressions precedes the call.

  1. Require a range receiver; otherwise throw a TypeError. Read only its internal endpoints, bounds and stored or inferred endpoint domain. Require that domain to be a supported numeric type, a union consisting of supported numeric domains, or any. Require every present endpoint to have a supported numeric type and a finite real value or infinity. Otherwise throw a TypeError.
  2. Require the supplied factor to be a finite real numeric value. Omission, undefined, nonnumeric values, complex values, NaN and infinities throw a TypeError. Do not perform ordinary object or String conversion. Read the exact mathematical factor, independently of its representation or the endpoint widths.
  3. If the source is empty under 21.2, or the factor is exactly positive one, return a copy retaining the original endpoint values, bounds and domain. Do not repeat endpoint admission.
  4. If the factor is zero of either sign, select a zero type: the concrete numeric source domain if there is one, otherwise the original start's type if present, otherwise the original end's type if present, otherwise number for an endpoint-free any domain. If no type is determined, throw a TypeError. Construct canonical positive zero in that type and admit it by the rule below once. Return a closed singleton using the same admitted zero at both endpoints and the original source domain.
  5. For a non-zero factor, process the original start first and the original end second. An absent endpoint remains absent. A present infinite endpoint retains its numeric type and presence, with its sign multiplied by the factor's sign. For a finite endpoint, compute the exact mathematical product and represent it by the rules below. Admit each manufactured endpoint before processing the next; propagate the first abrupt completion immediately.
  6. If the factor is positive, publish the resulting endpoints in their original roles. If negative, exchange their roles and the corresponding bounds, including absence. Every result retains the original source endpoint domain, including a stored or inferred union. No result range is published before all required endpoint admissions succeed.

An integer product must be integral and representable in that endpoint's original base; otherwise throw a RangeError. There is no truncation, wrapping, saturation or promotion. A rational product is exact, reduced to its canonical fraction, and checked against its existing numerator/denominator representation limits; a failure throws a RangeError.

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 finite product whose magnitude exceeds the base's greatest finite magnitude throws a RangeError, rather than manufacturing infinity. Binary subnormals participate. A non-zero decimal product whose adjusted exponent is below the base's supported minimum throws a RangeError; this operation does not introduce another decimal format. For binary zeros, including a non-zero product rounded to zero, retain the sign of the mathematical multiplication; the zero-factor singleton rule instead uses positive zero. These rules require no intermediate conversion through Number and no enumeration of interval members.

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 mathematical value, binary zero sign, and, for a decimal, coefficient and exponent. If it changes that representation, throw a RangeError. After successful admission and representation checks, the manufactured endpoint carries its full admitted numeric parameterization, even when the admission judgment accepted an otherwise bare base value. Attaching that already-admitted type does not invoke admission again and must preserve the same representation. Subsequent membership and reflection recognize the carried type by their ordinary rules; admission may not rescale or requantize the endpoint. For zero scaling there is one admission; for empty or identity copies there is none. Source ranges are never mutated, earlier hook side effects are not rolled back, and a later call can reuse the source. Success and failure confer no reference authority; borrowed locations retain all existing liveness checks.

Note

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 interval arithmetic, custom arithmetic domains and numeric iteration are separate operations; this clause does not supply a common promoted domain for two input ranges.

21.4 Additive Numeric Interval Arithmetic

The built-in + and - operations on two ranges compute coordinate addition and subtraction in the supported real numeric representations of 21.3. They use one agreed numeric endpoint type. These operations compute exact mathematical bounds followed by checked representation; arbitrary declared scalar operator bodies are not invoked as an interval enclosure algorithm.

After ordinary left-to-right operand-expression evaluation, perform these steps in order.

  1. Inspect the left range's stored or inferred domain and present endpoint types, then the right's. Read internal values without public property access, ordinary coercion, or user ordering calls. A domain must be a supported concrete numeric type, including a numeric metadata parameterization, or any. A union or other domain throws a TypeError. Every present endpoint must have a supported numeric type and be finite real or infinity; otherwise throw a TypeError.
  2. Each concrete domain and each present endpoint's full numeric type contributes a type. An any domain contributes only its present endpoint types, so an endpoint-free full-any operand contributes nothing. Require all contributed types to be identical by SameType; otherwise throw a TypeError. Let the result domain be the agreed concrete type, or any if there are no contributions. No type is selected by magnitude, union-member order, or implicit numeric promotion.
  3. Determine emptiness of the left operand, then the right if needed, by 21.2. If an operand is empty, return a copy of that operand's endpoint values and bounds carrying the agreed result domain, preferring the left when both are empty. Do not manufacture or readmit its endpoints. Domain agreement is required even for these exits.
  4. For addition, calculate the lower result from the two lower endpoints. For subtraction, calculate it from the left lower endpoint minus the right upper endpoint. Represent and admit that endpoint by the rules below before proceeding.
  5. For addition, calculate the upper result from the two upper endpoints. For subtraction, calculate it from the left upper endpoint minus the right lower endpoint. Represent and admit that endpoint by the rules below.
  6. Publish the result range with the calculated endpoints, bounds and agreed domain. Propagate any earlier abrupt completion immediately; no result is published on failure.

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 finite values, compute their exact mathematical sum or difference with no intermediate Number conversion. Integer and rational results obey their exact representability checks; floating results round outward according to the final lower or upper role, with the same finite-overflow, binary-subnormal and decimal adjusted-exponent rules as numeric scaling. An exact result bound is open if either contributing bound is open; otherwise it is closed. If floating rounding moves a bound, make that bound closed.

For present infinities, subtraction first reverses the right infinity's sign. Adding a finite value and infinity, or two infinities of the same sign, produces a present infinity of that sign in the agreed type. Opposite infinities in addition, and like infinities in subtraction, throw a RangeError at that endpoint calculation. Do not publish a NaN endpoint or replace an indeterminate calculated bound with absence. Missing bounds contribute no calculation, so their propagation is independent of arithmetic on explicitly present infinities. The empty-image exit precedes endpoint arithmetic.

Exact cancellation produces positive binary zero. When both finite contributors are zero, addition produces negative zero only if both are negative zero; subtraction produces negative zero only for negative zero minus positive zero. Other exact zero results are positive zero. Decimal addition/subtraction uses the reduced coefficient/exponent representation of the calculated result. Neither operator has a zero-operand identity shortcut; manufactured endpoints receive ordinary admission even when their mathematical values equal old endpoints. Numeric scaling's explicitly specified identity and reflection rules remain unchanged.

Apply the agreed full numeric parameterization's existing checked admission to every manufactured endpoint. Propagate its exceptions and require it to preserve the calculated mathematical value, binary zero sign and decimal coefficient/exponent, as in numeric scaling; a change throws a RangeError. Do not drop, merge or infer new endpoint metadata from arbitrary scalar operator bodies. Earlier hook effects retain their ordinary semantics, with no rollback. Source ranges remain unchanged and reusable. Neither the operation nor its failure extends reference lifetime or grants reference capabilities.

Note

An erased range with uniformly typed endpoints can establish the agreed carrier. A full-any range beside a byte range yields RangeFull.<uint8>; two full-any ranges yield 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 21.5; addition/subtraction alone does not determine the unit type of a product or the treatment of a divisor crossing zero. Custom interval enclosure declarations require their own contract.

21.5 Multiplicative Numeric Interval Arithmetic

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 units from metadata. The agreed full numeric type is the result domain and its ordinary admission applies to manufactured bounds. These are coordinate interval operations, independent of the representable-member lattice used by iteration or 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.

  1. Validate both operand domains and present endpoints, establish one identical contributed numeric type, and select the first empty operand, exactly as the first three steps of 21.4. This includes full numeric parameterizations, rejection of unions, the erased any contribution rule, the left-first empty preference, and copying an empty operand with the agreed domain without readmission. Do not read public properties, invoke ordinary coercion or call user ordering operators.
  2. For division, if the right operand has neither a strictly positive lower endpoint nor a strictly negative upper endpoint, return an endpoint-free range in the agreed domain. An open zero endpoint does not satisfy this guard. This exit follows domain/empty checks and performs no endpoint admission, including when the numerator is zero.
  3. For multiplication, if either operand has a present infinite endpoint and the other contains zero or has a zero endpoint, throw a RangeError. This includes zero in the interior and excluded zero or infinity endpoints. For division, if both operands have present infinite endpoints, throw a RangeError, including excluded endpoints. Missing endpoints do not count as present infinities. Complete this indeterminate-coordinate preflight before any result endpoint representation or admission.
  4. Calculate exact candidates and select a lower and upper extreme by the rules below. This calculation performs no endpoint admission or intermediate representation rounding. Represent and admit the selected lower endpoint first, then represent and admit the selected upper endpoint. Stop immediately on abrupt completion.
  5. Publish the resulting range with the agreed domain, resulting endpoint presence and bounds. No partial result is published, and neither source range is mutated.

For candidate calculation only, describe each present finite endpoint by its exact rational mathematical value, binary zero sign when applicable, and whether its bound is closed. Describe a present infinity by its sign and closedness. Describe a missing lower or upper endpoint by a private negative or positive infinite limit respectively, marked absent and not closed. These private coordinates are not values in the endpoint domain and are never admitted or stored as new infinity endpoints.

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.

  1. Two finite coordinates contribute their exact mathematical product or quotient. Division's guard ensures that a finite denominator coordinate is non-zero. Normalize its denominator to be positive.
  2. For multiplication, zero times a missing infinite limit contributes signed finite zero. Other products involving infinite coordinates contribute an infinity of the product sign; mark the candidate absent if any infinite contributor is absent. The preflight has already rejected products of zero and a present infinity.
  3. For division by an infinite coordinate, a finite numerator contributes signed finite zero. An absent numerator limit divided by a present infinite denominator also contributes signed finite zero. An infinite numerator coordinate divided by an absent denominator limit contributes no candidate. Two present infinities have already been rejected. These rules impose no constraint from a doubly unbounded quotient corner; other corners supply the extrema.
  4. For division with an infinite numerator coordinate and a finite denominator, contribute an infinity of the quotient sign and retain the numerator's absence mark.

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 finite zero, select negative binary zero for the lower result if any tied candidate has negative sign, and positive binary zero for the upper result if any has positive sign; retain the sole sign when all ties agree. Decimal zero is canonical.

In addition to a closed corner, interior values can establish attainment of a selected extreme. A finite zero extreme in multiplication is attained when either operand contains zero. A finite zero extreme in division is attained when the numerator contains zero, or the divisor has a closed present infinite endpoint and the numerator has a finite coordinate value. A present infinite extreme in multiplication is attained when a closed present infinite endpoint in either operand can combine with a non-zero coordinate of the appropriate sign in the other operand; that coordinate may itself be infinite. A present infinite extreme in division is attained when a closed present infinite numerator endpoint can combine with a finite non-zero divisor coordinate of the appropriate sign. These tests use the coordinate intervals' intrinsic order and stored bounds, not enumeration or user callbacks. An absent result remains absent regardless of attainment. An exact present bound is closed if attained by any of these rules, otherwise open.

Finite selected extrema use the checked representation rules of 21.3: integers must be integral and fit, rational fractions reduce before their numerator/denominator width check, and binary/decimal floating bounds round outward in their final lower or upper role. A moved floating bound is closed. Finite overflow throws a RangeError; binary subnormals and the existing decimal adjusted-exponent floor participate as specified there. A non-zero finite value rounded to binary zero retains its exact sign. Decimal results use the reduced coefficient/exponent form. No reciprocal, unselected corner or intermediate result receives these checks or admissions. Neither multiplication nor division has an identity-copy exemption.

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 RangeError. Missing endpoints receive no admission. Although candidate selection and indeterminate preflight precede representation, lower representation/admission precedes upper representation/admission: an upper overflow does not suppress earlier lower hook effects. Those effects are not rolled back. Sources remain reusable after failure. No success, failure, empty copy or full-range fallback grants reference authority or extends the lifetime of a borrowed location.

Note 1

(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.

Note 2

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-interval API. The result always has one RangeBounds view in the agreed domain. Custom scalar overloads or metadata alone do not establish a different enclosure or dimensional-arithmetic contract.

Note 1

The bound is per endpoint rather than one four-valued interval because a shape with one endpoint has one bound, and a RangeFrom parameterized by an interval would admit a type no value inhabits while still being nameable by an overload. The split also gives each marker in a literal one parameter to set, makes an operation that replaces one endpoint a substitution rather than a computation over an enumeration, and lets a signature fix one bound and quantify the other, which a single parameter could express only as a union of interval values.

scale is conditional on the element type rather than declared for every range because an interface that advertises a member no instantiation of it can run is not a contract. The mechanism is the ordinary one for a member that exists on some instantiations, narrowing a parameter of a partial declaration, which 1.5.12 already uses to declare the mask operations on the vectors whose lanes are bits and on no others. A narrowing to an interface differs from that one only in admitting every element type that satisfies the interface rather than a single lane type.

21.6 Range Literals

It is a type error to use a value statically known to be a built-in range as a range endpoint, or as an operand on the built-in path of <, <=, >, or >=. A range's endpoints being ordered does not make the range itself ordered. A range literal has the corresponding range Static Type even when no outer contextual type is supplied; parentheses and participating constant aliases preserve it. Applicable declared and derived operators are considered first, so a declared comparison may accept a range argument. A union proves rejection only when every possible dispatch path establishes the prohibited built-in operation; an unknown left operand can still supply an applicable operator. Equality, containment and other interval operations are unaffected. The judgment uses the resolved built-in identity, not a user class's spelling, and dynamic failures retain their runtime checks.

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 interval, and the open-ended forms 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 live. Claiming <.. 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 integer literal followed by .. 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 (41) binds looser still, so 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 TypeError rather than the false an ordinary object's comparison would yield. Answering a meaningless comparison with false is the failure this rejection exists to prevent, and it is the only reading the family costs, so the cost is paid where it can be seen.

The value carries its start and its end, and its bounds are in its type rather than in a field. Its 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 finite progression nowhere exactly as an absent end does. length is the exact BigInt count of the members of a bounded integer range, which is one more than the difference of its endpoints less one for each endpoint whose bound excludes its own value, and never less than zero; and 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 21: over a bounded integer interval the step is 1 and implicit, and step supplies a step where the interval is not one an implicit step fits or widens the stride where it is. A range with no start is not iterable, and a range whose endpoints are not a whole integer interval is a TypeError to iterate without an explicit step, rather than a silent choice of 1.

21.6.1 Static Types of Range Properties

A statically established built-in range-family receiver contributes the normal-result property types in Table 61. Recognize the resolved intrinsic identity, not the spelling of a user declaration. Let T be its endpoint type, S and E its bound arguments, B the intrinsic Range.Bound enum, I the intrinsic Range.Interval enum, and U the type undefined. Retain T's complete numeric metadata, generic parameter identity or class identity. An erased any endpoint remains any under the ordinary union rules. Existing generic-argument requirements still apply.

Table 61: Range property contributions
ReceiverstartendstartBoundendBoundinterval
Range.<T, S, E> and its aliasesTTB, narrowed to S when knownB, narrowed to E when knownI, narrowed to the derived member when both bounds are known
RangeFrom.<T, S>TUB, narrowed to S when knownUU
RangeTo.<T, E>UTUB, narrowed to E when knownU
RangeFull.<T>UUUUU
RangeBounds.<T>T | UT | UB | UB | UI | 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 enum, not an unrelated Number literal or a newly declared enum. The interval members for closed/closed, closed/open, open/closed and open/open are respectively Closed, ClosedOpen, OpenClosed and Open. When a bound argument is not established, retain the whole corresponding enum; do not evaluate a generic argument to force a singleton.

Use the same contribution for dot access, a statically established String property key, optional-chain access and object destructuring. Join every alternative of a statically established finite key domain; join receiver alternatives under the ordinary union-member rules. Optional-chain short-circuiting contributes undefined by the existing rules. An unknown key or unrelated property retains the existing gradual/member judgment. This table does not introduce types for untyped bindings or parameters independently of their existing participation rules.

These are normal-result types, not proofs that the read completes normally. In particular, length retains the preconditions in 21.8, even for a statically known range. Empty and descending ranges retain their endpoint types. The judgment neither calls a getter, examines endpoint values, orders endpoints, acquires an iterator nor grants reference authority.

The public read uses ordinary property access and its effects and abrupt completions. Its Static Type does not prove the identity or behaviour of a replaceable getter. Under 1.7.9.1, retain any required receiving or return boundary unless an independent proof establishes that the actual property implementation supplies that type. A guarded implementation may establish such a proof without changing observable property access or reference liveness. A replacement getter returning an incompatible value cannot silently initialize a binding whose required contract rejects that value.

21.6.2 Static Types of Derived Ranges

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, finite key domains, optional chains, extracted method values and receiver alternatives under the ordinary member and call rules. It neither binds the receiver to an extracted function nor introduces a typed contribution into an otherwise gradual binding.

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 index type or a factor to T as a consequence of this result judgment. Unsupported endpoint domains, invalid arguments and unrepresentable bounds may throw despite the known result view. Explicit iterator results are specified in 21.6.4; terminal forwarded helpers are specified in 21.6.6.

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 any, contribute RangeBounds of the other endpoint type. If neither condition holds, this rule supplies no common result contribution. It does not unify different type parameters, strip metadata, promote numeric families or choose a union as an arithmetic carrier. A whole operand of any or unknown type does not establish built-in range dispatch and is not the endpoint-erasure case.

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 normal completion or of the actual concrete shape. The runtime algorithms retain their domain validation before empty, zero and singular exits, full endpoint admission, failure order and reference-liveness checks. The actual value has the shape produced by the operation. A manufactured numeric endpoint retains its completed full-domain admission as specified for numeric scaling; an inferred receiving boundary must not lose that carried identity or repeat validation solely because the result gained a Static Type. Its static RangeBounds view contributes possible endpoint absence under 21.6.1; do not copy the operand's concrete shape or bounds into this result contract. Two-ended division can produce a full range, a negative scale can exchange one-ended shapes, and zero scaling can produce a closed point.

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 1.7.9.1 unless an independent proof establishes the actual implementation's result contract. A failed inferred initialization preserves prior effects, leaves its binding uninitialized and does not permit subsequent uses to trust an unchecked replacement result. Optional short-circuiting and reference liveness retain their ordinary rules.

21.6.3 Static Results of Range Selection Methods

A statically established built-in range-family receiver contributes a callable boolean normal-result contract for contains. Its candidate position is any, with omission supplying undefined under the runtime method's existing rules. A range candidate selects the subset operation; another candidate selects scalar containment. This result judgment neither converts the candidate to the receiver's endpoint type nor introduces an ordering-capability preflight or a narrowing predicate. Existing exact heterogeneous numeric comparison, declared class ordering, NaN refusal, short-circuiting, failure and evaluation-order rules remain applicable.

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 shape with a specific domain participates in the union normally. A RangeBounds.<any> view does not prove absence and is not neutral.

A candidate whose range view or endpoint domain is unknown contributes any; the established method still contributes a RangeBounds.<any> result. For an argument union, include every alternative's contribution and canonicalize their endpoint union. An unresolved or cyclic alternative contributes any and cannot be omitted. Receiver alternatives use the ordinary union-member rules; a whole unknown receiver supplies no built-in contract. This is a normal-result judgment, not a proof that the argument is a range, that ordering succeeds, or that any result exists.

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 contribution through generic substitution without capture by its own type parameter. Its actual normal result retains the selected endpoints, concrete shape, bounds and reflected domain specified by 21.2. Its static view exposes possible endpoint absence under 21.6.1.

Apply the same contracts to dot access, established computed String keys, finite key domains, optional chains and extracted method values, including object patterns, under the ordinary member and call rules. Extraction does not bind a receiver. This judgment creates no typed participation for an otherwise gradual receiver. It evaluates no endpoint, getter, comparison or metadata hook and grants no reference capability.

These contracts do not prove the identity of a replaceable method. Preserve ordinary property access, argument evaluation, invocation and abrupt completions. Under 1.7.9.1, keep required inferred-initialization and return admission unless an independent proof establishes the actual implementation's result contract. A replacement returning an incompatible result cannot initialize a checked binding; prior effects remain and that binding stays uninitialized after failure. Optional short-circuiting and reference liveness retain their ordinary rules. This clause does not supply iterator or callback result contracts.

21.6.4 Static Results of Explicit Range Iteration

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 synchronous iterators with item contribution I, terminal contribution undefined, and an optional any input to next. The numeric cursor ignores incoming values. Omission of by supplies undefined and retains the numeric difference validation failure; it does not select implicit iteration. Its next result is the discriminated union { value: I, done: false } | { value: undefined, done: true }. Its self-iteration method contributes an Iterator with those same three protocol arguments. The terminal contribution is not void and a direct .value read must include possible exhaustion.

Derive I without evaluating endpoints. For a concrete range shape, a complete supported numeric endpoint domain contributes itself, including metadata. The supported bases are number, bigint, sized int and uint, and float16, float32, and float64. A literal endpoint type contributes the endpoint-fallback contribution of its base. A union contributes the canonical union of every arm's endpoint-fallback contribution. A cyclic, unresolved, erased or unsupported alternative contributes any and cannot be discarded. This judgment supplies an upper bound on normal items, not proof that acquisition or any request completes normally.

The endpoint-fallback contribution of a supported typed-numeric or BigInt type is its full type, including metadata. A Number type, including a Number parameterization, instead contributes number; a Number endpoint does not select a typed-number carrier in 21.7. Apply the same literal, union and unknown rules recursively. 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 shape with a Number metadata domain retains that domain in I, while a RangeBounds view of it contributes number. Unresolved generic parameters contribute any at this judgment; a destination annotation does not select, reconstruct or retag a stored domain.

Apply these contracts to dot access, established computed String keys, finite key domains, optional chains and extracted methods, including object patterns, under the ordinary member and call rules. Extraction does not bind a receiver. A whole gradual receiver gains no participation from its method's spelling. The result satisfies the structural iteration protocol; an implementation's internal helper carrier must not require resolving an unwritten global constructor. Existing helper contracts continue to apply to the helper-bearing result. This clause does not add those methods to arbitrary protocol objects.

The contracts do not prove intrinsic method identity. Retain required inferred-initialization and return admission under 1.7.9.1, ordinary property access, argument evaluation and invocation. An incompatible replacement cannot initialize a checked binding; prior effects remain and a failed binding stays uninitialized. The result contract does not consume the iterator during checking or admission, add reference authority or extend reference lifetime. Numeric item admission, metadata hooks, fused exhaustion, failure order and step/origin validation remain as specified by 21.7. Default range iteration follows 21.6.5; terminal forwarded helper callbacks are specified in 21.6.6.

21.6.5 Default Range Iterator Contracts

A statically established built-in range-family receiver contributes a callable [Symbol.iterator] member with no parameters. Its normal result has the helper-bearing synchronous cursor contract of 21.6.4: item contribution I, terminal contribution undefined, and optional any input to 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 21.7's rules. A known result contract does not prove successful acquisition, a non-empty sequence or termination.

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 set is not an item set: a ClosedRange.<1 | 3> can yield 2. Derive a consumer's yielded contribution from I, and a delegation's terminal contribution independently from undefined. Keep existing literal-loop counter facts and unresolved protocol uncertainty. A protocol contribution does not by itself promote an ordinary Array literal into typed storage or establish an Array's replaceable iterator from its indexed element type.

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 interval. Receiving admission of a metadata-bearing item retains ordinary validation effects after 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 1.7.9.1. Do not freeze or wrap a range, cursor or method to establish it. Checking executes no hook or next call. An incompatible replacement retains prior effects and leaves a failed initialization uninitialized. No part of this judgment grants reference authority or extends a reference lifetime.

A simple loop binding whose type was inferred from the source protocol receives the ordinary inferred-binding admission before the body executes, with captured generic parameters substituted in the active type context. Retain that binding contract for subsequent stores. Explicit annotations retain their own checks, explicit any remains gradual, and destructured bindings use their ordinary extraction checks. Reference iteration retains its separate location and liveness rules. A failed loop-entry admission follows the existing abrupt-completion path, including environment restoration and iterator closing; a downstream elision may not rely on an inferred binding that was never admitted.

21.6.6 Terminal Range Helper Contracts

A statically established built-in range-family receiver contributes the following callable contracts, where I is derived by 21.6.4, including its endpoint-fallback and uncertainty rules. This contribution does not assert successful acquisition, traversal or callback completion.

Table 62: Terminal range helpers
MethodParameterNormal result
toArrayNone[].<I>
forEachcallback: (value: I, index: uint64) => voidvoid
somecallback: (value: I, index: uint64) => booleanboolean
everycallback: (value: I, index: uint64) => booleanboolean
findcallback: (value: I, index: uint64) => booleanI | undefined

Apply these member contracts to established computed String keys, finite key domains, optional access and extracted method views under the ordinary member and call rules. Extraction does not bind a receiver, and a whole gradual receiver gains no participation from the spelling. The callback positions provide contextual types under 1.6.5. A fresh literal's adopted bindings are enforced at initialization, including checked admission of the helper's counter to uint64; an already created uncontextualized function or a written any parameter is not retroactively annotated. Contextual void requires nothing of a discarded callback result. A predicate's contextual Boolean return remains a checked result boundary; an existing function with an explicit any return does not acquire that declaration by being passed as an argument.

find includes undefined even for a non-empty range because the predicate may never match. The void result of forEach does not denote a value a binding may hold. The original forwarding methods acquire a fresh numeric forward iterator and read the corresponding helper by ordinary property access. 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 21.7.

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 1.7.9.1. A replacement that retains a contextually typed callback retains that callback's binding obligations on later calls. Failed admission preserves prior effects and leaves an unsuccessfully initialized binding uninitialized. Checking invokes no callback or iterator and grants no reference authority or extended lifetime. Lazy mapping and filtering are specified in 21.6.7, and flattening in 21.6.9; reduction recurrence follows 21.6.8. Default range iteration follows 21.6.5; argument-routing judgments remain separate.

21.6.7 Lazy Range Mapping and Filtering Contracts

A statically established built-in range-family receiver contributes callable contracts for map and filter. Let I be its item contribution under 21.6.4, including endpoint-fallback and uncertainty rules. The mapper takes callback: (value: I, index: uint64) => U, where U is its generic output type inferred from the callback under 1.6.5. Its normal result is a helper-bearing synchronous iterator with item U. The filter takes 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 contribution undefined and an optional any input to 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 constructor, and retain the applicable synchronous helper contracts for subsequent calls.

These judgments also govern mapping and filtering of an established helper-bearing synchronous iterator, using its established item contribution in place of I. They do not add helpers to arbitrary structural iterator Objects. A whole gradual receiver gains no participation from a method name. Established computed names, optional access and extracted method views use the same contracts under the ordinary member rules; extraction does not bind a receiver.

The original range methods acquire a fresh numeric iterator and reach the corresponding helper by ordinary property access. Creating the helper does not invoke the callback. Traversal invokes it on demand, and filtering can consume several source items before yielding. An async mapper's returned Promise remains an item of the synchronous helper; it is not assimilated. Acquisition, callback failures, exhaustion, iterator closing and unbounded traversal retain their runtime rules. A result contract does not guarantee successful or terminating traversal.

Contextual callback binding admission, written annotations and Boolean predicate result obligations follow 21.6.6. The views do not prove intrinsic identity: retain ordinary lookup, effects and required inferred-initialization and return admission under 1.7.9.1. A replacement that retains a callback retains its adopted binding obligations. An incompatible replacement result cannot initialize a checked binding; prior effects remain and that binding stays uninitialized. No contribution or admission consumes the iterator, executes the callback, grants reference authority or extends a lifetime. Flattening follows 21.6.9; reduction recurrence follows 21.6.8. Default range iteration follows 21.6.5.

21.6.8 Range Reduction Contracts

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 contribution of 21.6.4, including its endpoint-fallback and uncertainty rules. An established helper-bearing synchronous iterator uses its established item contribution instead. This judgment does not add helpers to structural iteration interfaces or to a whole gradual receiver.

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-any first parameter type anchors U; an existing callback with one established nongeneric signature may contribute its first parameter type on the same terms. A wider union in that position is an anchor, not a request to narrow U to the initial value. Use the input contract for the first supplied argument, not the collected rest binding: a fixed first tuple-rest position or a homogeneous array-rest element can contribute it. Unresolved leading-rest distribution supplies no anchor. Optional/default inputs include undefined before binding initialization. An ignored first parameter supplies no anchor. Otherwise derive U from the initial argument with ordinary binding widening and reference-value decay. A receiving result context may provide ordinary numeric-literal adoption but does not override a known incompatible initial type. A written any callback input does not erase an independently established initial contribution. Genuinely unknown contributions remain any. If unresolved spread count or routing leaves initial-argument presence unknown, do not choose either recurrence by guessing; retain independent item and index facts and leave the accumulator gradual.

Check established initial values, callback inputs, callback results and completed results against the selected contract under the ordinary checking rules. Do not widen an anchored recurrence merely to accept a callback result. Apply contextual-function adoption under 1.6.5 to fresh callbacks; written annotations take precedence, and existing callbacks are not retroactively annotated. Preserve ignored callback parameters and symbolic types from enclosing generic contexts. These rules apply to established computed names, optional access and extracted method views. Each invocation selects its own accumulator contract. Extraction does not bind a receiver, and ordinary binding-stability rules remain applicable.

A fresh callback's adopted parameters and result are enforced at their ordinary declaration boundaries. An explicit any result defers its produced-value check without discarding known parameter obligations: a subsequent typed accumulator binding checks its next input, and a required final receiving binding or return checks the completed value. A discarded final value creates no receiving obligation. An empty traversal does not invoke a callback to validate an unknown initial value. Do not wrap a callback, change its identity, or add an intrinsic accumulator-store check solely from this static view. Existing declaration checks and required receiving admission under 1.7.9.1 remain effective, including after method replacement.

The original range method acquires a fresh forward numeric iterator and reads its helper by ordinary property access. It retains the refusal of an absent or positive-infinite stop before acquisition. Without an initial argument, the first item seeds the accumulator, callback indices begin at 1, and an empty iterator throws a TypeError exception. With an initial argument, indices begin at 0 and an empty iterator returns that argument. Explicit undefined is present, not omitted. Traversal, callback failure and iterator closing retain the base language's runtime order. No contribution guarantees successful or terminating traversal. Checking executes no callback, default, getter or iterator; it grants no reference authority or extended lifetime. An incompatible replacement retains prior effects and leaves a failed initialization uninitialized. These synchronous contracts introduce no await, public generic intrinsic capability or named-argument routing rule.

21.6.9 Lazy Range Flattening Contracts

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 contribution of 21.6.4. An established helper-bearing synchronous iterator uses its established item contribution instead. First establish the callback's independent inputs, then infer its result R under 1.6.5. A written annotation takes precedence; an existing callback contributes its established signature and is not retroactively annotated. Observe the callback's declared or published inferred result through the ordinary inference dependencies. Do not adopt an inferred R as a new callback return obligation merely to derive flattened items; doing so can introduce observable structural-property reads. Written return annotations and independently required receiving admission retain their obligations. A contextual any result places no Promise-shaped return obligation on an async callback; it does not relax the requirements on a written async return annotation. Apply 32's flattening eligibility judgment separately from the item contribution below.

Let F be the normal flattened item contribution of R under 21.6.10, or any when unknown. The normal result is a helper-bearing synchronous iterator with item F, terminal contribution undefined, and optional any input to 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 iterable.

This is an operation contribution carried by an established method view, including computed names, optional access and extraction. Extraction does not bind a receiver. It is derived after establishing the callback result, not by equating a receiving item context with the callback's whole result. A whole gradual receiver gains no participation from the method name. An unresolved callback protocol remains unknown; the judgment introduces no public dependent type operator and does not add helpers to arbitrary structural iterators.

Creation does not call the mapper. Traversal invokes it and acquires inner iterators on demand, retaining ordinary lookup, failures, closing and termination behaviour. The view does not prove intrinsic identity. Required inferred-initialization and return admission, callback binding obligations after replacement, prior effects and unsuccessful initialization state follow 21.6.7. Neither inference nor admission traverses the iterator to discover its items. These contributions grant no reference authority and do not extend a reference lifetime.

21.6.10 Static Flattening Contribution

This judgment describes normal items reached by GetIteratorFlattenable with reject-primitives, followed by synchronous iteration. It is distinct from a negative proof that every callback result alternative fails. Inspect established type contracts only; invoke no callback, getter or iterator method.

  1. For each established callback result alternative, a proved primitive result has no normal items. For an Object with an established non-nullish @@iterator method, inspect the method's established callable result as the iterator. For an explicitly nullish hook, use the original Object as the direct iterator. An undeclared hook on an open shape is unknown, not proof of absence. An optional hook includes the direct-iterator fallback.
  2. Inspect each established iterator's next callable result. A proved non-callable selected method, non-Object acquired iterator or non-Object step has no normal items. Optional 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 void protocol result is also unknown: a void-returning callable type can conceal an Object result under 1.5.16, so it is not a primitive-category proof. Callable alternatives contribute their declared result, or their published inferred result when none is declared, under the ordinary stability rules.
  3. For each Object step, a required literal done: true contributes no item. Otherwise contribute the read type of value, including undefined for an optional property. An undeclared property on an open shape is unknown. This judgment does not infer a yielded value from a terminal value.
  4. A synchronous generator contributes its established yield argument. A finite Composite tuple contributes its established positions. An ordinary array or tuple storage type alone does not constrain its replaceable iteration protocol; separately established protocol evidence is required.
  5. Join all established normal-item alternatives. Proved throwing and definitely terminal alternatives contribute never. Any unknown alternative makes the contribution unknown. An empty collection of normal-item alternatives contributes never, not unknown. Recursive protocol evidence without a completed contribution is unknown. An established symbolic yielded type can remain symbolic, but an unresolved type for the entire callback result does not itself establish an item type.

A precise normal-item contribution does not assert that traversal succeeds or terminates. In particular, a primitive alternative can throw even when another alternative establishes a precise item. The eligibility error still applies when every established result alternative fails. Helper exhaustion independently contributes undefined.

A range literal consumed at an iteration element type T is a type error unless every element it produces is a value of T. A loop binding, array or rest spread, or collection seed supplies an iteration context, not a stored endpoint construction context. In the absence of an explicit iteration element type, a typed endpoint may supply it. This judgment does not change the range value's endpoint domain. A stored range annotation instead follows 21.1. Where T is a sized integer type, the elements run from the first to the last: the first is the start, or the start plus one where the start is open, and the last is the end, or the end less one where the end is open. So 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 compile-time evaluable contributes its value; one that is not, but has a sized integer type, contributes only that its value lies within that type's range, and the range is judged by the lowest first element and the highest last element that range allows. A range whose first element exceeds its last for every such value produces nothing and fits every T. A range with no end, or with an infinite end, produces values without limit and fits no sized integer type.

Where T is a binary floating-point type, a range whose endpoints are both whole integers iterates by 1, and its elements are the integers from the first to the last as above. Each must be exactly a value of T: a range of two or more elements fits only where its first and last element both lie within ±2p, p being the precision of T, and a range of one element fits where that element is a value of T. A range with an endpoint that is not a whole integer has no implicit step and so no elements; it fits unless an endpoint is outside the range of T, so that converting it would yield an infinity.

Note 1

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 integer type, such as 0..<n for an untyped n; a typed store of an element that does not fit is refused where it happens.

A typed endpoint's value is not known, but its type bounds it, and that is enough to decide. Iterating 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 integer it steps to, so a float16 range reaching 2049 would yield 2049, which float16 cannot hold, and converting it would give 2048. A typed floating-point endpoint is not decided, since whether the range iterates at all depends on whether its value is a whole integer.

Note 2

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 set aside: ... reads as spread to every reader and Ruby's ../... pairing is a known hazard, and a contextual keyword reads poorly in an index position. An earlier draft set ..< 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 integer element type is 1 and implicit. Numeric iterator acquisition and requests follow 21.7. The counter of a for over a range has the range's element type refined by the range's interval (6), so 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 compile-time constant that refinement is one too, and an index of a [N].<T> by that counter needs no check, which is the case 1.7.9 names.

Note 2

The bound is the range's own interval, so nothing about the elision is special to a 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 interval is part of the type rather than a convention a reader has to remember.

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 element type, where a computed count can round up and yield one value too many and repeated addition accumulates error; the arithmetic and final item-width rounding are specified below.

21.7 Numeric Range Iteration

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 ordered domains require their own progression operation; a present unsupported endpoint must not be interpreted as an absent endpoint.

The numeric range item type is the stored endpoint domain when that domain is a concrete numeric type or numeric parameterization covered by this clause. Otherwise it is the type of the typed start endpoint, or, if there is none, the type of the typed end endpoint, or otherwise number. A plain Number endpoint does not supply a typed endpoint for this choice; a BigInt endpoint does. This choice uses the original range's endpoints in both traversal directions. It does not change the range's reflected endpoint domain, and a union domain does not select an arithmetic operation over that union. Consumer iteration element checks remain the separate judgments of 21.6.

Each acquisition produces a fresh iterator with internal state consisting of its item type and numeric base, origin, stop, stop bound, signed difference, direction, mathematical non-negative integer index, previous item if any, and exhausted and executing flags. Its state is not read from public properties of the range or iterator. Acquisition does not enumerate or admit future items. The range is unchanged by requests on any of its iterators.

  1. For forward traversal the origin is the start and the stop is the end. For reverse traversal the origin is the end and the stop is the start; bounds move with their endpoints. An absent or nonfinite origin is a TypeError. Each present endpoint must have a numeric value covered by this clause; no coercion or user-defined conversion is performed to discover it. A NaN endpoint is a TypeError. A missing stop alone is unbounded; an infinite stop retains its sign.
  2. Implicit forward traversal and reverse require each present finite endpoint to be integral. Their differences are respectively 1 and -1 in the mathematical integers. A nonintegral endpoint is a TypeError at acquisition.
  3. step performs forward traversal. Its argument must be a positive finite numeric value covered by this clause; otherwise acquisition throws a TypeError. An integer item base additionally requires an integral origin and integral difference. The difference need not be representable at the item width, and its numeric type need not equal the item type. No object-to-number or string-to-number coercion is performed.
  4. If origin and difference are integral, progression is exact integer progression. Otherwise both operands must enter binary64 without loss of their numeric values, or acquisition throws a RangeError. This exact-entry requirement matters for an integer wider than binary64 in an otherwise fractional calculation.
  5. The initial index is 0 for a closed origin bound and 1 for an open origin bound. For an open bound the origin is also the initial previous value, so a first rounded item that fails to move past that excluded value is refused. The executing flag is initially false. After the preceding validation, the exhausted flag is initially true if the original interval is descending, or if both endpoints have equal numeric values and either bound is open; otherwise it is false. An empty interval is completed without attempting to advance a rounded candidate.

A numeric range iterator's next method performs the following steps:

  1. If the receiver has no numeric range iterator state, throw a TypeError. If its executing flag is true, throw a TypeError. If its exhausted flag is true, return CreateIteratorResultObject(undefined, true).
  2. Set executing and exhausted to true. All exits from this request clear executing. An abrupt completion leaves exhausted true, and propagates the original completion. A reentrant request that is caught by an admission hook does not itself abort the outer request.
  3. For exact progression, let the candidate be origin + index × difference in the mathematical integers, without modular reduction or rounding. Otherwise, at index 0 let the candidate retain the origin's binary64 representation; at a later index convert the index to binary64, perform binary64 multiplication by the difference, and then binary64 addition of the origin, in that order. Round this fractional candidate to the item base's format, using round-to-nearest, ties-to-even. These separate binary64 operations must not be replaced by a fused operation that changes the result.
  4. Compare the candidate to the stop before admitting it as an item. In forward traversal a value greater than a closed stop or greater than or equal to an open stop finishes the iterator; in reverse traversal the inequalities are reversed. If finished, return CreateIteratorResultObject(undefined, true). There is no comparison for an absent stop. For fractional progression the compared candidate is the rounded value the consumer would receive.
  5. Construct a value at the item base from the candidate. A sized integer candidate must lie within that type's bounds. An exact integral candidate at a binary floating-point base must be finite and exactly representable, rather than rounded to a different integer. A fractional floating candidate must be finite. Failure of any of these conditions throws a RangeError. A closed origin at index 0 preserves the sign of zero. An excluded candidate that finished the iterator in the preceding step need not inhabit the item base.
  6. If a previous value exists, the item must be strictly greater than it for forward traversal, or strictly less for reverse traversal. Otherwise throw a RangeError. This condition also prevents rounding from repeatedly producing a value without progress.
  7. If the item type is a parameterization, apply its existing checked admission to the constructed value. Propagate any abrupt completion. Admission must retain the candidate's numeric value, and the zero sign at a floating base; if it changes that value, throw a RangeError. Thus metadata admission does not silently rescale or move a computed item past a bound.
  8. Record the item as the previous value and increase the mathematical index by 1. Set exhausted to false, unless the item is equal in numeric value to the present closed stop, in which case leave it true. Return CreateIteratorResultObject(the item, false).

Once exhausted, an iterator remains exhausted, whether exhaustion followed the bound test, return of a closed endpoint, or an abrupt request. Carrier overflow is not implicit range completion: an unbounded byte progression from 255 yields 255, then throws RangeError, then returns done on subsequent requests. A bounded closed byte range ending at 255 instead finishes normally. A new iterator over the same range starts independently. Any metadata checks retain their existing effects and error classes; numeric iteration neither establishes nor extends the liveness of a reference later accessed by a consumer.

Note

The cursor may use checked machine arithmetic when that produces the same sequence; mathematical integer state does not require a particular allocation or representation. Range-returning take and drop and exact BigInt length are defined in 21.8. Numeric scaling is defined in 21.3. Interval arithmetic and nonnumeric progression domains have separate contracts and are not defined by substituting this cursor's private state.

21.8 Numeric Range Counts and Adapters

A newly manufactured cut carries its full admitted numeric endpoint domain after successful admission and representation checks, under the retention rule of 21.3. Reusing an original endpoint does not admit it again. Attaching a completed admission neither changes its numeric representation nor waives any failure or reference-liveness check.

The length getter and range-returning take and drop operations use the supported numeric endpoint representations of 21.7. All integer computations in this clause are mathematical integer computations: they do not wrap, round to an endpoint width, or coerce objects. These operations do not enumerate members or invoke per-item iteration checks.

The length getter first requires a range receiver, then two present finite integral supported numeric endpoints; otherwise it throws a TypeError exception. It returns the BigInt value of the greater of zero and the end minus the start plus one, less one for each open bound. Its result type is always 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 unit integer lattice. It is independent of the representation or metadata admission of intermediate items and does not prove that iteration succeeds. Missing, infinite, fractional, or unsupported endpoints have no length in this numeric contract; isEmpty retains its interval-ordering judgment.

A numeric take or drop call performs these steps in order. The usual evaluation of the receiver expression and argument expressions precedes the call.

  1. Require a range receiver; otherwise throw a TypeError exception. Read its internal endpoints and bounds, without reading public properties.
  2. Require a present finite integral supported numeric start. If the end is present, require a supported numeric value that is integral or is positive or negative infinity. Otherwise throw a TypeError exception.
  3. Require the supplied count to be a supported numeric value; an omitted argument, undefined, and every nonnumeric kind throw a TypeError exception without conversion. If the count is negative, fractional, NaN, or infinite, throw a RangeError exception. Let the non-negative mathematical integer count be n; negative zero counts as zero.
  4. Retain the source endpoint domain: the stored domain if present, otherwise the same inferred endpoint domain that 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.
  5. If n is zero, return the canonical empty result for take, or a copy for drop.
  6. Let first be the exact integer start plus one if its bound is open, or the start otherwise. For a finite end let last be the exact integer end minus one if its bound is open, or the end otherwise. If the end is negative infinity, or first exceeds a finite last, return a copy.
  7. If last exists and n is at least last minus first plus one, return a copy for take, or the canonical empty result for drop.
  8. Let cut be first plus n minus one. Select the original range's numeric item domain by 21.7. If the cut equals the stored start's integer value and the stored start already has that item type, reuse the stored start, including its zero sign and metadata, without new admission. Otherwise manufacture an endpoint of that type with exact integer value cut. Throw a RangeError exception if it does not fit the numeric base exactly. Then apply the full parameterized type's existing admission, propagating any exception. Require the admitted value to retain the exact computed integer and, for zero, positive zero; otherwise throw a RangeError exception.
  9. For 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 published before the required endpoint admission completes.

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. Iterator-returning helper operations retain their own count-conversion and consumption contracts.

22 Structural Matching

Reflect.inferSlot(name) returns a fresh slot, a Type Object standing for a position a match is to bind. Reflect.matchType(pattern, subject) first obtains patternRecord as ? ResolveReflectedType(pattern) and then subjectRecord as ? ResolveReflectedType(subject). It performs one-sided structural unification of those records: where every slot in pattern can be bound consistently so that the pattern becomes subject, it returns an object mapping each slot's name to the Type Object bound there, and otherwise it returns null. A slot occurring twice binds once and must bind alike. A constraint on a binding is an ordinary check of the result afterwards, so Reflect.isAssignable supplies what an infer S extends string says.

A pattern is structural: it is a node, and its slots stand where a Type Object would. A slot inside an deferred Type Record is a type error at the match rather than null, because evaluating a builder against an unbound slot is exactly the inversion no operation of this specification performs (17). A pattern for the result of a builder is therefore written against the shape the builder produces, an optional property or a wrapped element, and never against the application.

Note

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 (17.3 is where the checker will consult it).

The operation was proposed as optional on the measurement that the design's catalog needed it exactly zero times. That measurement has since changed: the decorators extension dispatches on the structure of a type it is handed, which is what a decorator that emits a schema, a validator, or documentation does, and there is no way to write that without this operation. The extends pattern of 26.2.4 is its syntactic form, standing to it as is stands to IsOfType, and consumes the unification defined here rather than defining a second one. Reflective use remains available and remains the escape hatch for what the syntax does not spell.

23 Provenance

A Property Type Record and a Type Record may carry an [[Origin]], a List of references to the declaration sites the record came from. It is not part of any type's identity: SameType does not read it, IsSubtype does not read it, and CanonicalizeType neither reads it nor writes it into the comparison that decides whether two Records are one. What canonicalization does with it is union it: where two Records that differ only in [[Origin]] are merged by interning, the merged Record's [[Origin]] is the union of theirs, ordered by 1.8.5.2. It is a HOST-FACING channel: an implementation makes it available to a tool, and no program reads it. That is a deliberate restriction rather than an omission. Because canonicalization unions origins, a reflected origin would change what a program observes about its own type when an unrelated module declared a structurally identical shape, which is a surprise with no upside for the program and none for the tool, since a tool already has the whole program in view. A builder that spreads a property record therefore spreads no origin, and one that mints a record has none to spread.

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 agent that does not exit is a leak and not a cost. Documentation is not carried here and is not carried in the node model at all. It stays with the source, and a tool resolves it from an origin at the moment it displays something, falling back for a minted record on the application that produced it, which is the expression a diagnostic already names.

Note

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 1.12.5, so a doc string either joins the canonical form, and two structurally identical types with different comments become different types, or interning crowns one winner and the rest is lost silently and by load order. Provenance escapes both because it is a reference rather than a payload and because it unions rather than wins.

That union is also why [[Origin]] does not reopen what 1.10.1 forecloses. The rule there is not that no second non-canonical field may exist; it is that no field may depend on the order modules loaded. A [[Name]] is first-won and so does depend on it, which is tolerated because a parameter has one name and there is nothing else to do. An [[Origin]] accumulates every site and is ordered canonically, so the same program produces the same origins in the same order however its modules were reached, and the accident the rule exists to prevent does not arise.

24 The Expansion Artifact

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 path, a dependency's concrete surface, which is the part that would otherwise be recomputed by every tool on every build. An artifact is an optimization and never a semantics: a consumer that ignores every artifact computes the same types, and one that reads a stale artifact detects it by the hash rather than by disagreeing. More generally it carries no UNBOUND TYPE PARAMETER, wherever one stands: an exported generic signature is unbound for the same reason 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.

Note

This is what makes the standalone conformance of A pay. A tool that reads an artifact is trusting that whoever produced it computed what this specification says, and the conformance suite is what makes that claim checkable rather than hopeful; the hash is what makes the trust revocable. The artifact also carries the extracted documentation for the origins of 23, since a dependency's sources are exactly what a consumer does not have on disk, which is the one thing provenance alone cannot supply.

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 Table 33 gives for its type, with every nested Type Object replaced by a reference to another entry, written as an object whose sole key is $ref and whose value is that entry's index. A table rather than nesting, because a primitive record refers to itself and a recursive type reaches itself through a member, so a form that carried nested types by containment would not terminate on the simplest type this specification defines. A tagged reference rather than a bare index, because a literal carries a value and a member carries an initializer, either of which may be a number.

Entries are ordered as the interning table orders the records they came from, so an artifact does not depend on the order a producer enumerated a module's exports; two producers over one graph write one artifact. A nominal type is carried as its module specifier and its declared name, never by value: its record holds a declaration and a constructor, and neither crosses a boundary. A consumer resolves that name against its own copy of the module. Every other leaf is a value, encoded as itself. A symbol is not encodable and a producer refuses rather than carrying one: an unregistered symbol has no name that survives a boundary, and a registered one would need the consumer to agree about identity across a wire, which is not a question a serialization answers. An artifact carries a version, and a producer writes it from the first artifact it writes.

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 shape it does not recognize — and evaluates instead. This is what makes "nothing in it can change what a program means" a rule rather than a hope: a consumer either reads an artifact it understands entirely, or it evaluates, so no artifact changes what a program means however it was produced. Declining is also what makes the format evolvable, since an addition a reader has never seen is refused by the same step that refuses a stale one.

The key is the SHA-256 digest of a canonical encoding of the module graph: for each module in the graph, reached transitively and ordered by specifier, the specifier and the source text, each written with its length before it. Length-prefixed rather than delimited, because a specifier and a source may contain any separator, and two graphs that encode alike would be one key. The digest is fixed alongside the encoding rather than left open: a shared input with an unshared digest is half a guarantee, and two implementations would then agree on what to hash and still not read each other’s artifacts. The key is written as the ASCII text 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.

25 Composites

A composite is a frozen object with a null prototype that is interned: two composites created from the same contents are the same object. Equality of contents is therefore identity, and ===, 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 design's composites extension, which integrates the Composites proposal under this type system; where the two differ, 25.8 records the difference.

Interning is defined over SameValueZero, and SameValueZero under this specification requires its operands to be values of one type before it compares them (1.5.1). Interning inherits that: a uint8 field and a Number field of the same mathematical value are different keys, so the value read back from a composite is the value that was stored, at its type, however many creation sites produced the object. The nondeterminism the upstream proposal resolves for -0 by normalization cannot arise between types here, because values of two types are never SameValueZero-equal; within a type it is resolved by 25.4.2, of which the -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

25.1 Composite Objects

A composite object is an object contained in the Composite Registry of its heap (25.3). Composite objects are created only by FindOrCreateComposite and FindOrCreateTupleComposite, which are the only operations that add an object to a Composite Registry. Membership in the registry is what an object's status as a composite consists of, so the status cannot be forged: a Proxy exotic object whose target is a composite object is not itself a composite object.

There are two kinds of composite object. A record composite is an ordinary object whose [[Prototype]] is null, whose [[Extensible]] is false, and whose properties are own data properties with String keys, each with [[Writable]] false, [[Enumerable]] true, and [[Configurable]] false. A tuple composite is an Array exotic object whose [[Prototype]] is null and whose [[Extensible]] is false, whose integer-indexed properties are own data properties with [[Writable]] false, [[Enumerable]] true, and [[Configurable]] false, and whose "length" property is non-writable, non-enumerable, and non-configurable. Array.isArray answers false for a record composite and true for a tuple composite, and typeof answers "object" for both.

Every composite object has a [[RuntimeType]] internal slot holding the composite type of its contents (25.2), which is what RuntimeTypeOf (1.7.1.1) reports for it. A composite object is frozen from its creation, so the slot never becomes stale.

Note 1

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 (1.6.7.1) and a frozen member cannot be written. 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.

Note 2

A record composite holds what upstream's composites hold. The tuple kind is this design's addition: an array-shaped composite whose "length" is own and non-enumerable, so that Object.keys lists the elements, and whose iteration 25.7 supplies without a prototype to carry %Symbol.iterator%. The two kinds never intern together (25.4.9): Composite([1]) and Composite({ 0: 1 }) are different objects, because a record with the entry "0" and a tuple of one element disagree about shape even where they agree about every entry.

25.2 Composite Types

A Type Record is a composite type when its [[Kind]] is primitive and its [[Name]] is "Composite". The composite type over a shape S, where S is an object, tuple or array Type Record and where an interface type contributes the object Type Record of its structural form, has [[Arguments]] « CompositeTreeShape(S) », and that record is called its shape. A shape is read as a composite tree: since a composite object is frozen from its creation and the value it holds at a structural position is itself a composite (a composite's contents are a tree terminating in non-composite leaves), CompositeTreeShape(S) is S with every Property Type Record's [[Readonly]] set to true and every [[Type]] of a property, tuple element or array element that is an object, tuple or array Type Record replaced by the composite type over that record, recursively; a member type that is not one of those kinds is unchanged, and the operation is idempotent. The reading is made where the composite type is constructed and nowhere else, so that every spelling of the type — an annotation, the typed creation Composite.<S>(source), a parse target, the record Reflect.typeOf reports — denotes one type. An array shape is the variable-length tuple composite: a composite of that type is a tuple composite of any length each of whose elements is of the element type. The top composite type, written Composite with no arguments, has [[Arguments]] « », states no shape, and is the type of every composite object. Composite joins the built-in type names of 1.5.27, resolving in type position by the rule stated there, and Composite.<T> is a TypeReference with TypeArguments like any other parameterized family. That the shape is a TYPE ARGUMENT and not a metadata record follows from the general rule (1.5.20) and needs no special case here: Composite declares a parameter, so its arguments are types. The rule matters at this clause in particular because a composite type's [[Kind]] is primitive, so a reading that asked what KIND the base was — rather than whether it declares parameters — would take the shape for metadata and put it in [[Metadata]], where 1.2.3.1 does not look for it. These Type Records canonicalize by canonicalizing the shape in [[Arguments]] (1.2.2.8), so Composite.<{ x: int32; y: int32 }> written anywhere is one Type Object. A composite is frozen from its creation, so no store through a receiver whose Static Type is a composite type can succeed, whatever the shape says: it is a type error to assign to, or delete, a member or element of a value whose Static Type is a composite type, the top composite type included; the shape's readonly reading states the same fact member by member for Composite.<S>, and the top type states it for every member at once. The Static Type of a call of the Composite function is the top composite type where the call supplies no TypeArguments and no contextual type reaches it, and the composite type over T's structural form for the typed creation Composite.<T>(source); Reflect.typeOf on a composite returns the Type Object of its [[RuntimeType]], the same object either spelling denotes. A const binding whose initializer is such a typed creation, or any call that writes its type arguments, takes the call's Static Type as its own, as 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 type error the subtyping rule states rather than a binding the checker cannot see.

Composite Type Records use the primitive representation but describe frozen Objects and a family of overlapping contracts. Their disjointness is decided before the scalar primitive rules of AreDisjoint (1.2.2.6). The top composite type overlaps every shaped composite type, and different shapes are not disjoint merely because their Type Records differ. Common-kind, required-member and admitted-length contradictions are judged by CompositeShapesAreDisjoint. A composite may also satisfy a structural object or tuple contract or an interface; absence of a subtype proof does not prove these contracts disjoint. Mutable array contracts exclude composite values, and a composite is neither callable nor a scalar or enum value. This judgment does not merge Type Objects, convert stored members, decide equality of particular values, or alter reference permissions or liveness. Equal contents still intern according to their stored types and composite kind.

The shape recorded for a composite object is derived from its stored contents by CompositeShape (25.4.6), so that objects with the same contents have the same shape and interning is coherent. The type of each field is the runtime type of its value, except that a field whose value is an Object records the any type, a field whose value is a composite object records that composite's own type, and a field whose value is a Type Object records the type type. An ordinary object's structural runtime type describes the properties it happens to have and can change as the object does, which is why it is not recorded: a frozen object's recorded type must not go stale, and the identity a reference field contributes is the reference, not a snapshot of its shape.

The subtype rules for composite types are stated in IsSubtype (1.2.3.1), where they precede the step that separates the kinds: every composite type is a subtype of the top composite type; a composite type over a shape is a subtype of a composite type over a wider shape; and a composite type over a shape is a subtype of that shape's object or tuple type and of an interface whose structural form it satisfies.

A composite type is therefore a subtype of the object type of its shape, which is what makes a composite satisfy an interface: 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 tuple shape is a subtype of the tuple types its shape is a subtype of; it is a subtype of no array type, because an array type describes a mutable container and every mutating operation on a composite throws. It is, however, a subtype of the composite type over an array shape whose element type each of its elements is a subtype of (the step in IsSubtype above): Composite.<[uint8, uint8]> is a subtype of Composite.<[].<uint8>>, since both are frozen and the array shape is the variable-length tuple composite.

Note

IsOfType (1.7.6.1) needs one step. A composite value reaching an object target is checked structurally by the existing steps, reading its properties as it would any object's; a composite target is decided by the operation's final step, IsSubtype of the value's runtime type, which the rules above extend; and a composite value reaching an array target is false, for the reason the subtyping rule gives: an array type describes a mutable container, and a tuple composite, though it is an Array, is not one. Without that step the structural read admitted a tuple composite as a [].<T> and the first mutation through the binding threw where the binding itself was the error. A tuple target is not so refused, a tuple composite being a subtype of the tuple types its shape is. The conversion of a composite to an array type at a typed position, where IsOfType is false, proceeds as for any array-like source and builds a new array of the type; the composite is not bound and is not modified. A record composite created without type arguments has Number fields where its source had numeric literals, so checking it against an interface of uint32 members fails on those fields by the no-implicit-conversion rule of 1.6; the remedy is to type the creation, which is the same advice every boundary of this specification gives.

25.3 The Composite Registry

Each heap has an associated Composite Registry: a List of Objects, initially empty, whose elements are exactly the composite objects of that heap. Objects are added to it only by FindOrCreateComposite and FindOrCreateTupleComposite. The registry is a property of a heap in the same way the type-object intern table is (1.2.2.9): agents that share a heap share one registry, so composites are === across every realm and every agent of one heap, like symbols obtained from Symbol.for, and an agent that does not share the heap is not reached by it (42). A composite object has a [[Prototype]] of null and so bears no relationship to the realm whose Composite function created it; only its contents matter.

Note 1

Under the agent model of the pinned edition each agent has its own heap and the registry is per-agent, which is the upstream proposal's rule. The threading extension is what makes the scope worth stating as the heap's: it gives several agents one heap, objects cross between their threads by reference with no serialization step, and a registry scoped more finely than objects are reachable would put two composites of equal contents, both reachable from both threads, in one heap, falsifying the invariant the feature is. Creation against a shared registry must accordingly be safe under concurrent callers; the observable requirement is only the one FindOrCreateComposite states, that equal contents yield one object, and implementations are expected to reach it without serializing unrelated creations through one lock.

Note 2

Membership in the registry does not keep a composite object alive; an implementation may remove one that is no longer live. The removal is unobservable, because composite objects cannot be held weakly (25.7) and so cannot be placed in a WeakMap, WeakSet, WeakRef, or FinalizationRegistry, which are the only instruments that observe liveness.

25.4 Abstract Operations for Composite Objects

25.4.1 IsComposite ( value )

The abstract operation IsComposite takes argument value (an ECMAScript language value) and returns a Boolean. It returns true if value is a composite object. It performs the following steps when called:

  1. If value is not an Object, return false.
  2. If the Composite Registry of the surrounding agent's heap contains value, return true.
  3. Return false.
Note

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.

25.4.2 CanonicalizeCompositeValue ( value )

The abstract operation CanonicalizeCompositeValue takes argument value (an ECMAScript language value) and returns an ECMAScript language value. It returns the canonical representative of the SameValueZero equivalence class of value within its type: the one value stored for every value the class contains, chosen so that the contents of a composite do not depend on which member reached the creation. Values of distinct types are never SameValueZero-equal (1.5.1), so the class is always within one type. It performs the following steps when called:

  1. If value is -0𝔽, return +0𝔽.
  2. If value is a negative zero of a binary floating-point type (1.5.3), return the positive zero of that type.
  3. If value is a finite value of a decimal floating-point type (1.5.4) whose type does not restrict the exponents of its values, then
    1. Let c be the coefficient of value.
    2. Let q be the exponent of value.
    3. Let qMax be the largest exponent of value's type.
    4. If c = 0, return the value of value's type with positive sign, coefficient 0, and exponent 0.
    5. Repeat, while c modulo 10 = 0 and q < qMax,
      1. Set c to c / 10.
      2. Set q to q + 1.
    6. Return the value of value's type with the sign of value, coefficient c, and exponent q.
  4. If value is a negatively signed zero of a decimal floating-point type whose type restricts the exponents of its values, return the positively signed zero of that type with the exponent of value.
  5. Return value.
Note

The rule generalizes the upstream proposal's normalization of -0, which is its Number instance. A SameValueZero class with one member canonicalizes to itself, which covers most of the type universe with no step naming it: every integer, String, BigInt, Boolean, Symbol, and Object value is alone in its class; a rational is because its type keeps every value in lowest terms (1.5.5), so each rational number has exactly one representation. The classes with more than one member are the signed zeros, handled above for the Number type and each binary float width, and the decimal cohorts.

A decimal type whose metadata fixes the exponents of its values, such as one parameterized by a DecimalContext scale (1.5.20), has collapsed each cohort before this operation sees a value: conversion into the type quantizes at the boundary, so the only remaining choice is the sign of a zero, which the fourth step settles. A bare decimal type stores the reduced cohort member, trailing zeros stripped, which is the one member computable from the numerical value alone and the value any hash of the field must be computed from in any case. The division of labour is deliberate: canonicalization is the type's wherever the type has an opinion, and this operation's only where it does not. A value whose significance matters to a program belongs in a type that declares its scale, at which point a composite stores exactly that scale; an unconstrained decimal in a composite does not carry significance, as it would not survive any other typed boundary either.

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 pinned edition permits and the byte views of the memory layout extension can observe (14), the NaN stored in a composite must have one fixed encoding per type, the canonical quiet NaN with positive sign and zero payload, so that reading a stored NaN's bytes reveals nothing about which creation interned the composite or what payload it supplied. This is the requirement the upstream proposal states for the Number type, applied to every float width the byte views reach.

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 field or an exponent field, which keys correctly with no special case. No option restores the distinction: an argument that changed interning identity would fragment the registry into flag-keyed families, so the preserveNegativeZero option the upstream FAQ floats is not adopted.

25.4.3 Composite Entry Records

A Composite Entry Record is a Record value describing one key/value pair of a record composite. It has a [[Key]] field (a String) and a [[Value]] field (an ECMAScript language value).

25.4.4 CompositeCanonicalOrder ( entries )

The abstract operation CompositeCanonicalOrder takes argument entries (a List of Composite Entry Records) and returns a List of Composite Entry Records. It returns entries in the canonical key order of record composites, which does not depend on the order in which the entries were provided. It performs the following steps when called:

  1. Let integerEntries be a new empty List.
  2. Let stringEntries be a new empty List.
  3. For each Composite Entry Record entry of entries, do
    1. If entry.[[Key]] is an array index, append entry to integerEntries; otherwise, append entry to stringEntries.
  4. Sort integerEntries into ascending order by the numeric values of the array indices their [[Key]] fields denote.
  5. Sort stringEntries into ascending lexicographic order of their [[Key]] fields, compared code unit by code unit.
  6. Return the list-concatenation of integerEntries and stringEntries.
Note

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 record composite a canonical form of its contents.

25.4.5 CompositeFieldType ( value )

The abstract operation CompositeFieldType takes argument value (an ECMAScript language value) and returns a Type Record. It returns the Type Record recorded for a composite field holding value. The result is a function of value alone and is stable over the composite's lifetime, which interning requires. It performs the following steps when called:

  1. If IsComposite(value) is true, return value.[[RuntimeType]].
  2. If value is a Type Object, return the Type Record for type (1.5.21).
  3. If value is an Object, return the Type Record { [[Kind]]: any }.
  4. Return RuntimeTypeOf(value).
Note

A value of a value type contributes its runtime type, so a uint8 field is a uint8 field and an untyped creation's numeric literals are Number fields, which is where the typed and untyped boundary of this clause's introduction becomes visible to reflection. An ordinary object contributes any rather than its structural runtime type, because that type is a mutable object's current shape and a frozen composite must not record something that can go stale; the identity a reference field contributes to interning is the reference itself. A nested composite and a Type Object are frozen and interned, so their types are stable and are recorded.

25.4.6 CompositeShape ( kind, contents )

The abstract operation CompositeShape takes arguments kind (record or tuple) and contents (either a List of Composite Entry Records or a List of ECMAScript language values) and returns a Type Record. It returns the shape of a composite with the given contents: an object Type Record for a record composite and a tuple Type Record for a tuple composite. contents is a List of Composite Entry Records where kind is record and a List of ECMAScript language values where kind is tuple. It performs the following steps when called:

  1. If kind is record, then
    1. Let props be a new empty List.
    2. For each Composite Entry Record entry of contents, do
      1. Append the Property Type Record { [[Key]]: entry.[[Key]], [[Type]]: CompositeFieldType(entry.[[Value]]), [[Optional]]: false, [[Readonly]]: true, [[Initial]]: none } to props.
    3. Return CanonicalizeType(the Type Record { [[Kind]]: object, [[Properties]]: props, [[IndexSignatures]]: « » }).
  2. Let elements be a new empty List.
  3. For each element v of contents, do
    1. Append the Tuple Element Record { [[Type]]: CompositeFieldType(v), [[Rest]]: false, [[Initial]]: none } to elements.
  4. Return CanonicalizeType(the Type Record { [[Kind]]: tuple, [[Elements]]: elements }).

25.4.7 FindOrCreateComposite ( entries )

The abstract operation FindOrCreateComposite takes argument entries (a List of Composite Entry Records) and returns a record composite. It returns the interned record composite for the key/value pairs of entries, creating it if none exists. No two elements of entries have the same [[Key]], and every [[Value]] is already the result of CanonicalizeCompositeValue. It performs the following steps when called:

  1. Let sortedEntries be CompositeCanonicalOrder(entries).
  2. Let registry be the Composite Registry of the surrounding agent's heap.
  3. For each composite object c of registry, do
    1. If CompositeMatch(c, record, sortedEntries) is true, return c.
  4. Let c be OrdinaryObjectCreate(null).
  5. For each Composite Entry Record entry of sortedEntries, do
    1. Perform ! DefinePropertyOrThrow(c, entry.[[Key]], PropertyDescriptor { [[Value]]: entry.[[Value]], [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }).
  6. Set c.[[Extensible]] to false.
  7. Set c.[[RuntimeType]] to the composite type over CompositeShape(record, sortedEntries).
  8. Add c to registry.
  9. Return c.
Note 1

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 typed creation site whose shape is statically known additionally admits a fixed hashing sequence with no key sorting and no descriptor walk, since the order and the field types are known before the program runs.

Note 2

No step of canonicalization, matching, or interning calls user code. The creation algorithms read the source's values once, getters included, and everything from CanonicalizeCompositeValue onward operates on the values read, over objects that cannot be Proxy exotic objects, so a composite's identity is a function of its contents and of nothing a program can interpose on. What a collection treats as one key is a separate question from what a composite is, and only the second is settled here.

25.4.8 FindOrCreateTupleComposite ( elements )

The abstract operation FindOrCreateTupleComposite takes argument elements (a List of ECMAScript language values) and returns either a normal completion containing a tuple composite or a throw completion. It returns the interned tuple composite whose elements are elements, creating it if none exists. Every element is already the result of CanonicalizeCompositeValue. It performs the following steps when called:

  1. Let registry be the Composite Registry of the surrounding agent's heap.
  2. For each composite object c of registry, do
    1. If CompositeMatch(c, tuple, elements) is true, return c.
  3. Let len be the number of elements of elements.
  4. If len ≥ 232, throw a RangeError exception.
  5. Let c be ! ArrayCreate(len).
  6. Set c.[[Prototype]] to null.
  7. Let i be 0.
  8. For each element v of elements, do
    1. Perform ! DefinePropertyOrThrow(c, ! ToString(𝔽(i)), PropertyDescriptor { [[Value]]: v, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false }).
    2. Set i to i + 1.
  9. Perform ! c.[[DefineOwnProperty]]("length", PropertyDescriptor { [[Writable]]: false }).
  10. Set c.[[Extensible]] to false.
  11. Set c.[[RuntimeType]] to the composite type over CompositeShape(tuple, elements).
  12. Add c to registry.
  13. Return c.

25.4.9 CompositeMatch ( c, kind, contents )

The abstract operation CompositeMatch takes arguments c (a composite object), kind (record or tuple), and contents (either a List of Composite Entry Records or a List of ECMAScript language values) and returns a Boolean. It returns true if c is a composite of the given kind with exactly the given contents. contents is a List of Composite Entry Records where kind is record, in the canonical order of CompositeCanonicalOrder, and a List of ECMAScript language values where kind is tuple. It performs the following steps when called:

  1. If kind is tuple and c is not a tuple composite, return false.
  2. If kind is record and c is a tuple composite, return false.
  3. If kind is tuple, then
    1. Let len be ! LengthOfArrayLike(c).
    2. If len ≠ the number of elements of contents, return false.
    3. Let i be 0.
    4. For each element v of contents, do
      1. If SameValue(! Get(c, ! ToString(𝔽(i))), v) is false, return false.
      2. Set i to i + 1.
    5. Return true.
  4. Let keys be ! c.[[OwnPropertyKeys]]().
  5. If the number of elements of keys ≠ the number of elements of contents, return false.
  6. Let i be 0.
  7. For each Composite Entry Record entry of contents, do
    1. If entry.[[Key]] is not keys[i], return false.
    2. If SameValue(! Get(c, entry.[[Key]]), entry.[[Value]]) is false, return false.
    3. Set i to i + 1.
  8. Return true.
Note

Both sides are canonicalized before this operation runs, so within one type SameValue and SameValueZero coincide here, and across types both are false because the values are distinct. Comparing with SameValue is therefore comparing with SameValueZero, stated with the finer relation so that the assertion is checkable.

25.5 The Composite Function

The Composite function:

  • is %Composite%.
  • is the initial value of the "Composite" property of the global object.
  • has a [[Prototype]] internal slot whose value is %Function.prototype%.
  • returns a composite object when called as a function, creating one only when no interned composite of the same contents exists.
  • does not have a [[Construct]] internal method; new Composite(...) throws a TypeError exception.
  • does not have a "prototype" property.
  • is the head of the composite type family: applying TypeArguments to it, in type position or in expression position (1.4.6), yields the Type Object of the composite type over the argument. It is a type error if the argument is not an object, tuple, array, or interface type, or if it has a Symbol-keyed member, since a composite's keys are Strings. Calling that Type Object is the typed creation of 25.5.2, the construction boundary a parameterized type already has (1.5.20).

25.5.1 Composite ( source )

The source parameter has the declared contract object (1.2.8): Composite is a global this specification introduces, so the contract applies everywhere, and a call whose argument's Static Type is participating (1.9.7) and holds no Object is a type error. An argument of the any type, or one whose type is unknown, is checked by 1. Arrays are Objects, so both composite kinds pass the contract; the Symbol-key rule of 6.b.i remains the dynamic check, since the own enumerable keys of a value are not a static fact of an open object type.

This function performs the following steps when called:

  1. If source is not an Object, throw a TypeError exception.
  2. Let isArray be ? IsArray(source).
  3. If isArray is true, then
    1. Let len be ? LengthOfArrayLike(source).
    2. Let elements be a new empty List.
    3. Let i be 0.
    4. Repeat, while i < len,
      1. Let v be ? Get(source, ! ToString(𝔽(i))).
      2. Append CanonicalizeCompositeValue(v) to elements.
      3. Set i to i + 1.
    5. Return ? FindOrCreateTupleComposite(elements).
  4. Let entries be a new empty List.
  5. Let keys be ? source.[[OwnPropertyKeys]]().
  6. For each element key of keys, do
    1. Let desc be ? source.[[GetOwnProperty]](key).
    2. If desc is not undefined and desc.[[Enumerable]] is true, then
      1. If key is a Symbol, throw a TypeError exception.
      2. Let v be ? Get(source, key).
      3. Append the Composite Entry Record { [[Key]]: key, [[Value]]: CanonicalizeCompositeValue(v) } to entries.
  7. Return FindOrCreateComposite(entries).
Note 1

The kind is decided by Array.isArray on the argument: an array source creates a tuple composite from its elements 0 through length - 1, and any other object creates a record composite from its own enumerable String-keyed properties. Inherited and non-enumerable properties are ignored, getters are invoked eagerly and exactly once each in the order the keys are reported, and an own enumerable Symbol-keyed property is a TypeError; a Symbol remains an ordinary value. The source is never itself converted; it only supplies values, and 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.

Note 2

The upstream proposal reads every array through the record path, where 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 25.8. It resolves the reservations upstream states against a list form, an enumerable length and a missing %Symbol.iterator%, by making length own and non-enumerable and by supplying iteration without a prototype (25.7); the reservation about creation cost growing with element count stands, and a long list remains a poor key.

25.5.2 Calling a Composite Type Object

Calling the Type Object of a composite type over a shape S, written Composite.<T>(source) for the S that is T's structural form, returns the result of CompositeFromShape(S, source), where source is the call's first argument, or undefined where none is supplied. This is the construction boundary of the composite types, as calling any parameterized Type Object is its type's (1.5.20). The same boundary is reached by the conversion of a value to a composite type at any typed position (1.6): an Object that is not a composite, reaching a position whose type is a composite type over a shape, is converted by CompositeFromShape of that shape, and a value that is already a composite is passed through, the typed creation being idempotent on one. Since a shape's structural members are composite types (25.2), this is what builds the nested composites of a composite tree: Composite.<S>(source) with an ordinary object or array at a structural position of source converts it, at the member's type, into the composite it must be, and the typed creation of a document interns with the typed parse of the same document.

25.5.2.1 CompositeFromShape ( S, source )

The abstract operation CompositeFromShape takes arguments S (an object, tuple or array Type Record) and source (an ECMAScript language value) and returns either a normal completion containing a composite object or a throw completion. It performs the typed creation of a composite: each supplied value is converted to its member's type, a required absence throws, an undeclared property throws, and an optional member's declared default is filled, before canonicalization and interning. S is a SHAPE (25.2), which is what the three kinds it may be have in common. It performs the following steps when called:

  1. If source is not an Object, throw a TypeError exception.
  2. If S.[[Kind]] is array, then
    1. Let len be ? LengthOfArrayLike(source).
    2. Let elements be a new empty List.
    3. Let i be 0.
    4. Repeat, while i < len,
      1. Let v be ? Get(source, ! ToString(𝔽(i))).
      2. Let converted be ? the conversion of v to S.[[Element]], as at a typed argument (1.6).
      3. Append CanonicalizeCompositeValue(converted) to elements.
      4. Set i to i + 1.
    5. Return ? FindOrCreateTupleComposite(elements).
  3. If S.[[Kind]] is tuple, then
    1. Let len be ? LengthOfArrayLike(source).
    2. If len is not a length S admits (1.14.7), throw a TypeError exception.
    3. Let elements be a new empty List.
    4. Let i be 0.
    5. Repeat, while i < len,
      1. Let v be ? Get(source, ! ToString(𝔽(i))).
      2. Let converted be ? the conversion of v to the [[Type]] of the element of S that receives position i, as at a typed argument (1.6).
      3. Append CanonicalizeCompositeValue(converted) to elements.
      4. Set i to i + 1.
    6. For each element e of S.[[Elements]] whose position is not less than len, do
      1. If e.[[Rest]] is false, then
        1. Assert: e.[[Initial]] is not none, since a tuple's defaults are trailing (1.4.2.7) and len is a length S admits.
        2. Append CanonicalizeCompositeValue(e.[[Initial]]) to elements.
    7. Return ? FindOrCreateTupleComposite(elements).
  4. Assert: S.[[Kind]] is object.
  5. If any Property Type Record of S.[[Properties]] has a [[Key]] that is a Symbol, throw a TypeError exception.
  6. Let entries be a new empty List.
  7. Let keys be ? source.[[OwnPropertyKeys]]().
  8. For each element key of keys, do
    1. Let desc be ? source.[[GetOwnProperty]](key).
    2. If desc is not undefined and desc.[[Enumerable]] is true, then
      1. If key is a Symbol, throw a TypeError exception.
      2. Let memberType be empty.
      3. Let p be the Property Type Record of S.[[Properties]] whose [[Key]] is key, or empty if there is none.
      4. If p is not empty, then
        1. Set memberType to p.[[Type]].
      5. Else,
        1. Let sig be the first Index Signature Record of S.[[IndexSignatures]] that accepts key, or empty if there is none.
        2. If sig is not empty, set memberType to sig.[[ValueType]].
      6. If memberType is empty, throw a TypeError exception.
      7. Let v be ? Get(source, key).
      8. Let converted be ? the conversion of v to memberType, as at a typed argument (1.6).
      9. Append the Composite Entry Record { [[Key]]: key, [[Value]]: CanonicalizeCompositeValue(converted) } to entries.
  9. For each Property Type Record p of S.[[Properties]], do
    1. If no element of entries has [[Key]] p.[[Key]], then
      1. If p.[[Optional]] is false, throw a TypeError exception.
      2. If p.[[Initial]] is not none, append the Composite Entry Record { [[Key]]: p.[[Key]], [[Value]]: CanonicalizeCompositeValue(p.[[Initial]]) } to entries.
  10. Return FindOrCreateComposite(entries).
Note

Typed creation is the untyped algorithm with each supplied value converted to its member's type at the boundary, which is what makes the resulting fields carry T's types and the result's Static Type Composite.<T>. It is that algorithm literally: the read set is the same one, the source's own enumerable String-keyed properties in the order its [[OwnPropertyKeys]] internal method reports them, each read exactly once, so the two spellings hold the same data and run the same getters in the same order. What the shape adds is the conversion of each value, the two rejections, and the defaults; what it does not add is a second way of finding a value. Three of its rules deserve their reasons. A required member that is absent throws rather than filling the type's default: a composite is a key, and a key half of whose contents were invented silently would collide with the key that stated them. A property the type does not name throws for the mirror reason: dropping it silently would intern two different sources to one object. And an optional member's declared default, the [[Initial]] of 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 (1.4.2.9), typed composite creation is a construction site, and a check of a composite that already exists writes nothing to it, which for a frozen shared object is not a preference but the only possibility.

The contextual type of source is T's structural form (1.6.5), so numeric literals in the source take the member types by 1.6.6 and the conversions above are checks rather than coercions at a literal creation site. A binding annotated Composite.<T> gives an unadorned Composite(...) initializer the same treatment through the return-type direction of overload resolution (1.10.5). An unannotated, uncontextualized Composite({ id: 7 }) in typed code produces Number fields; code that means anything else says so at the creation site.

That the read set is the enumerable own properties, rather than everything a lookup would answer, is a decision and the alternative is worth naming. A boundary check (1.7.6.1) reads through accessors and the prototype chain, because it asks whether a value can be used as the type; a creation asks what the new value's contents are, and every operation in the language that copies contents, 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 shape; the benefit is that the case fails loudly, with a TypeError naming the absent member, where the untyped creation silently produces a composite without it. Reading through the chain instead cannot be made symmetric in any case: the rejection of an undeclared property needs a finite set of keys to walk, and a lookup has none, since every object inherits "toString".

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 (1.4.2.7). One consequence of filling is worth stating: 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.

25.5.3 Composite.isComposite ( arg )

This function performs the following steps when called:

  1. Return IsComposite(arg).

25.5.4 Composite [ %Symbol.hasInstance% ] ( value )

This function performs the following steps when called:

  1. Return IsComposite(value).
Note

The Composite function has no "prototype" property, so without this method value instanceof Composite would throw through OrdinaryHasInstance. With it, instanceof against the bare function answers membership in the top composite type, and instanceof against Composite.<T>, a Type Object, answers membership in that type by 1.7.11, narrowing the static type on success as any type-object instanceof does.

25.5.5 Composite [ %Symbol.customMatcher% ] ( subject, hint )

The function performs the following steps when called:

  1. Return IsComposite(subject).
Note

Composite needs this method because it is a function, not a Type Object: a name pattern that evaluates to a Type Object tests membership, but a plain function is a constant compared by SameValue (26.2.1), so without the matcher 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 SameValue and an interned composite's SameValue is its contents.

25.6 Composites and JSON

JSON.stringify requires no change: a record composite is an object whose own enumerable properties are its contents, and a tuple composite is an array, so each serializes as its data. Interning is deliberately not on the wire; it is an identity within one heap, not a serialization format.

The typed parse re-interns: CoerceJSONValue (45.1) carries a case for composite types, placed before its primitive case since a composite type is of that kind, which coerces the parsed value against the shape, canonicalizes each resulting value, and obtains the result through FindOrCreateComposite or FindOrCreateTupleComposite. The order is load-bearing: the coercion against the shape is where the document is validated — a member token that does not fit its integer type, or a missing required member, is a TypeError exception there, exactly as it is for 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 shape's structural members are themselves composite types (25.2), the coercion of the shape reaches this same case for every nested object and array of the document, so the whole document is interned as a tree with no further step, and an array shape yields a tuple composite as CompositeFromShape does. The top composite type is rejected there with a TypeError exception, because it states no shape to validate against.

Note

JSON.parse.<Composite.<T>>(text) therefore validates against T by the walk of 45 and interns the result, so whether a configuration has changed between two polls is === between two parses. This answers the mapping the design's serialization document left open for composites. Absent members follow the construction rule of 1.4.2.9 through the coercion of the shape, since the shape's own coercion is what fills them: an optional member's declared default is filled and an optional member without one stays absent, and a document whose array is shorter than a tuple shape takes that shape's trailing defaults. A parsed composite therefore interns with the typed creation of the same document.

A host that serializes objects for transfer between heaps, as the HTML structured clone algorithm does, must serialize a composite as its kind and its contents and, on deserialization, obtain the result through FindOrCreateComposite or FindOrCreateTupleComposite in the receiving heap, each value having been deserialized and canonicalized first. Equality between composites is thereby preserved within every heap, two messages carrying equal contents arriving ===, and never across heaps, whose registries are disjoint; a composite is never the same object on two sides of a boundary that copies.

25.7 Modifications to Existing Abstract Operations

25.7.1 CanBeHeldWeakly ( v )

The abstract operation CanBeHeldWeakly takes argument v (an ECMAScript language value) and returns a Boolean. It returns true if and only if v is suitable for use as a key of a WeakMap, an element of a WeakSet, the target or unregister token of a FinalizationRegistry, or the target of a WeakRef. It performs the following steps when called:

  1. If v is an Object, IsComposite(v) is false, and v is not an instance of a typed class that seals its instances (1.12.9), return true.
  2. If v is a Symbol and KeyForSymbol(v) is undefined, return true.
  3. Return false.
Note

The one operation gates every weak position, so this change makes each of WeakMap, WeakSet, WeakRef, and FinalizationRegistry reject a composite with a TypeError exception. A composite is excluded for the reason a value type is (1.12.9), reached from the other side: its identity coincides with its contents, so the registry may always resurrect an equal object and there is no liveness for a weak reference to observe, only the registry's own entry, which holding weakly would keep alive. A cache that must not retain composite keys holds their constituent trackable objects weakly instead.

25.7.2 GetIterator ( obj, kind )

The abstract operation GetIterator takes arguments obj (an ECMAScript language value) and kind (sync or async) and returns either a normal completion containing an Iterator Record or a throw completion. It obtains an iterator for obj. The algorithm restates the pinned edition's, with the inserted steps marked. It performs the following steps when called:

  1. If obj is a tuple composite, then
    1. Let iterator be CreateArrayIterator(obj, value).
    2. Let nextMethod be ! Get(iterator, "next").
    3. Let iteratorRecord be the Iterator Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
    4. If kind is async, return CreateAsyncFromSyncIterator(iteratorRecord).
    5. Return iteratorRecord.
  2. If kind is async, then
    1. Let method be ? GetMethod(obj, %Symbol.asyncIterator%).
    2. If method is undefined, then
      1. Let syncMethod be ? GetMethod(obj, %Symbol.iterator%).
      2. If syncMethod is undefined, throw a TypeError exception.
      3. Let syncIteratorRecord be ? GetIteratorFromMethod(obj, syncMethod).
      4. Return CreateAsyncFromSyncIterator(syncIteratorRecord).
  3. Else,
    1. Let method be ? GetMethod(obj, %Symbol.iterator%).
  4. If method is undefined, throw a TypeError exception.
  5. Return ? GetIteratorFromMethod(obj, method).
Note

A tuple composite has a null prototype, so no %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 tuple composite's elements with no method on the object; the manual protocol spelling is Array.prototype.values.call(m), which builds the same iterator. In typed code the iteration is additionally type-directed, an index loop over known element types, exactly as an array of statically known element type iterates; the step above is what extends that behaviour to untyped code, where nothing else could. Record composites are unaffected and are not iterable, matching ordinary objects.

The non-mutating array generics apply to a tuple composite explicitly, as Array.prototype.map.call(m, f), or through the typed signatures the standard library gives them; the mutating ones throw on a frozen receiver as they do for any frozen array, and delete and every store throw as 1.14.10 already provides for typed elements.

25.8 Deviations from the Upstream Proposal

For feedback in both directions, the deltas between this clause and the upstream Composites proposal, in one place:

  • Interning is type-sensitive, because SameValueZero is: Composite({ (x: uint8): 1 }) and Composite({ x: 1 }) are distinct objects. Upstream has one number type and no such distinction to make.
  • Canonicalization is stated as a rule over SameValueZero equivalence classes, of which upstream's -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.
  • The tuple kind is added, and it changes the result of Composite on an array source, which upstream reads through the record path with length dropped.
  • The registry is scoped to the heap rather than to the agent. The two coincide under the pinned edition's agent model; they come apart exactly where the threading extension shares one heap among agents.
  • Typed creation through the composite Type Objects, the 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 proposal as it stands and is independent of types.
  • The typed JSON parse and the host serialization requirement of 25.6 are defined.
  • Everything else, the creation semantics and eager getters, the Symbol-key refusal, the canonical key order, frozenness and the null prototype, Composite.isComposite, the weak-position refusal, the cross-realm identity within one registry, and the impossibility of cycles, since a composite is frozen at birth and can reference only values that already existed, is adopted as upstream specifies.

26 Pattern Matching

This clause specifies the design's pattern matching extension: 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 runtime semantics here perform the first two, and the static semantics hand the third to the narrowing machinery of 1.8.5.

26.1 The match Expression

PrimaryExpression[Yield, Await] : MatchExpression[?Yield, ?Await] MatchExpression[Yield, Await] : match [no LineTerminator here] ( Expression[+In, ?Yield, ?Await] ) { MatchClauses[?Yield, ?Await] } match [no LineTerminator here] all [no LineTerminator here] ( Expression[+In, ?Yield, ?Await] ) { MatchAllClauses[?Yield, ?Await] } MatchAllClauses[Yield, Await] : MatchAllClause[?Yield, ?Await] MatchAllClauses[?Yield, ?Await] MatchAllClause[?Yield, ?Await] MatchAllClause[Yield, Await] : when MatchPattern[?Yield, ?Await] MatchGuard[?Yield, ?Await]opt : MatchArmBody[?Yield, ?Await] MatchClauses[Yield, Await] : MatchClause[?Yield, ?Await] MatchClauses[?Yield, ?Await] MatchClause[?Yield, ?Await] MatchClause[Yield, Await] : when MatchPattern[?Yield, ?Await] MatchGuard[?Yield, ?Await]opt : MatchArmBody[?Yield, ?Await] default : MatchArmBody[?Yield, ?Await] MatchGuard[Yield, Await] : if ( Expression[+In, ?Yield, ?Await] ) MatchArmBody[Yield, Await] : AssignmentExpression[+In, ?Yield, ?Await] ; throw AssignmentExpression[+In, ?Yield, ?Await] ; Block[?Yield, ?Await, ~Return]

The Block of a MatchArmBody carries the [Return], [Yield], and [Await] permissions of the function enclosing the match, so return, break, continue, await, and yield inside a block arm mean what they mean in that function; the Return above is notational for the production and the permission is threaded in the obvious way by the enclosing contexts. 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:

  • the value is an Array of the matched clauses' values, empty when none matched;
  • exhaustiveness is not required (26.3.1);
  • subtraction narrowing between clauses does not apply, since a later clause is reached whether or not an earlier one matched.

A MatchAllClause admits no default. default and the wildcard pattern are interchangeable in a MatchClause, and a form in which one meant "always" and the other "only where nothing else did" would give the same spelling two meanings one form apart. A clause that always contributes is written when _.

Note 1

all is a contextual keyword: it is the modifier only where 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 MatchClause is not the last of its MatchClauses.

Note 2

match, when, and, or, and not are contextual keywords, treated as keywords only in the positions above and in the pattern grammar, which are Syntax Errors today. match(x) {} remains a call followed by a block, because MatchClauses requires at least one clause; in statement position the braced sequel of match (…) is parsed under a cover, as the type operator's parenthesized operand is (1.4.5), and refined to MatchClauses where they parse as such, otherwise remaining the block it is today. Every program the refinement accepts was a Syntax Error: a clause's pattern is never empty, so the label 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.

26.1.1 Runtime Semantics: Evaluation

A Match Cache Record has a [[Reads]] field, a List of Records { [[Object]], [[Key]], [[Present]], [[Value]] }, and an [[Iterations]] field, a List of Records { [[Object]], [[Elements]] (a List), [[Done]] (a Boolean), [[Iterator]] (an Iterator Record) }. It memoizes what a match reads of its subject, so that a property is read at most once and an iterator is obtained at most once however many patterns look, and every pattern of one match sees the same values.

  1. Let subject be ? GetValue(? Evaluation of Expression).
  2. Let cache be the Match Cache Record { [[Reads]]: a new empty List, [[Iterations]]: a new empty List }.
  3. For each MatchClause clause of MatchClauses, in source order, do
    1. Let outerEnv be the running execution context's LexicalEnvironment.
    2. Let env be NewDeclarativeEnvironment(outerEnv).
    3. For each element name of the BoundNames of clause, do
      1. If the MatchBindingPattern binding name has const, perform ! env.CreateImmutableBinding(name, true).
      2. Else, perform ! env.CreateMutableBinding(name, false).
    4. Set the running execution context's LexicalEnvironment to env.
    5. If clause is a default clause, let matched be true.
    6. Else, let matched be Completion(PatternMatches(the clause's MatchPattern, subject, cache, env)).
    7. If matched is an abrupt completion, set the running execution context's LexicalEnvironment to outerEnv and return ? matched.
    8. Set matched to ! matched.
    9. If matched is true and clause has a MatchGuard, then
      1. Let guard be Completion(GetValue(Completion(Evaluation of the MatchGuard's Expression))).
      2. If guard is an abrupt completion, set the running execution context's LexicalEnvironment to outerEnv and return ? guard.
      3. Set matched to ToBoolean(! guard).
    10. If matched is true, then
      1. Let result be Completion(MatchArmEvaluation of the clause's MatchArmBody).
      2. Set the running execution context's LexicalEnvironment to outerEnv.
      3. Return ? result.
    11. Set the running execution context's LexicalEnvironment to outerEnv.
  4. Throw a TypeError exception.

The second form collects rather than returning:

  1. Let subject be ? GetValue(? Evaluation of Expression).
  2. Let cache be the Match Cache Record { [[Reads]]: a new empty List, [[Iterations]]: a new empty List }.
  3. Let results be a new empty List.
  4. For each MatchAllClause clause of MatchAllClauses, in source order, do
    1. Let outerEnv be the running execution context's LexicalEnvironment.
    2. Let env be NewDeclarativeEnvironment(outerEnv).
    3. For each element name of the BoundNames of clause, do
      1. If the MatchBindingPattern binding name has const, perform ! env.CreateImmutableBinding(name, true).
      2. Else, perform ! env.CreateMutableBinding(name, false).
    4. Set the running execution context's LexicalEnvironment to env.
    5. Let matched be Completion(MatchPatternMatches of the clause's MatchPattern with arguments subject and cache).
    6. If matched is an abrupt completion, set the running execution context's LexicalEnvironment to outerEnv and return ? matched.
    7. Set matched to ! matched.
    8. If matched is true and clause has a MatchGuard, then
      1. Let guard be Completion(GetValue(Completion(Evaluation of the MatchGuard's Expression))).
      2. If guard is an abrupt completion, set the running execution context's LexicalEnvironment to outerEnv and return ? guard.
      3. Set matched to ToBoolean(! guard).
    9. If matched is true, then
      1. Let result be Completion(MatchArmEvaluation of the clause's MatchArmBody).
      2. Set the running execution context's LexicalEnvironment to outerEnv.
      3. Append ? result to results.
    10. Else,
      1. Set the running execution context's LexicalEnvironment to outerEnv.
  5. Return CreateArrayFromList(results).
Note 1

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 abrupt completion from any clause propagates, which is what makes an arm that 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 ? GetValue(? Evaluation of AssignmentExpression). Of throw AssignmentExpression ;, it evaluates as the throw statement does. Of a Block, it is the evaluation of a do expression's Block (39): the block is evaluated and the result is its completion value.

Note 2

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 ExpressionStatement, and void otherwise - and that rule was a strict subset of this one, so no program changes meaning by its replacement. What it could not do was let an arm end in an if with an else or a try, and what it did badly was make an arm ending in a declaration a silent void rather than the Syntax Error 39.1 now gives. The bindings of a clause are created immutable and are initialized by the pattern as it matches; a clause that does not match discards its environment, so a binding never leaks between arms, and the guard and body of the arm that runs see exactly what its own pattern bound.

26.2 Patterns

MatchPattern[Yield, Await] : MatchOrPattern[?Yield, ?Await] MatchOrPattern[Yield, Await] : MatchAndPattern[?Yield, ?Await] MatchOrPattern[?Yield, ?Await] or MatchAndPattern[?Yield, ?Await] MatchAndPattern[Yield, Await] : MatchUnaryPattern[?Yield, ?Await] MatchAndPattern[?Yield, ?Await] and MatchUnaryPattern[?Yield, ?Await] MatchUnaryPattern[Yield, Await] : not MatchUnaryPattern[?Yield, ?Await] MatchPrimaryPattern[?Yield, ?Await] MatchPrimaryPattern[Yield, Await] : ( MatchPattern[?Yield, ?Await] ) _ extends TypePattern[?Yield, ?Await] MatchBindingPattern[?Yield, ?Await] ${ Expression[+In, ?Yield, ?Await] } ObjectMatchPattern[?Yield, ?Await] ArrayMatchPattern[?Yield, ?Await] MatchNamePattern[?Yield, ?Await] MatchNamePattern[?Yield, ?Await] ( MatchPatternList[?Yield, ?Await]opt ) MatchNamePattern[?Yield, ?Await] ObjectMatchPattern[?Yield, ?Await] MatchNamePattern[?Yield, ?Await] ArrayMatchPattern[?Yield, ?Await] MatchLiteralPattern[?Yield, ?Await] Type[?Yield, ?Await] MatchBindingPattern[Yield, Await] : let BindingIdentifier[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt const BindingIdentifier[?Yield, ?Await] TypeAnnotation[?Yield, ?Await]opt TypePattern[Yield, Await] : Type[?Yield, ?Await] MatchNamePattern[Yield, Await] : IdentifierReference[?Yield, ?Await] MatchNamePattern[?Yield, ?Await] . IdentifierName MatchNamePattern[?Yield, ?Await] TypeArguments[?Yield, ?Await] MatchPatternList[Yield, Await] : MatchPattern[?Yield, ?Await] MatchPatternList[?Yield, ?Await] , MatchPattern[?Yield, ?Await] ObjectMatchPattern[Yield, Await] : { } { MatchPropertyList[?Yield, ?Await] ,opt } { MatchPropertyList[?Yield, ?Await] , ... MatchBindingPattern[?Yield, ?Await] } { ... MatchBindingPattern[?Yield, ?Await] } MatchPropertyList[Yield, Await] : MatchProperty[?Yield, ?Await] MatchPropertyList[?Yield, ?Await] , MatchProperty[?Yield, ?Await] MatchProperty[Yield, Await] : PropertyName[?Yield, ?Await] : MatchPattern[?Yield, ?Await] MatchBindingPattern[?Yield, ?Await] ArrayMatchPattern[Yield, Await] : [ ] [ MatchPatternList[?Yield, ?Await] ,opt ] [ MatchPatternList[?Yield, ?Await] , ... MatchBindingPattern[?Yield, ?Await] ] [ ... MatchBindingPattern[?Yield, ?Await] ]

The identifier _ is the wildcard wherever a pattern is expected, taking precedence over its MatchNamePattern reading, so a program that binds the name _ and wants its value writes ${_}. A MatchProperty that is a MatchBindingPattern is the shorthand: let x is x: let x. Within a MatchPattern, ${ 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 ShortCircuitExpression restricted by the early errors below to a literal-like form; the restriction is applied after the cover parse, as the parenthesized operand of type is refined (1.4.5).

MatchLiteralPattern[Yield, Await] : ShortCircuitExpression[+In, ?Yield, ?Await]
  • It is a Syntax Error if a MatchLiteralPattern's ShortCircuitExpression is not one of: a NumericLiteral, optionally preceded by + or -; a StringLiteral; a NoSubstitutionTemplate; true, false, or null; a RegularExpressionLiteral; or a range literal of 21.6, each of whose present endpoints is one of the preceding forms or a MatchNamePattern form.
  • It is a Syntax Error if the two alternatives of a MatchOrPattern do not have the same BoundNames.
  • It is a Syntax Error if the BoundNames of a MatchPattern contain any duplicate entries, except that the same name bound by both alternatives of an or is one binding.
  • Within a TypePattern, a CaptureBinding in a TypeArgument position is the pattern slot, the syntactic form of the slot that 22 creates reflectively: 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 1.4.3.2 admit a CaptureBinding in. The BindingIdentifier : Type form of TypeArgument is a named type argument (1.4.2.2) in a TypePattern as everywhere else; the marked capture is what keeps the two readings apart without consulting the context.
  • It is a type error if the MatchNamePattern of a juxtaposition or of an extractor form resolves to a binding that does not denote what the form requires: the juxtaposed head must denote a type, and the extractor head must denote a value with a %Symbol.customMatcher% method, decided at the site where the head's Static Type is known and at run time otherwise. Every name resolves to some binding, a class declaration's included, so the rule turns on what the binding denotes. A type, a class and a Type Object are Objects, so a juxtaposed head whose Static Type establishes only non-Object values denotes no type.
  • A JUXTAPOSITION matches where the value is of the type its MatchNamePattern denotes and the ObjectMatchPattern or ArrayMatchPattern then matches it, and its PatternType is the head's type narrowed by the shape's. This follows from the overlap rule above rather than qualifying it: 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.
  • No LineTerminator may appear between a juxtaposition's MatchNamePattern and its ObjectMatchPattern or ArrayMatchPattern. A pattern is the right operand of 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 shape.
  • MatchNamePattern ArrayMatchPattern and an IndexedAccessType are ambiguous after a name: 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 Type cannot express, such as a binding or _, and the index is taken otherwise. This is the rule the braced and bracketed patterns already carry on their own - a shape every one of whose elements is a type is read as the type, the two readings agreeing there - and it is decided on the FORM of each element rather than on what it denotes, so it is settled before any name is resolved.

A MatchNamePattern and a Type overlap where a name is both; the name form is preferred, and the two readings agree wherever both exist, since a name denoting a type matches by membership under either. The Type alternative exists for the forms only a type can spell, a union of literal types being the or-pattern for constants with no combinator: 'GET' | 'HEAD' is one type pattern whose test is two comparisons. A MatchLiteralPattern and a literal Type likewise coincide; the literal form is preferred and the semantics are one.

26.2.1 PatternMatches ( P, subject, cache, env )

The abstract operation PatternMatches takes arguments P (a Parse Node), subject (an ECMAScript language value), cache (a Match Cache Record), and env (an Environment Record) and returns either a normal completion containing a Boolean or a throw completion. It decides whether the pattern P matches subject, initializing the bindings P declares in env as it does. Sub-patterns are tried in source order and the operation returns at the first failure, so user code a pattern can reach, a getter, a matcher, an interpolation, runs at most once and only up to the deciding test. It performs the following steps when called:

  1. If P is MatchOrPattern : MatchOrPattern or MatchAndPattern, then
    1. Let bindings be the states of the bindings named by the BoundNames of the first alternative in env.
    2. Let left be ? PatternMatches(the first alternative, subject, cache, env).
    3. If left is true, return true.
    4. Restore those bindings in env to bindings. This restoration does not undo observable effects or discard entries in cache.
    5. Return ? PatternMatches(the second alternative, subject, cache, env).
  2. If P is MatchAndPattern : MatchAndPattern and MatchUnaryPattern, then
    1. Let left be ? PatternMatches(the first operand, subject, cache, env).
    2. If left is false, return false.
    3. Return ? PatternMatches(the second operand, subject, cache, env).
  3. If P is MatchUnaryPattern : not MatchUnaryPattern, then
    1. Let inner be ? PatternMatches(the operand, subject, cache, env).
    2. If inner is true, return false; else return true.
  4. If P is a parenthesized pattern, return ? PatternMatches(its MatchPattern, subject, cache, env).
  5. If P is MatchPrimaryPattern : _, return true.
  6. If P is MatchPrimaryPattern : extends TypePattern, return MatchTypePattern(the TypePattern, subject, env).
  7. If P is a MatchBindingPattern, then
    1. If P has a TypeAnnotation, then
      1. Let ok be ? IsOfType(subject, the Type Record it denotes).
      2. If ok is false, return false.
    2. Perform ! env.InitializeBinding(the StringValue of P's BindingIdentifier, subject).
    3. Return true.
  8. If P is ${ Expression }, then
    1. Let v be ? GetValue(? Evaluation of Expression).
    2. Return MatchConstant(subject, v).
  9. If P is a MatchLiteralPattern, then
    1. Let v be ? GetValue(? Evaluation of its ShortCircuitExpression), except that a numeric literal takes the contextual type of the pattern's position (1.6.6).
    2. If v is a Range, return ? MatchRange(subject, v).
    3. If v is an Object with a [[RegExpMatcher]] internal slot, return ? MatchRegExp(subject, v, none, cache, env).
    4. If P's ShortCircuitExpression is a NumericLiteral with no preceding + or - whose value is a zero, return the result of the sameValueZero comparison of subject and v within their type, false where their types differ.
    5. Return MatchConstant(subject, v).
  10. If P is a Type, then
    1. Let t be the Type Record P denotes.
    2. Let structural be ? MatchStructuralType(t, subject, cache).
    3. If structural is not not-applicable, return structural.
    4. Return ? IsOfType(subject, t).
  11. If P is a MatchNamePattern alone, then
    1. If P resolves to a type, then
      1. Let t be the Type Record it denotes.
      2. Let structural be ? MatchStructuralType(t, subject, cache).
      3. If structural is not not-applicable, return structural.
      4. Return ? IsOfType(subject, t).
    2. Let v be ? GetValue(? Evaluation of P read as the member expression it spells).
    3. If v is a Type Object, return ? IsOfType(subject, the Type Record v reifies).
    4. If v is a Range, return ? MatchRange(subject, v).
    5. If v is an Object with a [[RegExpMatcher]] internal slot, return ? MatchRegExp(subject, v, none, cache, env).
    6. If v is an Object, then
      1. Let matcher be ? GetMethod(v, %Symbol.customMatcher%).
      2. If matcher is not undefined, then
        1. Let r be ? Call(matcher, v, « subject »).
        2. If r is an Object, throw a TypeError exception.
        3. Return ToBoolean(r).
    7. Return MatchConstant(subject, v).
  12. If P is MatchNamePattern ( MatchPatternList? ), then
    1. Let v be ? GetValue(? Evaluation of the MatchNamePattern read as the member expression it spells).
    2. If v is not an Object, throw a TypeError exception.
    3. Let matcher be ? GetMethod(v, %Symbol.customMatcher%).
    4. If matcher is undefined, throw a TypeError exception.
    5. Let r be ? Call(matcher, v, « subject »).
    6. If r is null, return false.
    7. If r is not an Object, throw a TypeError exception.
    8. Let len be ? LengthOfArrayLike(r).
    9. If len ≠ the number of patterns in the MatchPatternList, or the list is absent and len ≠ 0, throw a TypeError exception.
    10. Let i be 0.
    11. For each pattern p of the MatchPatternList, in order, do
      1. Let e be ? Get(r, ! ToString(𝔽(i))).
      2. If ? PatternMatches(p, e, cache, env) is false, return false.
      3. Set i to i + 1.
    12. Return true.
  13. If P is a juxtaposition of a MatchNamePattern and an ObjectMatchPattern or ArrayMatchPattern, then
    1. Let t be the Type Record the MatchNamePattern denotes; it is a TypeError exception at run time if it denotes none.
    2. Let ofType be ? IsOfType(subject, t).
    3. If ofType is false, return false.
    4. Return ? PatternMatches(the structural pattern, subject, cache, env).
  14. If P is an ObjectMatchPattern, return ? MatchObjectPattern(P, subject, cache, env).
  15. Assert: P is an ArrayMatchPattern.
  16. Return ? MatchArrayPattern(P, subject, cache, env).
Note

NaN needs no rule of its own: it is a name pattern holding a Number, so MatchConstant compares it with the type's sameValue, under which a NaN equals a NaN, and a float32 NaN does not match a float64 one because their types differ. MatchConstant(a, b) is the sameValue comparison within one type: false where the types of a and b differ, and the type's T::sameValue otherwise, with SameValue for values that are not of a numeric type. For an interned composite that is one pointer comparison, and for a value type class instance it is the fieldwise comparison its === performs. The bare-zero step is the one SameValueZero literal: 0 matches both zeros of the position's type at every width, while an explicit +0 or -0 distinguishes them, the design's rule stated per type.

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 (25.5.5); a matcher that returns an object there is a TypeError, as parentheses on a boolean matcher are, so the two protocols cannot be confused silently. Matchers and interpolations are the patterns that run user code, and they run in source order at most once each per match attempt.

26.2.2 MatchObjectPattern ( P, subject, cache, env )

The abstract operation MatchObjectPattern takes arguments P (a Parse Node), subject (an ECMAScript language value), cache (a Match Cache Record), and env (an Environment Record) and returns either a normal completion containing a Boolean or a throw completion. It matches an ObjectMatchPattern: each named member must be present, by the 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:

  1. If subject is not an Object, return false.
  2. For each MatchProperty prop of P, in order, do
    1. Let key be the property key prop names.
    2. If no element of cache.[[Reads]] has [[Object]] subject and [[Key]] key, then
      1. Let present be ? HasProperty(subject, key).
      2. If present is true, let value be ? Get(subject, key); else let value be undefined.
      3. Append the Record { [[Object]]: subject, [[Key]]: key, [[Present]]: present, [[Value]]: value } to cache.[[Reads]].
    3. Let read be the element of cache.[[Reads]] whose [[Object]] is subject and whose [[Key]] is key.
    4. If read.[[Present]] is false, return false.
    5. If ? PatternMatches(the sub-pattern of prop, read.[[Value]], cache, env) is false, return false.
  3. If P has a rest MatchBindingPattern, then
    1. Let rest be OrdinaryObjectCreate(%Object.prototype%).
    2. Perform ? CopyDataProperties(rest, subject, the List of the property keys the MatchProperty list names).
    3. Perform ! env.InitializeBinding(the StringValue of its BindingIdentifier, rest).
  4. Return true.
Note

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 HasProperty and at most one Get per key per match. A composite subject makes the cache unnecessary rather than the guarantee weaker, since its every read is idempotent, and an implementation may skip it there (25). An absent optional member fails the pattern rather than matching undefined, which is the design's presence rule; matching the absence is a guard's job. A member the pattern does not name is ignored, since this type system has width subtyping and no exact object type, so a pattern has nothing to say about members it did not name and no token it must spell to stay silent about them; a member whose value does not matter is named against the wildcard, which tests presence and reads nothing.

26.2.3 MatchArrayPattern ( P, subject, cache, env )

The abstract operation MatchArrayPattern takes arguments P (a Parse Node), subject (an ECMAScript language value), cache (a Match Cache Record), and env (an Environment Record) and returns either a normal completion containing a Boolean or a throw completion. It matches an ArrayMatchPattern through iteration rather than through an array test, which is what reaches every array-shaped value of this proposal - a [N].<T> need not be an Array exotic object, a tuple composite is iterable by kind rather than by prototype, and a typed view answers no array predicate - memoized per subject: elements are pulled as patterns need them, a pattern without a rest element requires the iterator exhausted at its length, and a rest binding collects the remainder into a fresh Array. It performs the following steps when called:

  1. If subject is not an Object, return false.
  2. If no element of cache.[[Iterations]] has [[Object]] subject, then
    1. Let iteratorRecord be ? GetIterator(subject, sync).
    2. Append the Record { [[Object]]: subject, [[Elements]]: a new empty List, [[Done]]: false, [[Iterator]]: iteratorRecord } to cache.[[Iterations]].
  3. Let it be the element of cache.[[Iterations]] whose [[Object]] is subject.
  4. Let n be the number of patterns in P's MatchPatternList, or 0 where it is absent.
  5. Perform ? MatchIteratorFill(it, n + 1).
  6. If P has no rest MatchBindingPattern, then
    1. If the number of elements of it.[[Elements]] ≠ n or it.[[Done]] is false, return false.
  7. Else,
    1. If the number of elements of it.[[Elements]] < n, return false.
  8. Let i be 0.
  9. For each pattern p of the MatchPatternList, in order, do
    1. If ? PatternMatches(p, it.[[Elements]][i], cache, env) is false, return false.
    2. Set i to i + 1.
  10. If P has a rest MatchBindingPattern, then
    1. Repeat, while it.[[Done]] is false,
      1. Perform ? MatchIteratorFill(it, the number of elements of it.[[Elements]] + 1).
    2. Let restList be the List of the elements of it.[[Elements]] from index n onward.
    3. Perform ! env.InitializeBinding(the StringValue of its BindingIdentifier, CreateArrayFromList(restList)).
  11. Return true.

26.2.4 MatchTypePattern ( P, subject, env )

The abstract operation MatchTypePattern takes arguments P (a TypePattern Parse Node), subject (an ECMAScript language value), and env (an Environment Record) and returns a Boolean. It matches a type against a pattern of types: subject must be a Type Object, its type must be within the type P writes, and each pattern slot of P binds the component of subject that stands where it does. It performs the following steps when called:

  1. If subject is not a Type Object, return false.
  2. Let t be the Type Record subject reifies.
  3. If P contains no pattern slot, return IsAssignable(t, the Type Record P denotes).
  4. Let pattern be the Type Record P denotes with each pattern slot replaced by a fresh slot (22).
  5. Let bindings be the one-sided structural unification of pattern against t (22).
  6. If bindings is null, return false.
  7. For each pattern slot s of P, do
    1. Let v be the component bindings binds for s: the Type Object where that component is a Type Record, and the component itself where it is a metadata object or another value.
    2. If IsAssignable(RuntimeTypeOf(v), the Type Record s's Type denotes) is false, return false.
  8. For each pattern slot s of P, perform ! env.InitializeBinding(the StringValue of s's BindingIdentifier, the value bound for s above).
  9. Return true.
Note

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 keyword promises to a reader who has met it in a type position. With slots the test is unification, because a slot has to bind the component that stands where it does, and unification is the operation that finds it; a slot is a CaptureBinding (1.4.3.2), and its written domain or bound is then an ordinary check on what it bound, exactly as 22 says an 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 dynamic, although 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 shape it expects, which is the same discipline 22 already imposes in refusing a slot inside an deferred.

The positions a slot may stand in are the ones this specification fixes: the [[Metadata]] of a parameterized Type Record and the [[Arguments]] of a nominal one. A slot standing where the generics extension rather than this specification defines the position, a variadic pack among them, reaches as far as that extension does (4), and a slot inside a deferred record is refused outright, which is the boundary 22 already draws for the same reason: a position no operation here can invert is a position no pattern can bind.

A slot binds a value rather than always a Type Object because the positions of a parameterized type do not all hold types: float32.<const B: NumberBounds> binds the metadata object, which is what makes { ...B } in the design's constraintsFor an ordinary spread, while Map.<K: type, V: type> binds Type Objects. RuntimeTypeOf of the bound value is what the constraint is checked against in either case, and it is also the binding's Static Type (26.3).

26.2.5 MatchIteratorFill ( it, count )

The abstract operation MatchIteratorFill takes arguments it (a Record of a Match Cache Record's [[Iterations]]) and count (a non-negative integer) and returns either a normal completion containing unused or a throw completion. It advances the memoized iteration until it.[[Elements]] has count elements or the iterator is exhausted. It performs the following steps when called:

  1. Repeat, while it.[[Done]] is false and the number of elements of it.[[Elements]] < count,
    1. Let next be ? IteratorStepValue(it.[[Iterator]]).
    2. If next is done, set it.[[Done]] to true.
    3. Else, append next to it.[[Elements]].
  2. Return unused.

26.2.6 MatchStructuralType ( t, subject, cache )

The abstract operation MatchStructuralType takes arguments t (a Type Record), subject (an ECMAScript language value), and cache (a Match Cache Record) and returns either a normal completion containing either a Boolean or not-applicable, or a throw completion. It matches a structural object type MEMBER BY MEMBER, reading each member through cache, so that the guarantee in 26.1 — that a property is read at most once however many patterns look — holds for a type pattern as it does for an object pattern. It returns not-applicable where t is not a type this treatment applies to, and the caller then uses IsOfType. It performs the following steps when called:

  1. If t is not an object Type Record and is not a structural nominal type whose [[Structure]] is one, return not-applicable.
  2. Let shape be t or its [[Structure]], as applicable.
  3. If shape has any index signature, return not-applicable.
  4. If any member of shape.[[Properties]] is optional or is not named by a String, return not-applicable.
  5. If shape.[[Properties]] is empty, return not-applicable.
  6. If subject is not an Object, return false.
  7. For each element p of shape.[[Properties]], do
    1. If no element of cache.[[Reads]] has [[Object]] subject and [[Key]] p.[[Key]], then
      1. Let present be ? HasProperty(subject, p.[[Key]]).
      2. If present is true, let value be ? Get(subject, p.[[Key]]); else let value be undefined.
      3. Append the Record { [[Object]]: subject, [[Key]]: p.[[Key]], [[Present]]: present, [[Value]]: value } to cache.[[Reads]].
    2. Let read be the element of cache.[[Reads]] with [[Object]] subject and [[Key]] p.[[Key]].
    3. If read.[[Present]] is false, return false.
    4. If ? IsOfType(read.[[Value]], p.[[Type]]) is false, return false.
  8. Return true.
Note

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.

26.2.7 MatchConstant ( a, b )

The abstract operation MatchConstant takes arguments a (an ECMAScript language value) and b (an ECMAScript language value) and returns a Boolean. It is the sameValue comparison within one type: false where the types of a and b differ, the type's T::sameValue where they are of one numeric type, and SameValue otherwise. It performs the following steps when called:

  1. If a and b are not values of one type, return false.
  2. If a is a value of a numeric type, return the sameValue comparison of that type applied to a and b.
  3. Return SameValue(a, b).

26.2.8 MatchRange ( subject, r )

The abstract operation MatchRange takes arguments subject (an ECMAScript language value) and r (a Range) and returns either a normal completion containing a Boolean or a throw completion. It matches a range pattern by containment. It performs the following steps when called:

  1. If subject is not a value of a type ordered with the element type of r, return false.
  2. Return ? the result of the containment test the range's contains performs on subject (21), which is at most two comparisons.

26.2.9 MatchRegExp ( subject, r, sub, cache, env )

The abstract operation MatchRegExp takes arguments subject (an ECMAScript language value), r (an Object with a [[RegExpMatcher]] internal slot), sub (a Parse Node or none), cache (a Match Cache Record), and env (an Environment Record) and returns either a normal completion containing a Boolean or a throw completion. It matches a regular expression pattern against the entire subject, and matches the juxtaposed structural pattern sub, where present, against the match result. It performs the following steps when called:

  1. If subject is not a String, return false.
  2. Let result be ? RegExpExec(r, subject) restricted to a match of the entirety of subject: the match must begin at index 0 and end at the length of subject, as the whole-string discipline of a pattern constraint requires (3), so a search is spelled as one.
  3. If result is null, return false.
  4. If sub is none, return true.
  5. Return ? PatternMatches(sub, result, cache, env).
Note

The match result's Static Type is the exact result type the regular expression's captures give it, so groups: { let year } binds the typed group and a misspelled group name is a type error against that type. The result is constructed once and its reads enter the cache as any subject's do.

26.3 Static Semantics of Patterns

The pattern type of a pattern P, PatternType(P), is the Type Record a successful match establishes, defined by the form of P: a Type, the type it denotes; a name resolving to a type or evaluating to a Type Object of known type, that type; a literal, its literal type at the position type by 1.6.6, so that a numeric literal at a position with no numeric member keeps its base number; a name denoting a constant of literal or enumerator type, that type; a binding with an annotation, the annotated type, and without one, any; a range whose endpoints are compile-time constants, the position type refined by the interval's metadata (21); a regular expression, string; an extractor whose head denotes a type, that type, which is the narrowing its matcher's author asserts and the checker takes, as it takes a declared narrowing predicate's, and any for a head that denotes no type; a juxtaposition, PatternType of its structural part narrowed to its head's type; an ObjectMatchPattern, the object Type Record whose properties are the named members, each required, at the PatternType of its sub-pattern; an ArrayMatchPattern, any, since testing iteration establishes no indexed storage type; its sub-patterns retain the position types established by the static iteration contribution; an and, the NarrowTo of its operands' pattern types in order; an or, the union of its alternatives'; an extends pattern, the type type (1.5.21), since what a successful match establishes of the subject is that it is a Type Object; a wildcard, a not, or an interpolation of non-literal type, any.

A pattern test divides the Static Type s of its position in two: MatchNarrow(s, P), the type of a value of s that P can match, and MissNarrow(s, P), the type of a value of s that P can miss, each empty where there is no such value. PatternType alone does not give both, because it is any both for a pattern that matches every value and for one that establishes nothing, and because the miss of not Q is the match of Q. They are defined by the form of P, a parenthesized pattern being its MatchPattern:

  • A Type, a name denoting a type or a constant of literal or enumerator type, a literal other than a regular expression or a range, an interpolation of literal type, and a binding with an annotation that does not denote any, each of which matches exactly the values of its PatternType: MatchNarrow is NarrowTo(s, PatternType(P)) and MissNarrow is NarrowFrom(s, PatternType(P)).
  • The wildcard, and a binding with no annotation or one denoting any, each of which matches every value: MatchNarrow is s and MissNarrow is empty.
  • not Q: MatchNarrow is MissNarrow(s, Q) and MissNarrow is MatchNarrow(s, Q).
  • Q or R: MatchNarrow is the union of MatchNarrow(s, Q) and MatchNarrow(s, R), and MissNarrow is MissNarrow(MissNarrow(s, Q), R).
  • Q and R: MatchNarrow is MatchNarrow(MatchNarrow(s, Q), R), and MissNarrow is the union of MissNarrow(s, Q) and MissNarrow(MatchNarrow(s, Q), R).
  • An ObjectMatchPattern or ArrayMatchPattern: MatchNarrow is empty where the structural impossibility rule below holds, and otherwise NarrowTo(s, PatternType(P)) for an object pattern and s for an array pattern; MissNarrow is s with every atom the pattern covers by the structural rules of 26.3.1 removed.
  • Every other form, each of which may miss a value of its PatternType - a regular expression, a range, an extractor, a juxtaposition, an extends pattern, and an interpolation of non-literal type: MatchNarrow is NarrowTo(s, PatternType(P)) and MissNarrow is s. For a range, MatchNarrow is empty where no member of s is of a type ordered with the range's element type, since MatchRange answers false for such a value whatever the interval; a Number and a BigInt are ordered with each other. Where every endpoint the range has is a compile-time constant, MatchNarrow is also empty where the interval holds no value of any member of s, an empty interval holding none (21.6), and MissNarrow is empty where every member of s is an integer type, or a literal type over one, every value of which the interval holds; a floating-point member is never wholly held, since its NaN lies in no interval. Apply these range rules separately to each known union member and canonicalize the union of the surviving results: on success remove a member whose domain cannot intersect the interval, and on failure remove a member whose entire domain is held by the interval. A partially overlapping member retains its existing type unless an existing refinement represents a narrower result. Unknown endpoints, unresolved domains or metadata, and an inexact endpoint representation supply no member-exclusion proof. This filtering introduces no new public Type Record or reflected type identity.

Either operation applied to empty gives empty, and the union of empty with a type is that type. Where MatchNarrow or MissNarrow is empty the pattern test can never succeed or can never fail, and 1.8.5.4 judges it, under its stability condition, where the test decides a branch.

For an interpolation pattern, it is a type error if the expression's Static Type and its position type are established to share no values. This judgment is separate from PatternType: a non-literal interpolation may contribute any to narrowing while still having a provably impossible comparison. Interpolation compares the value actually produced, without adopting the position type or performing a conversion. Unknown or overlapping types do not establish failure. The judgment applies at nested pattern positions and after preceding narrowing.

An array pattern's PatternType of any records the absence of additional indexed-storage information; it is not a test covering every value. Neither matching nor failing the pattern establishes a different indexed-storage type. It is therefore not a pattern that matches every value of its PatternType, and MissNarrow does not remove a subject atom through it: exclusion of an entire atom requires the array-pattern coverage rule of 26.3.1. The same restriction applies to array patterns within combinators and structural sub-patterns.

Within a pattern, each sub-pattern's position type is the subject's Static Type at that position, computed left to right: the subject narrowed by the preceding operand of an 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 static iteration contribution (1.14.8), an extracted position's type from the matcher's declared return. A binding's Static Type is NarrowTo of its position type and its PatternType. A pattern slot's Static Type is its written domain, or its position's domain where none is written, so const B: NumberBounds binds a NumberBounds and const K in a type position binds a type; a slot is a binding of the clause for every other purpose, immutable, in scope in the guard and the arm, and subject to the duplicate-name and or-consistency errors above. It is a type error if MatchNarrow of a sub-pattern's position type and the sub-pattern is empty under the stability condition of 1.8.5.4, the impossible-test rule applied within a pattern, in every position the pattern occurs in: each alternative of an 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 type error if an ObjectMatchPattern or ArrayMatchPattern occurs at a position whose Static Type is known and none of whose members can be an Object, by the first boundary of that condition, or if an ArrayMatchPattern of n elements occurs at a position every member of whose type has a static iteration contribution with a number of positions other than n, since MatchObjectPattern and MatchArrayPattern answer false for a value that is not an Object and MatchArrayPattern for a sequence of another length; this structural impossibility rule applies at the top of a pattern as well as within one; and it is a type error if a numeric literal's position type is a union of two or more numeric types, since the literal has two types it could take, resolved by and with a type or by an interpolation.

Impossible and irrefutable pattern judgments read a generic parameter through an established upper bound, as 1.8.5.4 requires. This includes range tests and match-clause selection. A verdict must hold for every value of the bound; an unknown bound, partial overlap, uncertain endpoint or unresolved metadata supplies no universal verdict. The projected bound is a proof domain only: surviving bindings, stores and result types retain the parameter's identity.

The rows this clause adds to Table 24: in the arm of a when P, MatchNarrow(s, P); in the arms after it, MissNarrow(s, P) where the clause is unguarded; and v is P narrows as when P does, in the positions the test governs. For ordinary match, the Static Type is the canonicalized union of its arm types, a throw arm and a diverging block contributing nothing (27) and a block arm contributing CompletionTypeOf of its StatementList (39.2); a contextual type on the match is the contextual type of each arm body.

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 Static Type is an array Type Record with [[Extent]] dynamic. An array contextual type supplies its element type as the contextual type of each arm body and as [[Element]] of the result: each collected value is admitted at that type, with an unknown contribution checked at run time. Without an array context, [[Element]] is the canonicalized union of the value-producing arm types; an unknown contribution makes [[Element]] any, without losing the known array result. A throw arm or diverging block contributes no element; a block arm contributes CompletionTypeOf of its StatementList. A scalar context supplies no arm context. The number of clauses does not establish an extent. The ordinary array element and reference-escape restrictions apply; contextual construction does not change array subtyping or make [].<any> assignable to an array of a more specific element type.

26.3.1 Exhaustiveness

A match whose subject is a Type Object has no atoms, the types being an open universe, so 26.3.1 requires such a match to carry a catch-all. The atoms of a Type Record t are: for an enum, its enumerators; for a sealed class, its direct subclasses and, where instantiable, itself; for boolean, true and false; for null and undefined, themselves; for a union of two or more members each of which is one of the preceding, an object type, a tuple type, or a composite type, the atoms of each member, an object, tuple, or composite member being its own atom; for a lone composite type, itself, so the coverage a union member gets applies to it alone, a composite being frozen with its members; for a dependent record type whose predicate is a discriminating where chain, the atoms of the union that chain denotes (5.3); for an intersection one or more of whose members is a union, the atoms of the union formed by distributing it, one member per choice of one arm from each such union intersected with the remaining members; and none otherwise. A union with a member of literal kind has atoms none: a closed set of literals that wants the check is an enum over its base, the standing decision of 1.11.8, restated rather than reopened.

An unguarded clause covers an atom a when: for a non-structural pattern P, MissNarrow(the type of a, P) is empty, which for a Type is IsSubtype(the type of a, PatternType(P), « ») and for not Q is MatchNarrow(the type of a, Q) being empty, reading an enumerator atom's type as its enumerator, covered by the constant pattern naming it or a type pattern naming its enum, and reading true, false, null, and undefined atoms as their literal patterns and types; an object or record-composite atom is covered by an object pattern each of whose named members is declared required by the atom and whose sub-patterns each cover the member's declared type; an array pattern covers an atom only where its static iteration contribution guarantees a sequence admitted by the pattern's length test and each sub-pattern covers its contributed position type, the subsumption the impossible-test rule computes run in the other direction. For structural patterns these conditions apply instead of the PatternType test, including within combinators: an 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 wildcard; a MatchBindingPattern with no TypeAnnotation, or one whose annotation denotes any; a parenthesized irrefutable pattern; an 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 Static Type are not none and every atom is covered by some clause, or when the Static Type folded by MissNarrow over its unguarded clauses' patterns is empty, as it is for match (1) { when 1: ... }, whose literal type the literal pattern names. It is a type error for a match whose subject's Static Type is not any not to be exhaustive; an untyped subject is not judged, and the TypeError of 26.1.1 remains its backstop. Neither this rule nor the one that follows applies to the match all form (26.1): no clause of a match all need match, since its value is the List of those that did and an empty List is an answer rather than a missing case; and a later clause there is reached whether or not an earlier one matched, so it is narrowed by nothing the earlier clause failed to establish. A clause of a match all is still a narrowing form, and it is a type error if MatchNarrow of the subject's Static Type and the clause's pattern is empty under the stability condition of 1.8.5.4, since that clause can never contribute to the result; the sub-pattern rules above apply to its pattern as to any other. It is a type error if a clause can match nothing the preceding unguarded clauses have left: where MatchNarrow of the subject's remaining type, the Static Type folded by MissNarrow over the preceding unguarded clauses' patterns, and the clause's pattern is empty, or where every atom the clause covers is covered before it; a default whose preceding clauses cover every atom of a subject with atoms is that error's instance, so a covered 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.

Note 1

The last two rows are derived forms, and they are derived for this check alone. A discriminating where chain denotes a union and an intersection over a union distributes into one, but neither changes what the type is: Reflect.typeOf reports the dependent record type and the intersection, CanonicalizeType flattens an intersection without distributing it (1.2.2.8) so identity and interning are untouched, and assignability compares against the type as written. What they change is only that the checker sees the cases the reader sees, which is what the check is for. Distribution is bounded by the type expression, since it multiplies the arm counts of the unions an intersection names, and it needs no new subtyping: IsSubtype already decides an intersection on the left by finding one member within the target, so an arm naming A covers an atom A & C.

Requiring exhaustiveness of every match is what makes the TypeError of 26.1.1 unreachable from typed code: a match whose subject holds a value of its Static Type has a clause for it, so the throw is left for an untyped subject and for a value that has drifted out of its type, where the boundary check is the backstop as everywhere else (Table 23). The rule pairs with unreachability into one statement a program can be told: exactly one of "this match needs a catch-all" and "this match must not have one" holds of it, since a closed subject covered atom by atom rejects the 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.

Note 2

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 set could move.

An atom set is read from a declaration, and no context rewrites one. The enum, enumerator, tuple, record, block, let, and const contexts admit no return replacement at all, so the enumerators a match covers are the ones the declaration lists; a decorator that mutates an enum object at run time changes no declared type and so moves no atom. A read of a declared enumerator has the enumerator's literal type, so a test of it is judged as a literal is; but a switch or match subject that a narrowing has reduced to one enumerator literal is still a subject of the enum's type for coverage and for the reachability of its arms, as the enum's declaration, not the subject's current value, says which enumerators there are.

A class replacement is a subclass. The Reflect.Class context does admit a replacement, and the design's singleton returns class extends T, but the declared return type is T and a class type is nominal (Table 1), identified by the declaration that introduced it, so a structurally identical class of another declaration is not a T and only T's own declaration or a subclass satisfies the return. Coverage is IsSubtype over the atom's type, which is closed downward, so an arm naming a class matches an instance of whatever the decorator returned. Where the class decorated is 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 (1.11.9), and one in the same module declares a case the checker reads like any other, so the check is stricter rather than wrong.

A field replacement is a value, not a type. Reflect.ClassField replaces the field's initial value at type T, and the accessor contexts replace a getter at () => T and a setter at T, so a discriminant declared with a literal type still holds a value of it and a member a pattern reads still has the type it was declared with.

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 Table 23 already provides, not a property of match.

Note 3

The switch exhaustiveness of 1.11.8 and 1.11.9 is the two-atom-kind instance of the coverage half of this rule, and nothing about 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 none absent a qualifying 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.

26.4 The is Operator Takes a Pattern

The right operand of is (1.4.5) is widened from Type to MatchPattern, of which a Type is one form, so every existing is keeps its parse and its meaning. v is P evaluates PatternMatches(P, the value of v, a fresh Match Cache Record, env) and returns the Boolean, where env is a new declarative Environment Record in which P's BoundNames are created as immutable bindings; the bindings are in scope in exactly the positions the truth of the test governs, the positions 1.8.5 enumerates, each such position evaluating in env. It is a type error to reference one of those bindings anywhere else, and it is a type error for a binding-carrying is to occur where no position is governed by its truth, since the bindings would then exist nowhere.

Note

subject is P is the one-arm match, exactly: it matches, binds, and narrows as when P would, which is what makes while (read() is Ok(let chunk)) a loop whose body sees chunk and whose exit is the miss. The dependent-record is of 5.2 is the Type case of this operator, unchanged.

26.5 %Symbol.customMatcher%

This extension adds the well-known symbol %Symbol.customMatcher% (Symbol.customMatcher), the method a name and an extractor pattern consult. The typed protocol is a method from a subject to a tuple or null, or to a Boolean for the parenthesis-less membership form; its declared signature is the whole contract, overloads select on the subject's Static Type, and a generic matcher infers its parameters from the subject as any generic call does, which is how an extractor's bindings are typed with nothing annotated (design, patternmatching.md).

A statically established custom matcher must be callable. Finding the symbol-named property alone does not satisfy the method requirement. It is a type error when the established member type has no callable or unknown alternative; callable intersections and overloads retain their ordinary judgments. No getter or matcher runs during this check.

27 Divergence

This clause, like 33, is owed elsewhere and stated here because this is the first clause that computes with it. The design defines divergence in its switch chapter, and 26.3 already reads a 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 path of control through it completes normally. The analysis composes statement completions, established immediate call-result contracts and effective awaited-result contracts; it does not execute expressions or infer termination from arbitrary callee bodies:

Two consequences follow, both of which the design states. A switch clause whose body diverges needs no break and is not a fallthrough; and a function whose body diverges satisfies a non-void return type with no trailing return.

Note

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 true to a reader, because a rule that reasoned about the condition would have to reason about values, and the design's analyses do not. That is why the list names while (true) and for (;;) as forms rather than describing a condition.

28 The Iteration Types

It is a type error to consume a value through an iteration protocol when its effective declared entry-hook type proves that every possible selected hook is invalid. This applies to for-of, for await, array spreads, array binding and assignment patterns, and yield*, using each consumer's synchronous or asynchronous protocol. The proof uses the actual well-known Symbol key and the effective member contract, including inherited and specialized members. A known non-object hook value is non-callable; an open object-shaped hook does not prove non-callability merely by lacking a declared call signature.

A nullish synchronous hook supplies no iterator. For asynchronous selection a nullish async hook instead selects the synchronous fallback, while a present non-callable async hook fails without fallback. Optional and union members are judged over these selection paths. A missing declaration on an open object, an unrelated Symbol, a Symbol index signature alone, or an unknown alternative supplies no proof of failure. The checker does not call a getter or an iterator to answer the judgment. Argument spread's separate named-property protocol is unaffected: an invalid iterator alone does not disqualify an object from named argument spreading. Reference iteration retains its separate index-based rules.

The negative iteration proof also follows effective callable contracts through the stages reached by the consumer. A known non-object entry-hook result fails iterator acquisition. If a step is requested, a known non-callable next or known non-object step result also proves failure. An empty array binding or assignment pattern acquires the iterator but does not call 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 iterator, apply the step-result Object judgment to the awaited result of next. Do not await the entry-hook result. For synchronous fallback, apply the Object judgment to the raw synchronous step result; awaiting a yielded value is a later stage and does not await that step result. Unknown returns, open members, or unresolved overload results prevent a closed proof at that stage. Reject only when every possible selected path is known to fail a stage reached on that path. These checks invoke no getter, hook or step and change no iterator-closing or reference-liveness requirement.

The bounded assimilation judgment of 1.6.2.23 also applies to a real asynchronous iterator's 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 synchronous step result, then apply the assimilation judgment to its effective value contract, even when done is true. Do not assimilate the raw iterable, entry-hook result or raw synchronous step. A real async iterator's 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 path is disproved. Unknown contracts, viable signatures, non-callable 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.

Iterator closing contributes a separate negative protocol judgment. It is a type error when a consumer is known to reach a closing stage whose effective contract fails on every selected path and whose failure is not suppressed by the incoming completion. A known present non-nullish non-callable 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 normal completion without requesting a step. A definitely-yielding first step followed immediately by 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 iterator not done, when each element is a hole or a target whose store cannot throw first: a binding identifier with neither an Initializer nor an annotation, or an assignment to a let binding with no annotation. Possible exhaustion, unresolved control flow, or an incoming throw completion that takes precedence over the closing failure prevents this proof at that edge.

For IteratorClose, judge the raw result Object; a Promise is an Object and is not awaited. For AsyncIteratorClose on an actual async iterator, judge the awaited result. Unresolved async-from-sync adapter paths remain dynamic. Use effective inherited and specialized contracts, the actual entry-hook Symbol, and the participating-source judgment above; unknown and viable alternatives prevent a closed proof. These checks execute no close method, getter or step. Accepted executions retain all required cleanup side effects, completion precedence, reference decay and liveness checks.

The negative iteration judgment includes the typed argument boundary of each reached implicit call. Entry hooks, ordinary consumer steps and IteratorClose or AsyncIteratorClose supply an empty argument List. Initial yield* forwarding supplies « undefined »; later forwarding is judged only where its actual argument contract is established. Apply the ordinary parameter-boundary rules, including optional/defaulted parameters, admitted undefined, implicit conversion, reference markers, generic substitution and rest distribution by SequenceAssignment. A possible applicable signature prevents an argument-failure proof; a valid result on a signature whose arguments cannot be supplied does not. Implicit calls may ignore surplus arguments as their runtime parameter boundary does. This rule imposes no canonical declaration shape, does not request a step for an empty pattern, and retains closing reachability and completion precedence. Async/sync adapter arguments are judged only along established forwarding paths. Unknown later resume arguments remain dynamic. Named argument spreading and index-based reference iteration keep their separate protocols.

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 design writes Iterator.<T> throughout and this specification has named no such type, so what a helper returns and what a hand-written iterator satisfies have both been undescribable. It precedes 33 because the generator types are stated in terms of it.

The iteration result type IteratorResult.<T, R> is the union of two object Type Records, one with members value of T and done of the literal type false, the other with value of IteratorCompletionValue(R) and done of the literal type true. It is discriminated by done, so a match over an iteration result binds value at T in one arm and at IteratorCompletionValue(R) in the other (26.3.1), and reading one requires no narrowing rule of its own.

Note 1

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 shape in one place and an identity in another, and which one a program gets depends on the order its resolution happens to consult. That divergence is invisible where a boundary check is elided (1.7.9), because nothing then asks the question at run time.

The protocols are interface types, satisfied structurally through their structural form (1.4.2.9):

Iterator is also a built-in class, the one on which the iterator helper methods are defined, and it declares that it implements IterableIterator. AsyncIterator is the same for the asynchronous protocol. Both join the built-in type names of 1.5.27. A value of the class satisfies the interface by refinement rather than by inspection (1.2.3.1), so it is decided by the declaration; a value that merely has the members satisfies it through the structural form.

A bare argument is the first: Iterator.<uint8> is Iterator.<uint8, void, void>, and likewise for the other five. This is the shorthand 33 states for the generator families, and it is one rule rather than two.

Note 2

R and N default to void as they do in the generator families. A generator's body return obligation and its operation results are distinct: conformance to a structural iterator follows 34, including ordinary exhaustion. Equal default spellings do not establish an unconditional same-argument implementation relation.

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.

Note 3

The element parameter is invariant, as every generic in this specification is (1.2.3.1), so an 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 iterator produces its element and never accepts one, which is the condition the invariance rule states for a mutable container and which these types do not meet. Admitting a declared variance is the generics extension's to make, and the invariance rule already names C# and Kotlin as the precedent; both spend that feature on exactly these types. It can be admitted later without invalidating a program, since a variance annotation only adds legal conversions, which is how .NET added covariance to its enumerable interfaces after they had shipped invariant.

29 Fixed String Properties

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 type error. Deleting that property is also a type error. The same judgments apply to a character-index property only when its existence is established, using the String's UTF-16 length and the base language's canonical property-key rules. Non-writability and non-configurability are separate reasons for these judgments.

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.

30 IteratorCompletionValue ( R )

The abstract operation IteratorCompletionValue takes argument R (a Type Record) and returns a Type Record. This operation separates a discarded completion contract from the value carried by a completed iteration result. It is a built-in type computation, not a new global binding or source operator. It performs the following steps when called:

  1. If R.[[Kind]] is void, return the any Type Record.
  2. If R.[[Kind]] is deferred and R.[[Operator]] is iterator-completion, return R.
  3. If R.[[Kind]] is parameter or deferred, return the Type Record { [[Kind]]: deferred, [[Operator]]: iterator-completion, [[Operands]]: « R » }.
  4. Return R.

Substitution into the deferred operation substitutes its operand and applies IteratorCompletionValue again. The existing identity and reflection rules for deferred Type Records apply. An open operation grants no more precise bound than any. This rule does not change an ordinary property of type void, rewrite the yielded component, or distribute a discard interpretation into other type constructors. An explicit undefined completion remains undefined.

The next, optional return and optional throw methods share the iteration result type, wrapped in a Promise for async protocols. Their inputs remain separate: 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-undefined body return at a written void obligation.

A nominal generator delegation's terminal contribution is GeneratorCompletionValue(R) (34); structural delegation reads the corresponding value component of the selected result shape. Self-iteration of IterableIterator and AsyncIterableIterator preserves T, R and N. Ordinary iterable shorthand retains its default discard contract. Existing async-from-sync assimilation, async return assimilation, ordinary lookup, receiving admission, effects, failed state and reference-liveness rules remain in force. The type computation executes no hook, iterator method or callback and extends no reference lifetime.

31 Explicit Intrinsic Mapper Specialization

The intrinsic Range and synchronous Iterator 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 replacement does not inherit intrinsic generic capability from the property name.

A stored intrinsic mapper specialization is interned by its original function Object and completed output Type Record under SameType. It is independent of the receiver. Equivalent named, positional, alias and spread applications have the same identity; distinct nominal declaration identities and distinct original function Objects remain distinct. A direct member application preserves its already evaluated receiver and selected callee without a second getter invocation. Public 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 synchronous iterator contract with item U, completion undefined and optional any next input. A statically established receiver retains its more precise input contribution under 21.6.7 and 32; specialization does not bind a receiver or replace its item type with U. An internal rename used to avoid capture does not change a declaration's public parameter name: named type argument selection and reflection use its declaration spelling, while substitution retains the distinct binding identity.

For the selected intrinsic Iterator mapper, capture the completed U in the existing mapping closure. After Call of the mapper completes normally and before Yield of that mapped value, admit the normal result to U by the ordinary value-boundary rules, including their permitted conversions. If admission is abrupt, perform the same IfAbruptCloseIterator step as for an abrupt mapper call. Preserve the existing close precedence, completion state and subsequent-resume behaviour. Do not execute the callback at specialization or helper creation, wrap or alter the callback, or add an iterator facade.

For U equal to void, retain the callback-result discard interpretation: it supplies no claim that the actual mapped value is undefined. For any, retain the value unchanged. An empty traversal has no mapped value to admit. A Promise or thenable result is an item; this synchronous operation introduces no Await. Admission and suspension retain all existing reference decay and liveness checks.

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 List. A selected user generic function or overload set uses the ordinary generic selection, constraint, where-clause and invocation rules; a nongeneric replacement fails before its body. Admit the delegated normal result to the helper-bearing U protocol by the existing receiving rules. This boundary neither executes replacement protocol methods to prove their future values nor strengthens a gradual user declaration beyond the existing callable contracts. Plain inferred mapping retains its ordinary forwarding behaviour.

32 Iterator Helper Callback Contracts

These contracts apply to the synchronous intrinsic Iterator helpers on a resolved helper-bearing receiver, rather than to an arbitrary user method with the same name. The iteration interfaces alone do not declare these helpers.

The result domain of a flatMap callback is an Object accepted by GetIteratorFlattenable with reject-primitives. It includes object iterables and direct iterator Objects whose next method is used when @@iterator is nullish; it excludes all primitive results, including Strings. It is a type error when every established callback result alternative fails the reached flattening protocol. A viable or unknown alternative prevents the negative proof. Eligibility is separate from inference of the flattened element type (21.6.10), and does not use Array.prototype.flatMap's scalar-result rule. No callback, getter or iterator method runs during checking.

Reduction follows 21.6.8, using the established iterator item contribution. One accumulator contract governs the initial value when present, recurrence inputs, callback result and completed reduction. The clause specifies anchoring, genuine uncertainty, explicit any results, empty traversal and ordinary declaration and receiving admission.

These synchronous contracts do not specify future asynchronous helpers. In particular, they do not add awaits to callback results or change generator suspension, protocol effects or reference liveness.

33 The Generator Types

When inferring a generator's completion component, including for a generator do-expression, preserve an established undefined contribution as the undefined value type. The ordinary-function inference rule that collapses wholly valueless results to void does not apply to this protocol component. Written generator annotations and their shorthand defaults are unchanged by this inference rule.

For structural yield* operands, derive separate yielded and terminal contributions from the selected iterator protocol. Non-completed records contribute to the enclosing yield boundary; completed records contribute to the delegation expression's result. A definitely completed record contributes no yielded value, and a definitely non-completed record contributes no terminal value. Preserve the value types of possible paths, leaving a contribution unknown where method selection, overloads, required properties or result types do not establish it. For a real async iterator, await the step result; the typed async yielded-value boundary separately assimilates an outgoing value as specified below. Async-from-sync adaptation assimilates both yielded and terminal values. A terminal value is checked when used at a typed destination, including a return, and is not itself a yield error when discarded. This does not extend the bounded proof of subsequent resume, throw or return forwarding, or execute a protocol method.

This clause is owed to the generator forms the core already parses, not to the extension that follows it. 1.10.8 admits a return annotation on a generator and says what it means in prose, that "a generator's annotation types the values the iterator yields", and the design writes Generator.<Y, R, N> throughout; the type itself has not been stated. It is stated here because 39 is the first clause that computes one.

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 nominal Type Record for the first with those arguments, and 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 1.2.3.1: a Generator.<uint8, void, void> is a subtype of no other instantiation.

A generator's return annotation is read as follows, which is the design's shorthand made precise. Where the annotation denotes a Generator instantiation, and an AsyncGenerator instantiation for an async generator, it is the declared type as written. Where it denotes any other type T, the declared type is Generator.<T, void, void>, or AsyncGenerator.<T, void, void>, so that function* f(): int32 declares a generator of int32. It is a type error for the annotation of an async generator to denote a Generator instantiation, or for a synchronous generator's to denote an AsyncGenerator instantiation.

The Static Type of a call of a generator function is its declared type. Within the body, the Static Type of a YieldExpression without * is N, and it is a type error if the operand's type is not assignable to Y. The Static Type of a yield* is GeneratorCompletionValue(R) for its operand's generator type, since delegation can receive the body return or ordinary exhaustion, and the operand's Y must be assignable to the enclosing Y.

A YieldExpression in a deciding position is judged by the truthiness of its established normal resumed result, as any other operand under 1.8.5.4. A plain yield reads N, and delegation reads the established terminal result of the selected protocol, not its yielded element type. A constant-answer value query remains permitted. Suspension does not preserve arbitrary pre-yield place relations; possible thrown or external return resumptions, catch handlers and finalizers remain separate completions. Unknown protocol or assimilation results supply no new verdict, and reference-liveness and scheduling requirements are unchanged.

When the running async generator has a resolved declared, contextual or published return contract that supplies a generator yield type, its yielded-value enforcement first applies Await to an Object value and then requires the resulting value to be of Y. An unresolved return type parameter supplies no such contract. This enforcement also applies to the outgoing value of 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 typed async yielded-value boundary, the bounded first-step proof follows the selected real async iterator, the awaited Object step result and a known false done to the effective value. It is a type error when every selected path fails a reached stage, including a yielded value whose callable then cannot accept the resolving callbacks under 1.6.2.23. A possible completed result, unknown done or value contract, viable overload or intrinsic Promise path prevents this new assimilation proof. Async-from-sync retains its separate value-assimilation rule. No getter or callback runs during checking; accepted programs retain their scheduling and reference-liveness obligations, and arbitrary later delegation requests are outside this bounded proof.

Note

A generator satisfies the corresponding Iterable or AsyncIterable element view. Its more precise structural iterator relationships follow its operation signatures, including exhaustion, under 34. The nominal R remains the body return obligation and is not widened in the generator's identity.

Reading a bare annotation as the yield type rather than as the whole generator type is the design's choice and is the useful one: a generator's return and next types are 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 GeneratorCompletionValue(R) as its type, while contributing its Y to the enclosing generator's, is the one part of the protocol a reader is likely to get backwards. It follows from the run time: yield* drives the operand to completion and evaluates to its return value.

34 GeneratorCompletionValue ( R )

The abstract operation GeneratorCompletionValue takes argument R (a Type Record) and returns a Type Record. A generator operation can observe ordinary exhaustion separately from its body's return. This operation computes its completed-value view without changing the body's obligation or the nominal generator arguments. It performs the following steps when called:

  1. Let completed be IteratorCompletionValue(R).
  2. Let absent be the Type Record of undefined.
  3. Return CanonicalizeType(the Type Record { [[Kind]]: union, [[Members]]: « completed, absent » }).

The read signatures of next, return and throw on Generator.<Y, R, N> each return the union of the result shape with done false and value of Y, and the shape with done true and value of GeneratorCompletionValue(R). The corresponding AsyncGenerator methods return a Promise of that union. All three methods are present; their optional synchronous inputs remain N, R and any respectively. Async next and throw retain those inputs; async return uses the raw input contract in 36. These signatures describe normal results and do not imply that a request fulfills or that 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 iterator conformance from these signatures, not an unconditional same-argument implementation claim. An optional R input already admits undefined; the generator's declared structural view may equivalently spell that input as optional _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 iterable element views remain available.

A nominal generator delegation uses GeneratorCompletionValue(R) as its terminal contribution, since the operand may already be exhausted. Structural delegation retains the terminal type of the selected structural result. Async-from-sync retains its existing assimilation stage. A source-local call alone does not establish a more precise lifecycle or immutable intrinsic identity. Open generic operands retain the deferred IteratorCompletionValue computation within the union until substitution and canonicalization resolve it.

This operation does not widen a written IteratorResult, admit undefined as a body return under a non-void R, itself change external request admission, or rewrite runtime result values. Synchronous request admission is specified separately in 35. Receiving checks, failed-state effects, queue order, cleanup, Promise scheduling and reference-liveness rules remain applicable. A completed result grants no reference permission across suspension.

35 Synchronous Generator Return Request Admission

A generator retains its resolved declared, contextual or published protocol at creation, including the captured generic context. For a synchronous intrinsic return request, first perform the ordinary receiver, brand and executing-state validation. Then admit its payload against the retained optional R: undefined, whether omitted or explicitly supplied, passes this input; a non-undefined payload crosses the ordinary RequireType boundary for R. An absent or gradual obligation has its existing behaviour. The void discard interpretation of a completed operation result does not widen this input. Erasing the receiver's static type, borrowing the intrinsic method, or reaching a suspended-start or completed generator does not remove the obligation.

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 return completion is associated internally with the generator that admitted it. Propagation of that completion, including through normal finally evaluation and suspension, preserves its external provenance. At that generator's final completion, such an unchanged external completion is not checked as a return executed by the body. The ordinary body-return obligation still applies to normal fallthrough and fresh body return completions, including replacements produced by finally. A second admitted external return has its own provenance. A failed request does not overwrite the provenance of an already suspended completion. Equal payload values do not establish provenance, and an association for one generator does not authorize another generator's body return.

For synchronous delegated yield receiving such an external completion, an absent delegate return method propagates the same completion. When the selected delegate return method produces a completed result, preserve the native lookup, call, Object validation, done read and value read order, then independently admit that replacement value against the enclosing generator's optional R. The resulting external completion belongs to the enclosing generator. Admission is performed even if its payload equals the incoming one; it is not treated as an executed body return. Abrupt lookup, call, result access or admission follows the body's ordinary abrupt path, including catch and finally.

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 early error, and gradual paths retain the runtime backstop.

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 synchronous request admission only; 36 retains asynchronous return assimilation, queue order and native rejection stages rather than replacing them with this synchronous pre-resumption sequence.

36 Async Generator Return Request Admission

An async generator retains its resolved declared, contextual or published protocol at creation, including its captured generic context. Its intrinsic return request admits a resolved payload against that protocol's optional R: omitted and explicit undefined pass; a non-undefined payload crosses the ordinary RequireType boundary for R. The void discard interpretation of completed operation results does not widen this input. An absent or gradual obligation retains its existing behaviour. Erasure, borrowing the intrinsic method or reaching completion does not remove an established obligation.

The optional raw input in the AsyncGenerator return member's read signature is the canonical union of direct R (the type of undefined for void), 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 any, the input remains gradual. This is a raw request contract, as for typed Promise resolvers, not a proof of fulfillment with R. Promises and nested thenables retain runtime admission of the eventual resolved value; an incompatible Promise fulfillment can reject the request. Known incompatible direct scalar and plain-object inputs retain ordinary static checking. Unknown objects are not all declared thenable. The read contract follows ordinary member lookup, extraction and structural conformance, without adding immutable intrinsic identity or receiver binding.

Preserve native receiver and brand validation, enqueue, state transitions and scheduling. Do not await or admit the raw operand before enqueue. In AsyncGeneratorUnwrapYieldResumption, after the native Await fulfills, admit its value against the captured optional R and produce an admitted external return completion for this generator. Await rejection retains its native throw resumption. Admission failure is also a throw resumption at the suspended yield, so the body's ordinary catch and finally processing applies; it may catch the error, yield, return a replacement or reject the current request.

In AsyncGeneratorAwaitReturn, preserve native PromiseResolve and rejection paths. In the fulfillment reaction, admit the resolved value before AsyncGeneratorCompleteStep. On success, complete the queue-head request with a normal completion containing the admitted payload. On admission failure, complete that request with the throw completion. In either case continue with AsyncGeneratorDrainQueue. An unstarted body is not entered, including on failure. The earlier transition to draining-queue is not rolled back. Admission effects can enqueue additional requests; the current request stays at the head until its completion, and subsequent requests retain their order. No conversion, getter or thenable effect is rolled back.

Associate each admitted external completion with its owning generator as in 35. Propagation through normal finally evaluation, Await and yield preserves its external provenance; the generator's final completion does not reclassify it as a body return. Fresh body returns, normal fallthrough and abrupt replacements retain their ordinary obligations. Equal payload values do not establish provenance. A failed second async request at a yielding finally follows the reached throw-resumption path and can replace the pending completion; it is not synchronous pre-resumption rejection.

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 Await, then admit its actual result against the enclosing optional R and produce an external completion for that generator. Otherwise call the selected return method, await its result object, validate Object, read done and then value in native order. For a done-true result, independently admit that replacement value against the enclosing optional R without adding an Await of the value property. A Promise-valued property is the payload at this boundary, not its eventual fulfillment. Async-from-sync adapters retain their own native assimilation. Abrupt lookup, call, await, result access and admission follow the body's ordinary abrupt path. Done-false results retain ordinary yielding and resumption; a later body return is a fresh obligation.

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 invalidated reference. General function and callback context transport is governed by its existing rules, not inferred from a mutable caller spelling.

37 Proven Generator Resume Forwarding

A bounded static proof may establish a later normal resume forwarded by synchronous 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 iterator and next methods must be established intrinsics, with no unknown call, getter, escape, reflective mutation or other intervening effect that could replace a dependency.

At that established forwarding boundary, it is a type error if the enclosing generator's declared input type is not assignable to the delegated generator's established input type. This compares one forwarded input contract, not the invariant identity of whole generator types. The initial request is separate from the later forwarded value. A known immediate completion, unknown completion or selected method, open input type or 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.

38 Typed Resource Management

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: null and undefined register no disposal, and a non-nullish resource requires a callable 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 any or a broad Object type. Otherwise, use the known Static Type of a participating initializer. This use does not infer or install a declared storage type on the resource binding. Resolve each initializer in its lexical environment and declaration order, including earlier bindings in the same declaration. Plain untyped initializers and unknown contracts retain runtime protocol discovery. This extension changes neither cleanup order nor suppressed-error and reference-liveness semantics.

It is a type error if the resource type used for this judgment proves that resource acquisition cannot succeed. A non-nullish primitive type or void cannot supply a disposal method. For an object or class type, a declared 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 type error.

It is also a type error when the effective declared synchronous disposal method is callable but every signature rejects the empty argument List supplied by disposal. This judgment uses the ordinary parameter boundary, including default and optional parameters, admitted undefined, reference markers, rest distribution and implicit conversions. A viable or unresolved signature prevents the proof. The normal result of synchronous disposal is ignored. This failure is determined before evaluation, even when the declaration occurs in an unused body.

This is a check of known impossibility, not a requirement that every annotation explicitly declare a disposal member. An open object type, any, an unknown member or callability, and a dynamically supplied method retain runtime protocol discovery. A union is refused by this judgment only when every alternative proves acquisition or the disposal call impossible; an admitted nullish alternative, a callable method not proved to reject the empty argument List, or an unknown alternative prevents that proof. A known callable member does not by itself justify eliding mutable method lookup, cleanup, or reference-liveness checks.

Note

For await using, a corresponding impossibility judgment must consider asynchronous acquisition, including Symbol.asyncDispose and the base language's synchronous-disposal fallback. Absence of an asynchronous member alone does not establish failure.

39 do Expressions

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 design's do expressions extension, which integrates the do expressions proposal under this type system and adds the generator forms of that proposal's issue 76.

PrimaryExpression[Yield, Await] : DoExpression[?Yield, ?Await] DoExpression[Yield, Await] : do Block[?Yield, ?Await, ~Return] do * { GeneratorBody } async [no LineTerminator here] do * { AsyncGeneratorBody }

The Block of the first form carries the [Return] permission of the function enclosing the do, as a MatchArmBody's does (26.1), so return, break, and continue inside it mean what they mean in the surrounding code; the Return above is notational and the permission is threaded by the enclosing contexts. The [no LineTerminator here] is what keeps async a contextual keyword: 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 DoExpression to appear where a Statement is admitted, since do there begins a do-while. In statement position a program writes a Block, or parenthesizes.

Note

do is a reserved word, so this form costs the grammar less than match did (26.1): there is no cover grammar and no program whose meaning changes, only a position where the word was previously a Syntax Error. The do * and async do * forms need no statement-position rule of their own, since * cannot begin a Statement and so cannot be 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.

39.1 Early Errors

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 design's own motivating example does end in a loop.

  • It is a Syntax Error if EndsInIterationOrDeclaration of the Block's StatementList is true. That operation is the upstream proposal's and is adopted unchanged: it holds when the completion value of the list can be the completion value of an iteration statement or of a declaration, which reaches through nesting, so a Block whose last statement is an if with an else one of whose branches ends in a loop is a Syntax Error too.
  • It is a Syntax Error if the Block's StatementList ends in an if statement with no else.
  • It is a Syntax Error for a VariableStatement to appear within a DoExpression that occurs in a parameter expression, since the function body its binding would hoist into does not yet exist.
  • It is a Syntax Error for an unlabelled break or continue to appear within a DoExpression in the head of an iteration statement.
  • It is a Syntax Error for a return to appear within a DoExpression in a computed property name in a class body, which is inside no function.
Note

Each of the first two is about a completion value a reader would not predict rather than about a shape being hard to evaluate. A declaration's completion is empty, so do { 'before'; let x = 'after'; } would be "before"; an if with no else is its consequent's value or undefined by a condition the reader has to trace; and a loop's is the last iteration's value, or undefined for none. The upstream proposal records that it intends to pursue changing the completion value of declarations in the base language, and this specification's rule follows whatever that settles: the restriction is stated in terms of EndsInIterationOrDeclaration rather than restated, so it moves with it.

39.2 CompletionTypeOf ( list )

The abstract operation CompletionTypeOf takes argument list (a StatementList Parse Node) and returns a Type Record. It returns the type of the completion value of list: the union of the types of the statements that can be the last one to complete normally.

A completion summary records each possible completion's kind, target and value type, distinguishing an empty value from the type 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 UpdateEmpty does. An expression statement supplies its expression's Static Type. A targeted break retains the carried value, and the matching labelled statement converts that break into a normal completion. A break or continue not consumed by a construct remains abrupt.

Blocks compose their reachable statements in order. Conditional branches are joined, applying the base language's replacement of empty values by undefined. A switch joins possible entry clauses and their reachable fall-through successors, consumes its unlabelled breaks, and includes the no-entry completion only when it is not exhaustive. A try statement includes its block and reachable handler outcomes; a normally completing finally preserves the incoming outcome, while an abrupt finally replaces it. Return and throw outcomes remain abrupt. Unsupported or budget-limited analysis yields an unknown fact, never proof that a path is absent. The separate restrictions on declaration and loop completions of a value-producing do still apply.

  1. Let outcomes be the completion summary of list.
  2. Let members be a new empty List.
  3. For each normal outcome outcome of outcomes, do
    1. If outcome's value type is unknown, return the Type Record for any.
    2. If outcome's value is empty, append the Type Record for undefined to members.
    3. Else, append outcome's value type to members.
  4. Return CanonicalizeType(the Type Record { [[Kind]]: union, [[Members]]: members }).
Note

The Static Type of a DoExpression of the first form is CompletionTypeOf(its Block's StatementList). For 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 (1.5.18) and is a subtype of every type, so 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 design's own. An exhaustive switch takes no path where no clause ran, so it contributes no undefined, which is the conclusion 1.2.7 already draws for a switch that maps a value to a type: "because enum and sealed-class exhaustiveness is checked, a switch used to map a value to a type covers every case its argument can take and therefore yields a Type Object rather than undefined". The same expression must not type one way inside a compile-time builder and another inside a do. A switch that is not exhaustive does contribute undefined, which is what makes const u: uint8 = do { switch (s) { case 'a': 1; } }; a type error.

Exhaustiveness here is the switch's, which this specification reserves to enums and sealed hierarchies (1.5.18 restates the reservation), and it is deliberately narrower than the atoms a match reads (26.3.1). A switch over a boolean covering true and false is therefore not exhaustive for this operation, and a do ending in one has undefined in its type, while the corresponding 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 undefined, the value void 0, and not void: void is the absence of a value and is a return type, while do {} produces one.

39.3 Contextual Typing

A contextual type on a DoExpression of the first form is the contextual type of every expression that can supply a normal completion value in CompletionTypeOf, including a value carried through a targeted break consumed within the expression. Unreachable lexical tails supply no value. A numeric literal at such a producing position takes the expected type by 1.6.6.

Read each producing expression in its own lexical scope and incoming flow environment, with that contextual type, before joining the result types. Retain that value type when UpdateEmpty carries the value across an empty completion or a matching label consumes a break. A normally completing finally does not replace the captured value or reinterpret it in the finally block's environment; an abrupt finally replaces the incoming outcome and its value producer. Read switch coverage from the discriminant's environment at that switch, not from the environment after its clauses or after leaving their scopes. A type query exports no additional execution effects; the ordinary flow transfer accounts for the expression's effects and escaping abrupt completions.

Example (Informative)
let n: uint8 = do { done: { 1; break done; "unreachable"; } };
let m: uint8 = do {
  done: { try { "replaced"; break done; } finally { 3; break done; } }
};
Note

Without this the form would be typed and not usable: every literal inside a 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 1.8.5, and no fact survives the expression, since which path ran is not visible outside it; what leaves is the type, and the union over the tails is what carries the narrowing outward.

39.4 do * Generators

A do * evaluates to a generator object, as a call of a generator function does, and its body is a generator body. The Static Type of do * { GeneratorBody } is Generator.<Y, R, N> (33), and of the async form AsyncGenerator.<Y, R, N>, where the three are found rather than declared, there being no annotation site:

  • Y is CanonicalizeType of the union of the type of each YieldExpression operand in the body that is not a yield*, and of the Y of each yield* operand's generator type.
  • R is CanonicalizeType of the union of the types of the body's return operands, or void where it has none.
  • N is void unless a contextual type supplies it, in which case it is that type's N. A contextual type also supplies Y and R, and it is a type error if what the body yields or returns is not assignable to them.

A contextual type on a do * is the contextual type of each YieldExpression operand, so a literal yielded takes the expected type as one in a plain do's tail does.

Computing Y and R from the body makes distinct yielded-result and completion-result queries under 1.6.2.8 and 1.6.2.9, with this DoExpression as their common source owner. Combining their computation in one traversal does not combine their accounting: each recomputed component spends an evaluation unit and remains active until its query returns. Record each completed component answer before constructing or publishing the generator carrier; an unanswered prerequisite records no completed answer. A contextual type can supply the components without those inference queries, but does not waive operand checks or inference required by a nested expression. These requirements apply to both the synchronous and asynchronous forms.

Example (Informative)

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 design's motivating example exists to delete a 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.

Note

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 proposal's own 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 typed form is what the IIFE it replaces cannot easily give. Promise.all takes an iterable of promises, so Y is checked against what it awaits and a 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.

39.5 Modifications to Existing Clauses

A match arm's Block (26.1) is a DoExpression's Block of the first form: its value is CompletionTypeOf(its StatementList) and it carries the Early Errors of 39.1. This replaces the rule that clause stated, under which an arm's value was its final statement's only where that statement was an ExpressionStatement and the arm was void otherwise, and it correspondingly replaces the sentence in 26.3 that gave such an arm void.

Note 1

No program changes meaning. The arm rule accepted a strict subset of this one - a block ending in an ExpressionStatement has the same value under both - and its own note reserved the difference, recording that it "errors exactly where full completion-value semantics would permit" so that a later general form could only make errors legal. Two things improve. An arm may now end in an if with an else, a try, or a switch, which the arm rule made void and therefore unreadable at the use site. And an arm ending in a declaration or a loop is now a Syntax Error naming what it ended with, where it was a silent void and a complaint at whatever read the 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 Table 47 gains 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 replacement: a 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 replacement was never about their being structural positions but about their having no value to replace, and these two have one.

Note 2

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 replacement makes memoization, caching, tracing that substitutes, and stubbing expressible at an expression as small as one wants. The generator context is the more useful of the two, since what a do * decorator receives is an iterator and wrapping one is what a decorator over a sequence is for.

40 constant Expressions

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 realm, and every later evaluation reaching it answers with the value already produced.

PrimaryExpression[Yield, Await] : ConstantExpression[?Yield, ?Await] ConstantExpression[Yield, Await] : constant [no LineTerminator here] Block[?Yield, ?Await, ~Return]

constant is a contextual keyword rather than a reserved word, so an existing program that uses it as an identifier keeps its meaning. The [no LineTerminator here] restriction is what keeps the two apart: without it, constant alone on a line followed by a block would be an ExpressionStatement and a Block under automatic semicolon insertion.

Note

This is the rule ECMA-262's GetTemplateObject applies to a tagged template's strings array - it looks the site up in the realm's [[TemplateMap]] and returns the array already built for it - generalized from a frozen List of Strings to the value of a Block. Both answer the same question - what belongs to a position in the source rather than to an evaluation of it - and both answer it by keying on the Parse Node.

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 Realm Record has an additional field for this purpose.

Table 63: Additional Field of Realm Records
Field NameValueMeaning
[[ConstantMap]] a List of Records with fields [[Site]] (a Parse Node) and [[Value]] (an ECMAScript language value) The value each ConstantExpression evaluated in this realm produced. It is the counterpart of [[TemplateMap]], which records a tagged template's strings array against its site.

40.1 Static Semantics: Early Errors

ConstantExpression : constant Block
  • It is a Syntax Error if the Block contains an IdentifierReference that does not resolve to a binding declared within the Block.
Note

The Block must be CLOSED: it reads nothing from outside itself. Its value is then a property of the site and of nothing else, which is what makes "evaluated once" unobservable except through identity.

Without this rule, function f(x) { return constant { x + 1 }; } would answer every call with the first call's argument, and nothing would signal it. The rule is stated over free references rather than over non-determinism because the two differ in both directions: a parameter is not ambient state, and reading Math.max is ambient but cannot observe how many times it ran. Being closed subsumes the non-determinism a replacement decorator is held to (1.2.7), since an ambient name is a free reference too.

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.

40.2 Runtime Semantics: Evaluation

ConstantExpression : constant Block
  1. Let realm be the current Realm Record.
  2. For each Record { [[Site]], [[Value]] } entry of realm.[[ConstantMap]], do
    1. If entry.[[Site]] is the same Parse Node as this ConstantExpression, return entry.[[Value]].
  3. Let completion be Completion(Evaluation of Block).
  4. If completion is an abrupt completion, return ? completion.
  5. If completion.[[Value]] is empty, let value be undefined; otherwise let value be completion.[[Value]].
  6. Append the Record { [[Site]]: this ConstantExpression, [[Value]]: value } to realm.[[ConstantMap]].
  7. Return value.
Note

Evaluation is LAZY: the Block runs when an evaluation first reaches it, not when the module containing it is evaluated. A branch never taken therefore costs nothing. Because the Block is closed, the choice between lazy and eager is unobservable except in whether it runs at all.

An abrupt completion is NOT recorded. Nothing was produced, and a stored failure could not be told from a stored value, so a later evaluation reaching the same site evaluates the Block again.

The value belongs to a realm as well as to a site, so a module evaluated in two realms builds two, and neither is reachable from the other.

41 Pipeline Operator

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 design's pipeline operator extension, which integrates the pipeline operator proposal under this type system.

PipelineExpression[In, Yield, Await] : ShortCircuitExpression[?In, ?Yield, ?Await] PipelineExpression[?In, ?Yield, ?Await] |> ShortCircuitExpression[?In, ?Yield, ?Await] ConditionalExpression[In, Yield, Await] : ShortCircuitExpression[?In, ?Yield, ?Await] ShortCircuitExpression[?In, ?Yield, ?Await] ? AssignmentExpression[+In, ?Yield, ?Await] : AssignmentExpression[?In, ?Yield, ?Await] PipelineExpression[?In, ?Yield, ?Await] PipelineExpression[?In, ?Yield, ?Await] ? AssignmentExpression[+In, ?Yield, ?Await] : AssignmentExpression[?In, ?Yield, ?Await] PrimaryExpression[Yield, Await] : IdentifierReference[?Yield, ?Await] TopicReference TopicReference : %

|> is left-associative, binds looser than ?? and tighter than the conditional operator. The upstream proposal names ShortCircuitExpression as its operand, which assumes the base language's precedence table; this design has placed ranges at that level (21), so a pipeline's operands are ranges and |> 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 proposal's: 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 TopicReference stands where an operand is expected and the remainder operator between two — which no operator declaration can change, since 1.9 lets a type supply a meaning for the binary form and never a new arity.

Note

The upstream proposal records its grammar as incomplete (issue 290) and its topic token as unsettled after the longest bikeshed the committee has run (issue 91). This clause takes %, which is the proposal's current spelling; every rule below is stated in terms of the topic rather than the token, so a different token is an edit to the grammar and to nothing else.

An expression language with operator overloading has fewer free tokens than one without, which is a fact about this design that the committee's discussion has not had before it. # is a private name, ? is optional chaining and the optional marker of 1.4.4, @ is a decorator in about twenty positions, _ is the pattern wildcard (26.2), and % and ^ are operators a class or a primitive block may declare. The design's own token would be _ 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.

41.1 Early Errors

  • It is a Syntax Error if a TopicReference is not contained within the right operand of a PipelineExpression. Outside one, % is the remainder punctuator and a % where an operand is expected is the ordinary Syntax Error it is today.
  • It is a Syntax Error if the right operand of a PipelineExpression contains no TopicReference that the operand binds. A step that does not mention the topic discards the value piped into it, which is not a pipeline and is more likely a mistaken tacit call than an intention.
  • It is a Syntax Error if a TopicReference is the target of an assignment, an update expression, or a destructuring pattern. The topic is an immutable binding: a step that reassigns what is being piped leaves the next step an input it cannot predict.
Note

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. 41.3 records what a typed language could do about a tacit form and why this specification does not.

"That the operand binds" excludes a topic belonging to a nested pipeline: in a |> f(b |> g(%)) the only TopicReference is the inner pipeline's, so the outer operand contains none of its own and is a Syntax Error. It is written a |> f(%, b |> g(%)) or not at all.

41.2 Runtime Semantics

Evaluation of PipelineExpression : PipelineExpression |> ShortCircuitExpression:

  1. Let ref be ? Evaluation of PipelineExpression.
  2. Let value be ? GetValue(ref).
  3. Let oldEnv be the running execution context's LexicalEnvironment.
  4. Let topicEnv be NewDeclarativeEnvironment(oldEnv).
  5. Perform ! topicEnv.CreateImmutableBinding(the topic, true).
  6. Perform ! topicEnv.InitializeBinding(the topic, value).
  7. Set the running execution context's LexicalEnvironment to topicEnv.
  8. Let result be Completion(Evaluation of ShortCircuitExpression).
  9. Set the running execution context's LexicalEnvironment to oldEnv.
  10. Return ? result.

Evaluation of TopicReference : % returns the value bound in the nearest enclosing topic environment.

Note

The left operand is evaluated once however many times the topic appears, which is the difference between a binding and a substitution and is what expensive() |> [%, %] rests on. An immutable binding in its own environment also gives the shadowing rule for free: a nested pipeline creates a second topic environment, so its steps see its own value and the outer one is unreachable from within it.

41.3 Static Semantics

The Static Type of the topic is the Static Type of the PipelineExpression that binds it, and the Static Type of a PipelineExpression is the Static Type of its right operand evaluated with that binding. The topic is a binding for every purpose of this specification: it is narrowed by the tests that govern it, it is shadowed by an inner pipeline's, and it is immutable, which the early errors above enforce syntactically rather than through the assignability rules.

A tested pipeline propagates its body's result environments and, where proved, the topic's captured input-place identity by 1.8.5.1. The input is still evaluated once. A write to that place or a prefix, or another invalidating effect, prevents publishing a fact about its new value from the old topic.

A contextual type (1.6.5) on a PipelineExpression is the contextual type of its right operand, so a numeric literal in a step takes the expected type by 1.6.6 rather than defaulting. Without this the form would be typed and unusable, which is the failure 39 had to avoid in the same way.

It is a type error for a TopicReference to be the operand of ref (2). A reference parameter binds the caller's location; the topic holds the value the left operand produced, so 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 topic being an ordinary binding and are stated because a pipeline reads like a substitution. Overload resolution (1.10.3.1) selects on the topic's type as on any argument. Generic parameters are inferred through it, and may be supplied explicitly as x |> f.<uint8>(%). And the topic is admitted wherever an expression is, including a spread and the argument positions a rest parameter distributes (1.10.4.4).

A PipelineExpression whose right operand diverges has the type of the empty union (27, 1.5.18). A PipelineExpression is compile-time evaluable exactly when both operands are: it introduces a binding and substitutes, reading nothing the surrounding code could not, so it is admitted in a type builder on the same terms as any other expression.

Note

The tacit form the proposal calls F# style — x |> f, calling f with the value and no topic — is refused by the second early error, and the reason is worth recording precisely, because it is not the reason usually given. That reason is that a language cannot see what a function expects, so a two-parameter function piped one argument fails somewhere else. It does not hold here: a function has a Signature Record with Parameter Records (1.10.1), so a tacit call could be checked, the topic tested against the first parameter and an under-supplied signature rejected at the pipe.

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 early error is what keeps them from being confused.

41.4 Modifications to Existing Clauses

Table 24 gains a row: a test governing a TopicReference narrows it in the positions the test governs, by the same rows the table gives 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 topic is a binding.

1.9 gains a sentence: |> 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.

42 Types Across Agents

The threading extension gives a program more than one executing thread over one heap. There is no copying step between them: a value is shared by being referenced, and no structured clone, no message, and no byte buffer stands in between. The threads of such a program are agents of one agent cluster that share a heap rather than only a block of memory, and the intern table of 1.2.2.9 is a property of that heap, as the Composite Registry is (25.3). They therefore share both. A type constructed on one thread is the same Type Object on every other, === answers on any thread what it answers on the thread that built it, and nothing about interning, canonicalization, or the total order is per-thread. This is what that clause's note defers to, and the answer is that the extension does not carry a Type Object across a boundary; it removes the boundary.

An agent that does not share a heap is the other case, and a Type Object does not reach it. A value that crosses to one crosses as data, and the receiving agent types that data by its own declarations and interns the result in its own table. Two structurally identical declarations in two such agents are two Type Objects and are not ===, 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 agent holds. A symbol literal type (1.5.19) over a registered Symbol has a counterpart in another agent, because the registry is shared and the key is what identifies it; over an unregistered Symbol it has none, and the type does not travel, because the Symbol does not. The canonical order of symbol values is by first interning within an agent (1.8.5.2), which is a per-heap fact for the same reason the table is, and two agents may order the same two symbols differently without either being wrong.

A value type is copied by assignment (1.11.2), and that copy is not atomic. A value wider than a single primitive, or an update spanning several fields, may be observed torn or half-applied by a thread that races it. Atomicity is obtained only through Atomics operations, whose compareExchange compares with SameValueZero so that a loop cycling through NaN terminates; there is no rule making some assignments atomic and others not, because leaving that set undefined would make a racing program unspecifiable and would tax every shared write. The shared modifier marks the state a program expects to cross threads, which is what lets the boundary be reasoned about statically rather than discovered.

Note

The proposal makes no claim to prove races away. A race here is a stale value and never a corrupted heap, which is the position Java and Go take rather than Rust's; what the types offer in place of a proof is the discipline that 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 value type rules rather than only here, since it is the one place the core's "a value type copies" meets a second thread. It is also why the layout extension's guarantees are the ones a concurrent program leans on: a shared SoA.<T> allocates each field its own column, so two threads writing two fields of one element never contend for a cache line, which no interleaved layout can promise.

43 Threading

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 thread on that heap: how a thread is created and when it ends, where a promise reaction runs when the thread that settles a promise is not the thread that attached to it, what the shared modifier claims about storage and about what a checker may assume of it, the Atomics operations over typed values, the Lock, Condition, and ThreadLocal objects, and parallel iteration over a range.

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.

Note

The clauses that follow are ordered as they depend on each other. 43.1 and 43.2 establish what a thread is and where a job runs, which 43.3 then uses. 43.4 gives shared its meaning in the type system and its placement, which 43.5 turns into the events the memory model of the pinned edition already reasons over, and only then can 43.6 say what an atomic operation on a typed value is. 43.7 and 43.8 are built from those. Cancellation is not a clause of its own: it is an AbortSignal observed at points the operations that can observe it each name.

43.1 Threads and the Agent Cluster

A thread is an agent. The threads of one program are the agents of one agent cluster, and what distinguishes that cluster from the one an ECMAScript program has today is that its agents share a heap rather than only a block of memory: one realm, one global object, one already-executed module graph, one intern table, one Composite Registry (42). Nothing is copied between them, so no rule of this specification describes a value crossing from one to another. There is nowhere to cross to.

Each thread holds what an agent already holds, per-thread: an execution context stack, the microtask queue its jobs are drained from, and its Agent Record fields, of which [[CanBlock]] is the one this extension consults (43.7). Each thread drains its own queue, and a job of one thread's queue never runs on another. The main thread is the agent that evaluated the program; it is a thread like the others in everything this clause says, and hosts commonly give it [[CanBlock]] false.

A thread runs to completion and then ends. CreateThread (43.3.3) gives the steps; the shape is that the function is called, a thenable result is adopted, the thread's own microtask queue is drained, the settlement is posted, and the agent is removed from the cluster. The drain comes before the post, so everything the thread did, its trailing microtasks included, happens-before the thread that awaits the result observes it.

Whether a thread may receive host-defined work — a timer, an I/O completion, a host job of any kind — and what becomes of such work still pending when the thread's function has settled, is host-defined. A host that keeps a thread alive to run it does not thereby change what the thread's handle means: the handle settles when the function does, which is the rule the thread's caller was given.

Note

A thread costs a native stack and the per-thread allocator state an agent needs, and not another evaluation of the program: the module graph is already executed and is shared, so nothing about starting a thread re-runs it. That is what makes a thread per request or per connection a reasonable structure rather than one to be pooled around, and it is the concrete difference from an agent that does not share a heap, which must be given its own copy of everything before it can run a line.

43.2 Job Scheduling Across Threads

A promise is an ordinary object of the shared heap, so any thread that holds one may settle it and any thread may attach to it. Where the reaction runs is a separate question, and this clause answers it: a promise reaction runs on the thread that created it.

A PromiseReaction Record gains an [[Agent]] field, set to the surrounding agent when the record is created. PerformPromiseThen creates the reactions it appends to a pending promise's lists, and the Await machinery creates the reaction that resumes a suspended execution context, so in both cases [[Agent]] is the agent that attached. NewPromiseReactionJob returns its job paired with [[Agent]] rather than with the settling agent, and HostEnqueuePromiseJob enqueues a job on the microtask queue of the agent it is paired with. Attaching a handler to a promise that has already settled enqueues on the attaching agent, which is the agent running PerformPromiseThen at that moment, so that path needs no rule of its own.

Settling a promise from another thread therefore posts to the reaction's home queue. The post carries a happens-before edge: every operation the settling thread performed before the settle happens-before every step of the reaction. This is the ordinary release-acquire pairing the memory model already gives a synchronizing operation (43.5), and it is what makes a value written before resolve visible to the code that awaits it.

The handle returned by callThread is not an exception to this rule. It settles on the thread that created it, which is the thread that called callThread, because that is where its reactions were attached — the same reason every other promise behaves the way it does.

Note

The alternative rule — run a reaction on whichever thread settled the promise, saving the post — does not survive 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 thread that released the lock, stacking every waiter's critical section onto the releasing thread; a main-thread function awaiting anything another thread can settle would silently continue off the main thread, which is fatal to a thread-affine host API and invisible at 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 thread and the same handler attached after it on another.

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 thread runs the body is one a programmer cannot hold in mind. The cost of the rule chosen here is a cross-thread enqueue, and settle-side execution is never needed, since the settling thread may always do the work before it settles. Recovering it where a program wants it is a matter for an explicit opt-in, not for the default.

43.2.1 Weak References Across Threads

A target is live while it is reachable from any thread of the cluster. Reachability is a property of the heap, and the heap is one, so no thread's stack is privileged in deciding it and a target held only by a thread other than the registering one is not collectable.

A cleanup callback registered with a FinalizationRegistry is called on the thread that registered it, whichever thread's collection observed the target become unreachable. This is the rule of 43.2 applied to a callback rather than to a reaction: the work runs where it was attached. What may be held weakly at all is unchanged — an instance of a typed class may not be (1.12.9).

43.3 Function.prototype.callThread

callThread calls a function on a new thread and returns a promise for its result.

43.3.1 Function.prototype.callThread ( ...args )

This method performs the following steps when called:

  1. Let func be the this value.
  2. If IsCallable(func) is false, throw a TypeError exception.
  3. Let options and callArgs be the result of ClassifyThreadArguments(func, args).
  4. Let signal be undefined.
  5. If options is not empty, then
    1. Set signal to ? Get(options, "signal").
    2. If signal is not undefined and signal is not an AbortSignal, throw a TypeError exception.
  6. Return CreateThread(func, callArgs, signal).

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.

43.3.2 ClassifyThreadArguments ( func, args )

The abstract operation ClassifyThreadArguments takes arguments func (a function object) and args (a List of ECMAScript language values) and returns a pair of an Object or empty and a List of ECMAScript language values. It decides whether the first element of args is the options bag or the first forwarded argument. It performs the following steps when called:

  1. If args is empty, return the pair (empty, args).
  2. Let first be args[0].
  3. If first is not an ordinary Object, return the pair (empty, args).
  4. For each signature sig of the declared type of func, do
    1. Let p be the type of the first parameter of sig, or unresolvable if sig has no parameters or that type is not resolvable at this point.
    2. If p is not unresolvable and ! IsOfType(first, p) is true, return the pair (empty, args).
  5. If first has no own property keys, return the pair (first, the List of the elements of args from index 1).
  6. If first has an own property "signal" whose value is an AbortSignal, return the pair (first, the List of the elements of args from index 1).
  7. Return the pair (empty, args).

The bag is recognized by the brand of the value it carries and not by the shape of the object carrying it, because a brand is the stronger evidence: an ordinary object whose 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 IsOfType is what it can ask, and asking it means an object is admitted by a parameter type it actually satisfies rather than by one it could be converted to. Any signature suffices, not only a sole one: an overloaded callee that can receive the object in at least one of its shapes is a callee the program meant to hand it to, and choosing among the shapes is the ordinary work of overload resolution at the call rather than of this operation.

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.

Note

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 property keys, while callThread reads the option with Get, which walks the prototype chain. The asymmetry is deliberate and is what makes 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.

43.3.3 CreateThread ( func, args, signal )

The abstract operation CreateThread takes arguments func (a function object), args (a List of ECMAScript language values), and signal (an AbortSignal or undefined) and returns a Promise. It creates an agent of the surrounding agent's cluster, calls func on it, and returns a promise for the result. It performs the following steps when called:

  1. Let capability be ! NewPromiseCapability(%Promise%).
  2. If signal is not undefined and signal is aborted, then
    1. Perform ! Call(capability.[[Reject]], undefined, « the abort reason of signal »).
    2. Return capability.[[Promise]].
  3. Let thread be a new agent of the surrounding agent's agent cluster, sharing its heap and its realm. The [[CanBlock]] field of thread is host-defined.
  4. Let finish be a new Abstract Closure with parameters (completion) that captures capability, thread, and the surrounding agent and performs the following steps when called:
    1. If completion is a normal completion, perform ! Call(capability.[[Resolve]], undefined, « completion.[[Value]] »).
    2. Else, perform ! Call(capability.[[Reject]], undefined, « completion.[[Value]] »).
    3. Remove thread from the agent cluster.
  5. Enqueue on the microtask queue of thread a job that performs the following steps:
    1. Let result be Completion(Call(func, undefined, args)).
    2. If result is a normal completion and result.[[Value]] is an Object with a callable "then" property, then
      1. Let inner be ! PromiseResolve(%Promise%, result.[[Value]]).
      2. Perform PerformPromiseThen(inner, a function that calls finish with a normal completion of its argument, a function that calls finish with a throw completion of its argument).
      3. Return unused.
    3. Perform finish(result).
  6. Return capability.[[Promise]].

The capability is created on the calling thread, so its reactions are that thread's reactions and the handle settles there (43.2).

Adopting a thenable is a suspension of the thread and not a step it runs through. An async function returns a pending promise the moment it reaches its first await, so a thread that settled its handle with the returned value and ended would end while its function was still suspended: the body past the 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 thread ends when the completion settles" means.

There is no separate step draining the thread's microtask queue. The settlement is delivered by a job on the spawning agent's queue, enqueued when finish runs, and by then every job the thread queued before it has already run — the drain is a consequence of the queue rather than an operation performed on it. What the clause promises, that everything the thread did happens-before the spawner observes its result, holds for that reason.

43.3.4 Cancellation

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 thread.

Otherwise the thread observes the abort at a cancellation checkpoint, and the checkpoints are the points at which it is between things or waiting for one: when it takes a job from its microtask queue, at the resumption of an await, and in a wait — Atomics.wait and Atomics.waitAsync (43.6), and the Lock and Condition waits (43.7). A wait that is already parked when the abort happens is woken by it. A wait that could only be observed on entry would leave a parked thread uncancellable, which is most of what cancellation is for.

Observing it throws the signal's abort reason from whatever operation was pending. That is an ordinary abrupt completion and unwinds ordinarily: finally blocks run, using declarations dispose, a lock held through hold is released, and a reason that reaches the top of the thread's function rejects the handle.

Those two sentences have an order, and it decides what a host may throw away. A thread parked on a wait is not idle: the abort is delivered through the wait, which completes abruptly with the reason. A thread that has been delivered an abort is not idle either: it is unwinding, and its remaining jobs are the finally blocks and catch clauses of that unwinding. So an abort abandons a thread's queued work only where the thread has nothing pending to deliver it through and has not already been delivered it — a thread that has not yet begun, or one between jobs. Abandoning the work of a thread that is unwinding would discard exactly the steps the paragraph above promises will run.

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 thread and the read that observes it on another are ordered by 43.5.

Note

Cancellation interrupts; it does not destroy. A thread torn down where it stands would leave broken exactly the invariants a lock was protecting, which is why Thread.stop and Thread.Abort were both removed from the languages that had them, and why the surviving design everywhere is an exception thrown at a point the thread can be caught at.

43.4 The shared Modifier

shared before a type produces the Type Record { [[Kind]]: shared, [[Target]]: the Type Record of its operand }. The grammar that admits it is PrimaryType : shared PrimaryType, given with the array and tuple types (1.4.2.7).

It is a type error if the operand is not a value type (1.5.1): the sized numerics, the vector types, a value type class, a fixed-length array of a value type, and the SoA.<T> of the structure-of-arrays extension are what the modifier admits. It is a type error if the operand is itself shared, and a type error if it is reference, since ref denotes a location rather than a value and a location is already reached from wherever the thread that holds it can reach.

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 type error even in an unused signature. An unresolved generic operand or class layout retains the existing deferral; formation does not imply atomicity or reference liveness.

An object needs no modifier and is not given one. There is one heap, so a thread that can reach a reference reaches the object it denotes; 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 design offers is the ordinary collection under a Lock. A composite (25) is refused for a stronger reason: it is frozen, so the question the modifier answers does not arise for it.

A shared type is invariant in its [[Target]], as a reference is, and for the same reason: the storage it describes is read and written, so a mismatch in either direction is unsound. Between a shared type and a type that is not one, the modifier is instead transparent, because it is not observable in the value. IsSubtype (1.2.3.1) reads, before its comparison of kinds: if s.[[Kind]] is shared and t.[[Kind]] is not shared, return IsSubtype(s.[[Target]], t, assumptions); if t.[[Kind]] is shared and s.[[Kind]] is not shared, return IsSubtype(s, t.[[Target]], assumptions). A value of type T is therefore assignable to storage of type shared T, which is how a value is published, and a read of that storage yields a value of T.

The unwrapping is one-sided on purpose. Two shared types meet under the invariant rule, and SameType holds of 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 (1.11.1.3), so { x: shared uint32 } is not viewable as { x: uint32 }, and the regime a slot is under (43.4.2) cannot be laundered by aliasing the slot through an object type that drops the marker.

RequireType (1.7.7.1) enforces a shared type by enforcing its [[Target]]: publishing a value into shared storage applies the same check and the same conversion as storing it in unmarked storage of that type. The conversion is the part that matters. A boundary that tested membership without it would refuse 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.

43.4.1 Placement

Storage of a shared type is backed by a Shared Data Block, laid out at the offsets Table 58 gives its [[Target]]. The modifier decides placement and nothing about layout: shared T and T have the same size, alignment, and field offsets, so a value moves between marked and unmarked storage by copying and no representation changes.

A shared SoA.<T> allocates each of its columns its own region. Two threads writing two different fields of one element therefore write two different regions, and no false sharing arises between them, which an interleaved layout cannot promise at any alignment.

Note

Unmarked storage of a value type is not thereby unreachable from another thread, and this clause does not make it so. A binding a function closes over is reached by whatever thread runs that function. What the modifier buys is that the storage is shared-capable from the start: an implementation that discovers a second thread touching unmarked storage may place it in a Shared Data Block at that point, at a cost the marked case does not pay. That is an implementation's affair, and no operation of this specification consults the modifier to decide whether it is permitted (43.6).

43.4.2 Stability and Narrowing

Narrowing a binding or a field (1.8.5) records at one program point a type narrower than the declared one and carries it to a later point. Carrying it assumes the storage did not change in between. The two regimes of this clause are the two answers to whether that assumption may be made.

For storage that is not shared, it may. A narrowing established before an operation that no thread of this agent can have changed the storage through survives that operation, and a check the narrowing makes redundant is elidable exactly as 1.7.9 allows in a program with one thread. If another thread writes the storage anyway, the narrowing may be stale, and a stale narrowing is a branch taken on old information: the value read is still of the storage's declared type, because a write from any thread passes the same boundary check every write passes (1.7.10), so what the program loses is the refinement and never the declared type.

For storage that is shared, it may not. The slot carries no narrowing across any operation: each read of it yields its declared type, and a program that wants a narrowing reads once into a local binding, which is unmarked storage, and narrows that. A program with no reachable second thread is unaffected in either regime, since a narrowing is only lost where another thread could have written.

Note

Declared types hold in both regimes, so every elision 1.7.9 justifies at a declared type is justified here unchanged, and that clause needs no threading caveat. What differs is only how long a narrowing lives, which is a claim about refinement and not about type.

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.

43.5 Shared Typed Memory

This clause adds no memory model. The one the pinned edition defines already says what a racing program may observe, in terms of a candidate execution over a set of events and the agent-order, reads-from, and synchronizes-with relations among them. What was missing was only that its events were reachable exclusively through a TypedArray over a SharedArrayBuffer. Typed storage has byte addresses too (9), so this clause names the events that typed accesses are, and everything that clause proves then applies to them unchanged.

Let a shared typed slot be storage of a shared type, which 43.4.1 places in a Shared Data Block at a known offset for a known number of bytes. Then:

  • A read of a shared typed slot is a ReadSharedMemory event whose [[Order]] is unordered, whose [[Block]] is that Shared Data Block, whose [[ByteIndex]] is the slot's offset, and whose [[ElementSize]] is the laid-out size of the slot's type.
  • A write of a shared typed slot is a WriteSharedMemory event with the same fields and [[Order]] unordered.
  • An Atomics operation on a shared typed slot (43.6) is the corresponding event with [[Order]] seq-cst, a read-modify-write being a ReadModifyWriteSharedMemory event as it is over a TypedArray.

A slot whose type is one of the sized numerics or boolean has [[NoTear]] true: a read of it reads the bytes of a single write and never a mixture of two. A slot whose type is an aggregate — a value type class, a fixed-length array, a vector — has no such guarantee, and a read of it concurrent with a write may compose bytes of both. This is what 42 states in prose and this clause states as events.

Note

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 thread through the typed-storage boundary (1.7.10), so every field of the result holds a value of that field's declared type. What the program loses is the relation between the fields — a point whose 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 (43.7) or builds the value, freezes it, and publishes it by a single reference store, which is the composite idiom (25) and is tear-free because a reference is one slot.

Accesses to storage that is not shared are not events of this model, and a program with no shared storage and no Atomics has the memory model of the pinned edition unchanged. Where such storage is nonetheless reached by two threads, an implementation that has placed it in a Shared Data Block gives its accesses the same event vocabulary; where it has not, the accesses are not shared-memory accesses and the checker's assumption of thread stability (43.4.2) is what governs. Either way the outcome is bounded the same way: a stale value of the declared type, never an unsafe one.

Two threads are ordered by the relations of the memory model and by nothing this extension adds. Settling a promise whose reaction belongs to another thread synchronizes-with the running of that reaction (43.2), and the release of a Lock synchronizes-with the acquisition that follows it (43.7); those are the edges an ordinary program is built from, and each is an ordinary seq-cst pairing rather than a new kind of ordering.

43.6 Atomics on Typed Values

The Atomics operations of the pinned edition take a TypedArray and an index. This clause admits two further targets: a typed binding reached through a reference, and a typed own data property of an object. A program that has a uint32 may operate on it atomically without first arranging for it to live inside a byte buffer.

43.6.1 Reference Arguments

An Atomics operation whose first argument position is written ref binding receives a Reference Record rather than the value of the binding, as a call to any function declaring a ref parameter does (2).

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) decays at %Function.prototype.call%, which declares no such position, so what it forwards carries a value.

The borrow is validated where the ref argument is evaluated, by RequireBorrowableReference (2.4.1), which is how every 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.

43.6.2 ValidateAtomicTarget ( args, operation )

The abstract operation ValidateAtomicTarget takes arguments args (a List of ECMAScript language values) and operation (integer-only or any-value-type) and returns either a normal completion containing an Atomic Target Record or a throw completion. It resolves the shapes an Atomics operation admits to one description of a place in memory.

An Atomic Target Record has fields [[Block]] (a Data Block or Shared Data Block), [[ByteIndex]] (a non-negative integer), and [[Type]] (a Type Record).

  1. If args[0] is a Reference Record, then
    1. Let ref be ? RequireBorrowableReference(args[0]).
    2. Let type be the declared type of the storage ref denotes.
    3. Let block and byteIndex be the Data Block and offset at which that storage is placed.
  2. Else if args[0] is an Object that is not a TypedArray, then
    1. Let key be ? ToPropertyKey(args[1]).
    2. If args[0] does not have a typed own data property whose key is key, throw a TypeError exception.
    3. Let type be the declared type of that property.
    4. Let block and byteIndex be the Data Block and offset at which that property is placed.
  3. Else,
    1. Return ? ValidateIntegerTypedArray(args[0], args[1]) as an Atomic Target Record whose [[Type]] is the element type of the TypedArray.
  4. If type is not a value type, throw a TypeError exception.
  5. If operation is integer-only and type is not an integer type, throw a TypeError exception.
  6. If the laid-out size of type (9) is not one the implementation performs atomically, throw a TypeError exception.
  7. Return the Atomic Target Record { [[Block]]: block, [[ByteIndex]]: byteIndex, [[Type]]: type }.

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 integer type where the operation needs bit patterns, a value type of a size the implementation can operate on atomically, and a typed own data property rather than an ordinary one, an any-typed slot having no width for an operation to be atomic over.

Note

Requiring the modifier would buy no invariant. Unmarked storage is not unreachable from another thread — a binding a function closes over is reached by whatever thread runs that function — so plain racing writes to it are possible whether or not the synchronized ones are permitted, and forbidding only the synchronized ones would leave a program the worse tool for the same storage. It would also fracture generic code: a function taking 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 (43.4.1).

43.6.3 The Operations

Atomics.load, Atomics.store, Atomics.exchange, and Atomics.compareExchange accept any target ValidateAtomicTarget (43.6.2) admits with operation any-value-type. Atomics.add and Atomics.sub accept the integer types and the floating-point types. Atomics.and, Atomics.or, Atomics.xor, Atomics.wait, Atomics.waitAsync, and Atomics.notify are integer-only: a bitwise operation on a floating-point value has no meaning the program intended, and a wait compares bit patterns.

A value stored through any of these operations passes the typed-storage boundary (1.7.10), so a store of a value not of the target's type throws as an ordinary assignment to that storage would, and the same conversion applies.

43.6.3.1 Floating-Point Addition

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 replacement succeeds. Each attempt is one ReadModifyWriteSharedMemory event.

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 43.8 does.

43.6.3.2 The compareExchange Comparison

Atomics.compareExchange converts the expected value to the target's [[Type]] and then compares it with the value read using SameValueZero. The conversion is not optional: a comparison of an unconverted operand against a typed value read from the target never succeeds, so every compare-exchange would silently fail and a claim loop would spin rather than throw. The TypedArray form of the operation converts for the same reason. On an integer target this is indistinguishable from every other candidate. On a floating-point target it is the choice that makes a compare-exchange loop terminate, which 42 already states normatively and this clause does not restate.

Note

Strict equality would not terminate: NaN is not strictly equal to itself, so a loop whose observed value is NaN would retry against the very value it read, forever. A comparison of representations would terminate but is not specifiable, since an implementation may write an implementation-chosen NaN encoding when it stores a floating-point value, which would leave the success of a compare-exchange varying by implementation. SameValue terminates and distinguishes +0𝔽 from -0𝔽, which breaks the commonest use of the operation in the subtlest way: a computed -0𝔽 fails to match a +0𝔽 sentinel, so a claim loop intermittently refuses a slot that is arithmetically zero. SameValueZero is also the language's own storage equality, the predicate of Map, Set, and Array.prototype.includes, which is the role an expected-value comparison plays.

43.6.3.3 Waiting

Atomics.wait, Atomics.waitAsync, and Atomics.notify on a typed target use the WaiterList of the pinned edition, keyed by the target's [[Block]] and [[ByteIndex]] as they are keyed for a TypedArray. A wait on storage the implementation has not placed in a Shared Data Block places it in one, which is the promotion 43.4.1 describes and the one case in which it is forced rather than discovered.

Atomics.wait throws a TypeError exception where the surrounding agent's [[CanBlock]] is false, as it does today. A wait is a cancellation checkpoint (43.3.4): a signal aborted while a wait is parked wakes it, and the wait then throws the signal's abort reason. The reactions of Atomics.waitAsync run on the agent that created them, as every reaction does (43.2).

43.7 Synchronization Objects

Atomics covers a single location. For anything larger — a multi-field update, a shared collection, a handoff — this clause specifies three ordinary objects: a Lock (43.7.1), a Condition (43.7.2), and a ThreadLocal (43.7.3).

Where the surrounding agent's [[CanBlock]] is false, Lock.prototype.hold, Lock.prototype.acquire, and Condition.prototype.wait throw a TypeError exception, as Atomics.wait does. They throw rather than quietly becoming their async counterparts: an operation returning T on one thread and Promise.<T> on another would have a return type that depends on which thread is running it, which is not a thing a typed language can say.

43.7.1 Lock Objects

A Lock has internal slots [[Owner]], an Agent Record or none, and [[Waiters]], a List of waiting agents in the order they arrived.

A Lock is not reentrant. Reentrant monitors buy tolerance of self-nesting at the price of owner-and-count bookkeeping on the fast path, a Condition that must release and restore a nesting depth, and the silent tolerance of a layering violation that a non-reentrant lock reports the moment it appears.

It is therefore a TypeError exception for an agent to acquire a Lock it already owns through hold or acquire. Those forms block, so the acquisition cannot make progress: the agent that would have to release is the agent now parked, which is a certain deadlock, and the owner is known because 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 host's main thread became an error rather than a frozen page.

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.

43.7.1.1 Lock.prototype.hold ( callback )

It acquires the Lock, calls callback with no arguments, releases the Lock, and returns what callback returned. If callback returns an abrupt completion, the Lock is released and the completion propagates, so the release has the force of a finally.

The closure does not escape the call, so an implementation may allocate nothing for it and may inline the critical section. It may not be marked inline, for the reason a ref callback may not (2).

43.7.1.2 Lock.prototype.acquire ( )

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 critical section wants an early return or spans a try.

Disposing a guard twice is a TypeError exception, as calling a release function twice is, and for the same reason.

Note

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 path, turns forgetting to release into a type error rather than a deadlock at run time. That refinement is not specified here, the guard being an ordinary disposable, but the shape is chosen so that it can be.

43.7.1.3 Lock.prototype.asyncHold ( )

It returns a promise for a release function, fulfilled when the Lock has been acquired for the calling agent. The promise's reactions run on the agent that created them, as every reaction does (43.2), so the critical section that follows the await runs on the thread that asked for the Lock.

The release function may be called once, by the agent that received it. A second call, or a call from another agent, is a TypeError exception. Tolerating a stale release would unlock a critical section that by then belongs to somebody else, which is the one failure a Lock exists to prevent.

43.7.1.4 Ordering

Releasing a Lock synchronizes-with the acquisition that next obtains it, in the sense of 43.5. Everything an agent did while holding the Lock therefore happens-before everything the next holder does, which is what makes a Lock a way to publish a multi-field update that 43.5 would otherwise permit to tear.

Which waiter obtains a released Lock is implementation-defined. An implementation is not required to be fair, and a program that requires fairness builds it rather than assuming it.

43.7.2 Condition Objects

A Condition has an internal slot [[Waiters]].

43.7.2.1 Condition.prototype.wait ( lock )

It is a TypeError exception if lock is not a Lock owned by the surrounding agent. It releases lock and parks the agent in one step, so no notification can be missed between the release and the park, and reacquires lock before returning.

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.

43.7.2.2 Condition.prototype.asyncWait ( lock )

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 agent that created them.

43.7.2.3 Condition.prototype.notify and Condition.prototype.notifyAll

notify wakes one waiter, notifyAll wakes every waiter. Which one notify wakes is implementation-defined. Neither requires the calling agent to hold any Lock, since the Lock a waiter reacquires is the waiter's and not the notifier's.

43.7.2.4 Cancellation

A wait and an asyncWait are cancellation checkpoints (43.3.4). An abort delivered while a waiter is parked wakes it, and the wait then completes abruptly with the signal's abort reason — after reacquiring the Lock, so that the unwinding runs with the invariant the waiter was holding. A Lock acquisition is a checkpoint on the same terms.

43.7.3 ThreadLocal Objects

ThreadLocal.<T> is a generic intrinsic, in the manner of SoA.<T> and Composite.<T>. An instance holds storage per agent: reading its value reads the storage of the surrounding agent, and writing it writes that agent's, so no two threads see one another's.

An agent that has not written the storage reads DefaultValueOf(T) (1.8.3.1), taken in the realm in which the ThreadLocal was created rather than in the reading agent's. Where T is a parameterization its default is a crossing, and a crossing consults the declarations of a realm; taking the reading agent's would let two agents read one storage and receive different values, or one of them a TypeError, for a write neither performed. An agent's storage is unreachable once the agent is not, so a ThreadLocal does not keep the values of ended threads alive.

Note

A ThreadLocal is coherent here because a reaction runs on the thread that created it (43.2). Were a continuation to resume on whichever thread settled the promise it awaited, a ThreadLocal read before an 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 compile-time evaluable (1.2.7). Each is either shared mutable state or a question about which thread is asking, and neither survives being answered at compile time.

43.8 Parallel Iteration

The Thread namespace object carries two operations over a range of integers. They exist because the alternative — spawning a thread per slice by hand and joining it — is both more code and worse, since a thread per call is a thread the pool would have reused.

43.8.1 The Partition

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 implementation-defined as a function of begin and end alone. Nothing else may influence it — not how many threads the host has, not how many are idle, not how long a body took on a previous call.

Which agent executes which slice is not fixed and may vary between runs of one program. An implementation may move whole slices between agents freely, which is what lets it balance work; what it may not do is cut differently because of what its agents are doing.

Note

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.

43.8.2 Thread.parallelFor ( begin, end, body )

It calls body once with each integer in the range, in slices (43.8.1), and returns undefined when every slice has finished.

The calling agent participates: it executes slices itself, and when none remain to claim it waits out the slices still in flight without parking on a WaiterList. A call therefore never blocks in the sense 43.7 uses, and is permitted on an agent whose [[CanBlock]] is false — a host's main thread included, which is where a frame loop lives and which is the reason the operation is shaped this way.

Two properties follow, and both are normative. Executing every slice on the calling agent, in ascending slice order, is a conforming implementation: a host with no threads to give still runs the program, and gets the same answer. And a body that itself calls parallelFor cannot deadlock by exhausting the pool, since the inner call participates on the same terms; the worst case is that the inner range runs on the agent that entered it.

A reference into an element is valid for the slice that borrows it (2.10), and it is a TypeError exception to change the length of an array a slice holds a reference into for the duration of the call. Because slices are disjoint, writes to distinct elements never contend; over a shared SoA.<T>, writes to distinct fields do not contend either, each field being its own region (43.4.1).

43.8.3 Thread.parallelReduce ( begin, end, initial, perElement, combine )

Each slice (43.8.1) folds perElement over its own elements, starting from initial, producing one partial. The partials are then combined with combine in ascending slice order, left to right. The calling agent participates as it does for 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 agent is running, or observes the scheduling in any other way reintroduces exactly the variation the partition rule removes, and this clause promises nothing about such a program.

43.8.4 Errors

If a call to body or perElement completes abruptly in slice k, slices numbered above k are cancelled, slices numbered below k are allowed to finish, and the operation completes abruptly with the completion of the lowest-numbered slice that failed. A combine that completes abruptly is attributed to the later of the two slices it was combining.

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.

Note

Reporting whichever failure happened first in time would be simpler and is what an unordered pool naturally does, but it would make the error a program sees depend on scheduling — so a program with two bad elements would report one of them on a fast machine and the other on a slow one. Collecting every failure would run work already known to be doomed, and the set collected would be scheduling-dependent anyway unless every slice ran to completion.

43.8.5 The Pool

The agents these operations execute slices on are a resource of the agent cluster, sized and managed by the host, and reused across calls. Creating a thread per call would make the operations too expensive for the loop they are meant to sit inside.

Neither operation is compile-time evaluable (1.2.7).

44 UTF-8 Conversions

A string has no layout, its size being a property of the value rather than of the type (9.1), so a string held in a fixed-width record or written to a wire format is held as bytes. Three operations convert between the two, and they are specified here rather than left to a host encoder because what they REFUSE is part of the language.

String.fromUtf8(bytes) returns the String the given bytes encode. bytes is any array-like of byte values, which is what lets a [N].<uint8>, a [].<uint8> and a Span.<uint8> all reach it. An element that is not an integer in the inclusive range 0 to 255 is a TypeError.

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 units: a single astral code point is two code units and four bytes, and a slot is sized in bytes.

44.1 What the Conversions Refuse

A value whose encoding does not fit into is a TypeError, and NOTHING IS WRITTEN. This is the rule 1.6.6 states for a literal a type cannot represent — "a type error rather than a silent truncation" — applied at the other end of the same conversion. The second half is as load-bearing as the first: a partial write would leave a record holding the front of one value and the back of another, which is worse than either, and a caller that caught the error would have no way to know.

Only WELL-FORMED text encodes. A String may hold an unpaired surrogate, which has no UTF-8 encoding; toUtf8 and utf8Length both raise a TypeError for one rather than encoding it as WTF-8 or substituting U+FFFD, either of which lets a value that cannot round-trip appear to have done so. String.prototype.isWellFormed is the question to ask first and String.prototype.toWellFormed is the program's explicit escape — the pair 9.1 draws between 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.

45 Typed JSON Parsing

JSON.parse gains a generic overload, written JSON.parse.<_T_>(_text_), that parses and validates in one pass. The Static Type of the call 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 Static Type any. It parses text as JSON, which is a SyntaxError on malformed input exactly as the untyped JSON.parse is, and then converts the resulting value against T by 45.1. Well-formed JSON that does not satisfy T is a TypeError naming the path at which the mismatch occurred, so a typed catch (_e_: SyntaxError) and catch (_e_: TypeError) separate a malformed document from a well-formed one of the wrong shape.

A JSON array coerces against an array type, whose extent it must match where that is fixed, and against a tuple type, whose admitted lengths it must be one of and whose positions type its elements one by one, with a short array taking the trailing positions' declared defaults. Validation is otherwise layered as any other boundary in this specification: a leaf is checked against its type as it is reached, a parameterized type consults its metadata's validation judgment (1.7.6.1), and a class or record where clause runs once the target is filled, as the construction boundary check. Unlike the general boundary of 1.7.7.1, which distinguishes a RangeError for a numeric value the target cannot represent, every failure here is a TypeError: in a parsed document the type is the schema, so a value outside a field's range violates the schema rather than overflowing an arithmetic. The overload takes no reviver, since a reviver observes the intermediate untyped graph this feature exists not to build.

45.1 CoerceJSONValue ( value, type, path )

The abstract operation CoerceJSONValue takes arguments value (an ECMAScript language value), type (a Type Record), and path (a String) and returns either a normal completion containing an ECMAScript language value or a throw completion. It converts the JSON value into type, or returns a throw completion whose value is a TypeError describing path. It may call user code, since converting a structural type reads and writes properties. It performs the following steps when called:

  1. If type.[[Kind]] is any, return value.
  2. If type.[[Kind]] is reference or type.[[Kind]] is shared, return ? CoerceJSONValue(value, type.[[Target]], path).
  3. If type is a composite type (25.2), then
    1. If type is the top composite type, throw a TypeError exception.
    2. Let shape be the shape of type.
    3. Let data be ? CoerceJSONValue(value, shape, path).
    4. If shape.[[Kind]] is tuple, then
      1. Let elements be a new empty List.
      2. Let len be ! LengthOfArrayLike(data).
      3. Let i be 0.
      4. Repeat, while i < len,
        1. Append CanonicalizeCompositeValue(! Get(data, ! ToString(𝔽(i)))) to elements.
        2. Set i to i + 1.
      5. Return ? FindOrCreateTupleComposite(elements).
    5. Let entries be a new empty List.
    6. For each own property key k of data, in the order returned by ! data.[[OwnPropertyKeys]](), do
      1. Append the Composite Entry Record { [[Key]]: k, [[Value]]: CanonicalizeCompositeValue(! Get(data, k)) } to entries.
    7. Return FindOrCreateComposite(entries).
  4. If type.[[Kind]] is primitive, then
    1. If type is a numeric type, then
      1. If value is not a Number, throw a TypeError exception.
      2. If type is a sized integer type and the mathematical value of value is not an integer in the range of type, throw a TypeError exception.
      3. If type is the number type, return value.
      4. Return value converted to type by Table 19.
    2. If type is the string type, then
      1. If value is not a String, throw a TypeError exception.
      2. Return value.
    3. If type is the boolean type, then
      1. If value is not a Boolean, throw a TypeError exception.
      2. Return value.
    4. If type is the null type, then
      1. If value is not null, throw a TypeError exception.
      2. Return value.
    5. Throw a TypeError exception.
  5. If type.[[Kind]] is literal, then
    1. If SameValue(value, type.[[Value]]) is true, return value.
    2. Throw a TypeError exception.
  6. If type.[[Kind]] is union, then
    1. For each element m of type.[[Members]], do
      1. Let r be Completion(CoerceJSONValue(value, m, path)).
      2. If r is a normal completion, return r.[[Value]].
    2. Throw a TypeError exception.
  7. If type.[[Kind]] is array, then
    1. If value is not an Array, throw a TypeError exception.
    2. Let len be ? LengthOfArrayLike(value).
    3. If type.[[Extent]] is not dynamic and type.[[Extent]] ≠ len, throw a TypeError exception.
    4. Let elements be a new empty List.
    5. Let i be 0.
    6. Repeat, while i < len,
      1. Let element be the element of value at index i.
      2. Let elementPath be the string-concatenation of path, the code unit 0x005B (LEFT SQUARE BRACKET), the decimal representation of i as a String, and the code unit 0x005D (RIGHT SQUARE BRACKET).
      3. Let converted be ? CoerceJSONValue(element, type.[[Element]], elementPath).
      4. Append converted to elements.
      5. Set i to i + 1.
    7. Return CreateArrayFromList(elements).
  8. If type.[[Kind]] is tuple, then
    1. If value is not an Array, throw a TypeError exception.
    2. Let len be ? LengthOfArrayLike(value).
    3. If len is not a length type admits (1.14.7), throw a TypeError exception.
    4. Let elements be a new empty List.
    5. Let i be 0.
    6. Repeat, while i < len,
      1. Let element be the element of value at index i.
      2. Let elementPath be the string-concatenation of path, the code unit 0x005B (LEFT SQUARE BRACKET), the decimal representation of i as a String, and the code unit 0x005D (RIGHT SQUARE BRACKET).
      3. Let converted be ? CoerceJSONValue(element, the [[Type]] of the element of type.[[Elements]] that receives position i, elementPath).
      4. Append converted to elements.
      5. Set i to i + 1.
    7. For each element e of type.[[Elements]] whose position is not less than len, do
      1. If e.[[Rest]] is false, then
        1. Assert: e.[[Initial]] is not none, since a tuple's defaults are trailing (1.4.2.7) and len is a length type admits.
        2. Append e.[[Initial]] to elements.
    8. Return CreateArrayFromList(elements).
  9. If type.[[Kind]] is object, then
    1. If value is not an Object or value is an Array, throw a TypeError exception.
    2. Let result be OrdinaryObjectCreate(%Object.prototype%).
    3. Let keys be ? value.[[OwnPropertyKeys]]().
    4. For each element k of keys such that k is a String and no Property Type Record of type.[[Properties]] has key k, do
      1. Let accepted be false.
      2. For each index signature s of type, do
        1. If accepted is false and s accepts k, then
          1. Let kv be ? Get(value, k).
          2. Let keyPath be the string-concatenation of path, the code unit 0x002E (FULL STOP), and k.
          3. Let converted be ? CoerceJSONValue(kv, s's value type, keyPath).
          4. Perform ! CreateDataPropertyOrThrow(result, k, converted).
          5. Set accepted to true.
      3. If accepted is false, throw a TypeError exception.
    5. For each Property Type Record p of type.[[Properties]], do
      1. Let present be ? HasProperty(value, p's key).
      2. If present is true, then
        1. Let pv be ? Get(value, p's key).
        2. Let fieldPath be the string-concatenation of path, the code unit 0x002E (FULL STOP), and p's key.
        3. Let converted be ? CoerceJSONValue(pv, p's type, fieldPath).
        4. Perform ! CreateDataPropertyOrThrow(result, p's key, converted).
      3. Else if p is optional, then
        1. If p's [[Initial]] is not none, perform ! CreateDataPropertyOrThrow(result, p's key, p's [[Initial]]).
      4. Else,
        1. Throw a TypeError exception.
    6. Return result.
  10. If type.[[Kind]] is parameterized, then
    1. Let base be ? CoerceJSONValue(value, type.[[Base]], path).
    2. If ? IsOfType(base, type) is false, throw a TypeError exception.
    3. Return base.
  11. If type.[[Kind]] is nominal, then
    1. If type is an enum type, then
      1. If value equals the value of an enumerator of type, return that enumerator.
      2. Throw a TypeError exception.
    2. If type has a structural form, return ? CoerceJSONValue(value, that form, path).
  12. Throw a TypeError exception.
Note

The physical form the extension describes, writing straight into contiguous typed storage with no intermediate graph, is an implementation strategy and not observable, so an implementation that parses to the untyped JSON value and then converts it conforms.

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 constructor, the exact wide numeric types (the 64-bit integers, 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 intersection target is deferred with them, and deliberately: coercion must produce one value, so an intersection of object types needs a rule merging its members into the single shape the walk fills, including what a key both members declare converts to, and that rule belongs with the objects work rather than with serialization. A function target throws, since no JSON value denotes a function, and the remaining kinds cannot reach a specialized type.

Annex A (normative) The Compile-Time Evaluable Fragment

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 (1.2.7.4), is what makes that agreement a checkable claim.

A.1 The Grammar of the Fragment

The fragment is the ECMAScript grammar less the following, together with the reading discipline of 1.2.7.1, which is what excludes input, output, and ambient state, since code in the fragment cannot name them.

  • AwaitExpression and YieldExpression, and every async and generator form of function, method, and arrow.
  • ClassDeclaration and ClassExpression: a builder transforms and constructs types but does not mint a nominal one (1.5.20).
  • A direct or indirect eval, the Function constructor, the construction of a Proxy, and a dynamic import(), which the library rule below also excludes.
  • The 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 1.2.7.4's carve-out, template literals, destructuring, spread, and regular expression literals.

A.2 The Library Surface

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 host or ambient state. The floor the fragment guarantees: the type operations of 1.12, Type Object identity and 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.

A.3 Semantics and Conformance

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 1.2.7.4 and with the memoization of 1.2.7.3 or anything observably equivalent to it.

Note

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 1.5.20 requires them anyway.

A.4 The Conformance Suite and the Acceptance Corpus

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 design's acceptance corpus: one hundred and ninety programs, the whole of the type-challenges repository, the warm-up, thirteen easy, one hundred and four medium, fifty-five hard, and seventeen extreme, each pairing the erased-language solution with a builder solution whose assertions must hold under this specification's semantics. The assertions include type identity written ===, which interning supplies and which the erased corpus can only imitate through unspecified checker behaviour.

Note

The corpus is maintained with the design, and the symbol literal types its eleventh challenge exercises are now those of 1.5.19, so no challenge is gated on an unadopted extension. The questions and divergences the corpus surfaced are recorded in its own notes and tracked with the design documents.

Annex B (informative) The Standard Builder Kit

The primitives are normative and live in the core: construction, relations, 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 typed signatures to methods that already exist and introduces no module, where the kit is new code and needs one. The kit is therefore the single new specifier, and a signature listing is never one until its final home is reconciled with the standard library extension. Shipping it as source is the proof that no construct in it is engine magic, gives the ecosystem one interned vocabulary, so that 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 Table 64: 71 exports in nine families. Each row gives the name, its signature as the module declares it, and what it answers. The definitions are the normative text — that is what shipping as source means — and the table is the index into them, so a reader can find an export without reading three hundred lines and a conformance suite has one place to enumerate. Three exports the table does not list are worth naming for the same reason: 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.

Table 64: The standard builder kit
ExportSignatureAnswers
reflectreflect(T: type)The type node of T, as 1.12.4 gives it.
literalliteral(value: string | number | boolean | bigint): typeThe literal type of value.
unionunion(armList: [].<type>): typeThe union of armList.
armsarms(T: type): [].<type>The arms of a union, or the one-element list of a non-union.
intersectionintersection(members: [].<type>): typeThe intersection of members.
literalValuesliteralValues(T: type)The values of a union of literal types, in order.
literalValueliteralValue(T: type): anyThe value of a literal type.
propprop(name: string | symbol, type: type,A property record, for objectOf.
objectOfobjectOf(properties: [].<any>, indexSignatures: [].<any> = []): typeAn object type with the given properties.
tupleOftupleOf(types: [].<type>): typeA tuple type with the given element types.
arrayOfarrayOf(element: type, extent: uint32 | undefined = undefined): typeA dynamic array type of element.
tupleElementstupleElements(T: type): [].<any>The element records of a tuple.
elementTypeselementTypes(T: type): [].<type>The element types of a tuple.
mapPropertiesmapProperties(T: type, f): typeT with each property record replaced by f of it.
mapPropertyTypesmapPropertyTypes(T: type, f): typeT with each property TYPE replaced by f of it.
mapElementsmapElements(T: type, f): typeA tuple with each element type replaced by f of it.
propertyTypepropertyType(T: type, name: string | symbol)The type of the named property of T.
genericApplicationgenericApplication(base: type | string, args: [].<any>): typebase applied to args, using an intrinsic declaration's String name or a nominal declaration Type Object as in 1.12.4. Intrinsic defaults and argument domains are checked by 1.5.25; library applications use 1.4.2.3.
partialpartial(T: type): typeT with every property optional.
requiredrequired(T: type): typeT with every property required.
readonlyreadonly(T: type): typeT with every property readonly.
mutablemutable(T: type): typeT with every property mutable.
pickpick(T: type, K): typeT restricted to the keys of K.
omitomit(T: type, K): typeT without the named keys.
recordrecord(K: type, V: type): typeAn object type with the keys of K, each of type V.
pickByValuepickByValue(T: type, V: type): typeT restricted to properties whose type is assignable to V.
removeKindremoveKind(T: type): typeT without its kind property.
mergemerge(A: type, B: type): typeA overwritten by B.
renamePropertiesrenameProperties(T: type, f): typeT with each property name replaced by f of it.
excludeexclude(T: type, U: type): typeThe arms of T not assignable to U.
extractextract(T: type, U: type): typeThe arms of T assignable to U.
nonNullablenonNullable(T: type): typeT without null and undefined.
mapUnionmapUnion(T: type, f): typeThe union of f applied to each arm.
toArrayEachtoArrayEach(T: type): typeA union of arrays, one per arm.
toArrayAlltoArrayAll(T: type): typeAn array of the whole union.
discriminantsdiscriminants(T: type, tag: string = 'kind')The literal values of the kind property across a union.
byKindbyKind(T: type, k: string, tag: string = 'kind'): typeThe arm of T whose kind is k.
handlershandlers(T: type, R: type, tag: string = 'kind'): typeAn object type of one handler per arm, keyed by discriminant.
fnfn(parameterTypes: [].<type>, returnType: type): typeA function type with the given parameter types and return type.
flattenflatten(T: type): typeThe element type of an array or tuple.
firstParameterfirstParameter(F: type): typeThe first parameter type.
returnTypereturnType(F: type): typeThe return type of a function type.
parametersparameters(F: type): typeThe parameter types of a function type, as a tuple.
mapLiteralsmapLiterals(T: type, f): typeA union of literals with each value replaced by f of it.
uppercaseuppercase(T: type): typeThe literal union uppercased.
lowercaselowercase(T: type): typeThe literal union lowercased.
capitalizedcapitalized(T: type): typeThe literal union with each first character uppercased.
uncapitalizeduncapitalized(T: type): typeThe literal union with each first character lowercased.
gettersgetters(T: type): typeAn object type of get-prefixed readonly accessors over T.
listenerslisteners(T: type): typeAn object type of on-prefixed change listeners over T.
headhead(T: type): typeThe first element type of a tuple.
tailtail(T: type): typeThe tuple without its first element.
concatconcat(A: type, B: type): typeThe two tuples joined.
reversereverse(T: type): typeThe tuple reversed.
zipzip(A: type, B: type): typeThe two tuples zipped into a tuple of pairs.
deepPartialdeepPartial(T: type): typepartial, applied recursively through object properties.
pathspaths(T: type): typeThe union of dotted paths through T.
composecompose(...fs)The composition of the given type functions.
traversetraverse(T: type,T rebuilt through the given per-kind visitors.
deepMapdeepMap(T: type, leaf): typeT with f applied to every leaf type.
keyskeys(T: type): typeThe key union of T — the function form of keyof.
indexedindexed(T: type, K: type): typeThe indexed access T[K].
awaitedawaited(T: type): typeThe awaited type of a Promise, or T unchanged.
routeParamsrouteParams(path: string): typeAn object type of the :name parameters of a route pattern.
noInfernoInfer(T: type): typeT, blocking inference at this position.
withThisTypewithThisType(F: type, Self: type): typeThe signature with Self as its this type.
thisParameterTypethisParameterType(F: type): typeThe declared this type, or never.
omitThisParameteromitThisParameter(F: type): typeThe signature without its this type.
optionsoptions(Data: type, Methods: type): typeAn object type pairing state with its operations.
brandbrand(T: type, tag: string | symbol): typeT refined by a brand metadata portion carrying tag.
constructorParametersconstructorParameters(C: type): typeThe constructor parameter types of a class, as a tuple.

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 } });
}
Note

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 1.12.5 rather than to equivalence: reflecting a type, transforming its node, and constructing the result must yield the same Type Object where the transformation changed nothing. The distinction is invisible to any check short of === — a type rebuilt incorrectly answers with the same kind, the same base and the same fields — so a conformance suite that compares shape does not test it. The key operation is not among them - 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.

Copyright & Software License

Software License

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:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.

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.