Skip to content

make harden() return a readonly type #2244

@turadg

Description

@turadg

What is the Problem Being Solved?

harden in the SES package makes the argument value immutable. Currently the return type is the value itself, despite this transformation. Here is the rationale:

// It's academically tempting to define a hardened type, but TypeScript doesn't
// strike a good balance in distinguishing "readonly" in the sense that you
// promise not to change vs "readonly" in the sense that you depend on a thing
// not changing.
// type Hardened<T> =
// T extends number | bigint | string | null | undefined | Function ? T :
// { readonly [P in keyof T]: Hardened<T[P]> };
// So Harden just passes the type through without modification.
// This will occasionally conflict with the type of Object.freeze.
// In those cases, we recommend casting the result of Object.freeze to the
// original thawn type, as if the signature of freeze were identical to this
// version of harden.
export type Harden = <T>(value: T) => T; // not Hardened<T>;

Because of this, the type system does not detect when a mutation is attempted on a hardened value. Solving this requires solving several sub-problems:

A migration path

Function signatures currently aren't responsible for saying they won't mutate the parameters. If a Hardened value is passed to a function that doesn't say it accepts readonly then the type checker will complaint that the function expects a mutable version.

Conveying the side effect
harden() returns a hardened value, but it also modifies the value itself. In prototyping this with,

declare function enharden<T>(val: T): asserts val is Hardened<T>;

I encountered,

A type predicate's type must be assignable to its parameter's type.
  Type 'Hardened<T>' is not assignable to type 'T'.
    'T' could be instantiated with an arbitrary type which could be unrelated to 'Hardened<T>'.

Enforcement of readonly

Even if we were to solve the above, TypeScript currently doesn't enforce the readonly attribute: microsoft/TypeScript#13347 But this may be solved soon: microsoft/TypeScript#58296

Description of the Design

Very TBD. It's not clear yet whether any design would be worth the cost in DevEx, let alone time to implement.

Migration path

We could have a utility like NoWrite,

type NoWrite<T> = T | Hardened<T>;

function printNum(nums: NoWrite<number[]>) {
  console.log(nums);
  // @ts-expect-error readonly
  nums[0] = 3;
}

Security Considerations

Scaling Considerations

Test Plan

Compatibility Considerations

Upgrade Considerations

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions