Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

make harden() return a readonly type #2244

Open
turadg opened this issue Apr 25, 2024 · 0 comments
Open

make harden() return a readonly type #2244

turadg opened this issue Apr 25, 2024 · 0 comments
Labels
enhancement New feature or request

Comments

@turadg
Copy link
Member

turadg commented Apr 25, 2024

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

@turadg turadg added the enhancement New feature or request label Apr 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant