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

Issues with NonReadonly types and array types #828

Closed
localrobot opened this issue Apr 18, 2023 · 5 comments
Closed

Issues with NonReadonly types and array types #828

localrobot opened this issue Apr 18, 2023 · 5 comments

Comments

@localrobot
Copy link

localrobot commented Apr 18, 2023

When an object contains a field with an array type, the NonReadonly wrapped type cannot be assinged to an array.

For Reference (this is generated for NonReadonly type:

type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? A : B;

type WritableKeys<T> = { [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P> }[keyof T];

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
type DistributeReadOnlyOverUnions<T> = T extends any ? NonReadonly<T> : never;

type Writable<T> = Pick<T, WritableKeys<T>>;

type NonReadonly<T> = [T] extends [UnionToIntersection<T>]
  ? { [P in keyof Writable<T>]: T[P] extends object ? NonReadonly<NonNullable<T[P]>> : T[P] }
  : DistributeReadOnlyOverUnions<T>;

What are the steps to reproduce this issue?

Example:

type Name = { first: string, last: string };

type NamesObject = { names: Name[] };

const namesObj: NonReadonly<NamesObject> = { names: [{ first: 'a', last: 'b' }] }; // This works.

// Type '{ [x: number]: { first: string; last: string; }; }' is missing the following properties from
// type 'Name[]': length, pop, push, concat, and 29 more.ts(2740)
const names: Name[] = namesObj.names; // This does not.

More Information

The problems goes away if the NonReadonly type is defined as below.:

type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? A : B;

type WritableKeys<T> = { [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P> }[keyof T];

type Writable<T> = Pick<T, WritableKeys<T>>;

type NonReadonly<T> = {
  [P in keyof Writable<T>]: T[P] extends number | string | boolean | undefined | null
    ? T[P]
    : NonReadonly<NonNullable<T[P]>>;
};

This was how it was defined in an earlier version of #813. See also: #813 (comment)

Additional Question

Can the NonReadonly type be exported? This would come in handy when extending generated code.

What versions are you using?

Operating System: Win 10
Package Version: 6.14.3

@localrobot localrobot changed the title Issues NonReadonly types with array types Issues with NonReadonly types and array types Apr 18, 2023
@anymaniax
Copy link
Collaborator

Hello @localrobot, for me in both case I have the problem

@anymaniax
Copy link
Collaborator

anymaniax commented Apr 18, 2023

This one seems to work properly no?

type IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X ? 1 : 2) extends <
  T,
>() => T extends Y ? 1 : 2
  ? A
  : B;

type WritableKeys<T> = {
  [P in keyof T]: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>;
}[keyof T];

type DeepWritablePrimitive =
  | undefined
  | null
  | boolean
  | string
  | number
  | Function;
type DeepWritable<T> = T extends DeepWritablePrimitive
  ? T
  : T extends Array<infer U>
  ? DeepWritableArray<U>
  : T extends Map<infer K, infer V>
  ? DeepWritableMap<K, V>
  : T extends Set<infer T>
  ? DeepWriableSet<T>
  : DeepWritableObject<T>;

type DeepWritableArray<T> = Array<DeepWritable<T>>;
type DeepWritableMap<K, V> = Map<K, DeepWritable<V>>;
type DeepWriableSet<T> = Set<DeepWritable<T>>;

type DeepWritableObject<T> = {
  [K in WritableKeys<T>]: DeepWritable<T[K]>;
};


type Name = { first: string; last: string };

type NamesObject = { names: Name[]; readonly id: string };

const namesObj: DeepWritable<NamesObject> = {
  names: [{ first: 'a', last: 'b' }],
};

const names: Name[] = namesObj.names; 

@anymaniax
Copy link
Collaborator

I think we can also omit empty value type with this

type EmptyKeys<T> = {
  [P in keyof T]: {} extends T[P] ? P : [{}] extends T[P] ? P : never;
}[keyof T];

type OmitEmptyKeys<T> = Omit<T, EmptyKeys<T>>;
type DeepWritableWithoutEmpty<T> = OmitEmptyKeys<DeepWritable<T>>;

all coming from here

@localrobot
Copy link
Author

Those also seem to have problems:

type Name = { first: string; last: string, address?: { line1: string, line2?: string } };

type NamesObject = { names: Name[]; readonly id: string };

// Property 'address' is missing in type '{ first: string; last: string; }' but required in type
// 'DeepWritableObject<Name>'.ts(2741)
const namesObj: DeepWritable<NamesObject> = {
  names: [{ first: 'a', last: 'b' }],
};

const names: Name[] = namesObj.names; 

// Property 'line2' is missing in type '{ line1: string; }' but required in type 
// 'DeepWritableObject<{ line1: string; line2?: string | undefined; }>'.ts(2741)
const name: DeepWritable<Name> = { first: 'a', last: 'b', address: { line1: 'abc' } }

@melloware
Copy link
Collaborator

So where are we with this ticket?

@melloware melloware closed this as not planned Won't fix, can't repro, duplicate, stale Mar 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants