Skip to content

Future-proof always-aliasing of mapped/intersection/union/etc. types #35616

Closed
@AnyhowStep

Description

@AnyhowStep

TypeScript Version: 3.5.1, 3.7.2

Search Terms:

Force TS to alias type, optional property, type expand

Code

type Identity<T> = T;

type PleaseDoNotExpand<T> =
    | T
    | { x : T }
;

type MappedType<ObjT> =
    Identity<
        & {
            readonly [k in Exclude<keyof ObjT, "x">]: (
                PleaseDoNotExpand<ObjT[k]>
            )
        }
        & {
            readonly [k in Extract<keyof ObjT, "x">]?: (
                PleaseDoNotExpand<ObjT[k]>
            )
        }
    >
;

declare function mappedType<ObjT>(obj: ObjT): MappedType<ObjT>;

/*
    Expected:
    const x: {
        readonly y: PleaseDoNotExpand<string>;
        readonly z: PleaseDoNotExpand<boolean>;
    } & {
        readonly x?: PleaseDoNotExpand<number>|undefined;
    }
*/
/*
    Actual:
    const x: {
        readonly y: PleaseDoNotExpand<string>;
        readonly z: PleaseDoNotExpand<boolean>;
    } & {
        readonly x?: number | {
            x: number;
        } | undefined;
    }
*/
const x = mappedType({
    x: 1,
    y: "hi",
    z: true,
});

Expected behavior:

Expected x to resolve to,

    const x: {
        readonly y: PleaseDoNotExpand<string>;
        readonly z: PleaseDoNotExpand<boolean>;
    } & {
        readonly x?: PleaseDoNotExpand<number>|undefined;
    }

Actual behavior:

Actually resolves to,

    const x: {
        readonly y: PleaseDoNotExpand<string>;
        readonly z: PleaseDoNotExpand<boolean>;
    } & {
        readonly x?: number | {
            x: number;
        } | undefined;
    }

This behaviour is caused by the ? optional property modifier. It seems to do something that messes up emit, and makes it inconsistent with required properties.

Since required properties do not expand the PleaseDoNotExpand<> type,
optional properties should also not expand the PleaseDoNotExpand<> type.

I believe this is a bug in emit.

Playground Link:

Playground

Related Issues:

Kind of related to this,
#34556

That issue wants a way to future-proof ways of forcing TS to not alias a type.
The Identity<> trick works nicely at the moment.


Also related to this,
#34777

#34777 (comment)

That issue wants a way to force TS to always alias a type.
There is a workaround using interface when the properties are always known and you do not have union types.

It does not apply in this case, because I have union types.


This mini-rant probably belongs in a different issue but...

I would like a way to have TS never expand a type alias for hover tooltip and emit.
Not by default, anyway.

My actual PleaseDoNotExpand<> type is easily 100+ lines long. It makes everything unreadable, when TS fully expands it.

I can read PleaseDoNotExpand<Date> better than I can read the following,

    readonly createdAt?: Date | tsql.IExpr<{
        mapper: Mapper<unknown, Date>;
        usedRef: tsql.IUsedRef<{}>;
    }> | (tsql.IQueryBase<{
        fromClause: tsql.IFromClause<tsql.FromClauseData>;
        selectClause: [tsql.IColumn<{
            tableAlias: string;
            columnAlias: string;
            mapper: Mapper<unknown, Date>;
        }> | tsql.IExprSelectItem<{
            mapper: Mapper<unknown, Date>;
            tableAlias: string;
            alias: string;
            usedRef: tsql.IUsedRef<never>;
        }>];
        limitClause: tsql.LimitClause | undefined;
        compoundQueryClause: readonly tsql.CompoundQuery[] | undefined;
        compoundQueryLimitClause: tsql.LimitClause | undefined;
        mapDelegate: tsql.MapDelegate<never, never, unknown> | undefined;
    }> & tsql.IQueryBase<{
        fromClause: tsql.IFromClause<{
            outerQueryJoins: readonly tsql.IJoin<tsql.JoinData>[] | undefined;
            currentJoins: undefined;
        }>;
        selectClause: readonly (tsql.ColumnMap | tsql.IColumn<tsql.ColumnData> | tsql.IExprSelectItem<tsql.ExprSelectItemData> | tsql.ColumnRef)[] | undefined;
        limitClause: tsql.LimitClause | undefined;
        compoundQueryClause: undefined;
        compoundQueryLimitClause: tsql.LimitClause | undefined;
        mapDelegate: tsql.MapDelegate<never, never, unknown> | undefined;
    }> & tsql.IQueryBase<{
        fromClause: tsql.IFromClause<{
            outerQueryJoins ...

Metadata

Metadata

Assignees

No one assigned

    Labels

    UnactionableThere isn't something we can do with this issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions