Skip to content

Optional nested readonly type changes type of property adding undefined #55058

Closed as not planned
@SimonSimCity

Description

@SimonSimCity

Bug Report

I was working with a library which reported that a property had an additional value of undefined, and the author of the plugin claimed that the error went by removing readonly. After investigating I found the following, which I found confusing.

The framework we use (vue) has a function for marking objects as deeply readonly:

export declare function readonly<T extends object>(target: T): DeepReadonly<T>;
export declare type DeepReadonly<T> = T extends {} ? {
    readonly [K in keyof T]: DeepReadonly<T[K]>;
} : Readonly<T>;

When wrapping an object with optional properties:

export declare class User {
    a?: string;
    b: string;
}
export const user = readonly(new User() )

... the exported types have an additional undefined at the type definition, even though it wasn't allowed in the original type.

export declare class User {
    a?: string;
    b: string;
}
export declare const user: {
    readonly a?: string | undefined;
    readonly b: string;
};

🔎 Search Terms

extra undefined, nested readonly, deepreadonly

🕗 Version & Regression Information

  • This is the behavior in every version I tried.

⏯ Playground Link

Playground link with relevant code

💻 Code

This was the smallest I could reduce the code to and still see the compiler doing the unexpected:

export declare function readonly<T extends object>(target: T): DeepReadonly<T>;
export declare type DeepReadonly<T> = T extends {} ? {
    readonly [K in keyof T]: DeepReadonly<T[K]>;
} : Readonly<T>;

export declare class User {
    a?: string;
    b: string;
}

export declare type ReadOnlyUser = DeepReadonly<User>

export const user = readonly(new User() )

🙁 Actual behavior

The code above is transformed to the following types. The generated type for the variable user is incompatible with the exported class User, because it adds undefined as type to the type of the optional parameter of the class.

export declare const user: {
    readonly a?: string | undefined;
    readonly b: string;
};

🙂 Expected behavior

The types of User and user should be identical, the latter should just have all props as readonly, but there should be no other changes to the type.

export declare const user: {
    readonly a?: string;
    readonly b: string;
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions