10000 Explicit generic type argument gets lost when attempting to carry through member expression · Issue #32937 · microsoft/TypeScript · GitHub
[go: up one dir, main page]

Skip to content
Explicit generic type argument gets lost when attempting to carry through member expression #32937
@jeffijoe

Description

@jeffijoe

TypeScript Version: 3.5.3

Search Terms: inference nested member

Code

The Playground link reproduces this issue, but here is the whole code.

// Issue: the generics are not flowing all the way through.

// This works fine
const handler1 = CommandHandler.create<RespondToQuestion>(
    requireThat(
        isCollaboratorOnRequest()
    ),
    requireThat(
        subjectIs(allowedTo.fooTheBar, BasedOnClaims.fromMessageBody(b => b))
    ),
    async ({ body }) => {
        console.log(body.subject_id)
        return { version: 1 }
    }
)

// But when I start chaining the specifications, the type that has been specified to be `RespondToQuestion` is
// now `unknown`.
const handler2 = CommandHandler.create<RespondToQuestion>(
    requireThat(
        isCollaboratorOnRequest().or(
            subjectIs(allowedTo.fooTheBar, BasedOnClaims.fromMessageBody(b => b))
        ) 
    ),
    async ({ body }) => {
        console.log(body.subject_id)
        return { version: 1 }
    }
)

// Specifying the generic seems to do the trick though, but why do I have to? Shouldn't it flow through?
const handler3 = CommandHandler.create<RespondToQuestion>(
    requireThat(
        isCollaboratorOnRequest<RespondToQuestion>().or(
            subjectIs(allowedTo.fooTheBar, BasedOnClaims.fromMessageBody(b => b))
        ) 
    ),
    async ({ body }) => {
        console.log(body.subject_id)
        return { version: 1 }
    }
)


const msg: Message<RespondToQuestion> = { body: { type: 'RespondToQuestion', subject_id: '123' }, meta: null as any }
handler1(msg)
handler2(msg)

handler3(msg)



// Sorry for all the types 😅
export interface AsyncSpecification<T> {
  and(other: AsyncSpecification<T>): AsyncSpecification<T>;
  or(other: AsyncSpecification<T>): AsyncSpecification<T>;
  execute(value: T): Promise<T>;
}

export declare function isCollaboratorOnRequest<T>(): AsyncSpecification<Message<T>>;

export interface Message<T, M = unknown> {
  body: T;
  meta: M;
}

export interface Claims {
  subject_id: string;
}

export type ClaimsLookupStrategy<T> = (message: Message<T>) => Promise<Claims>;

export type AllowedToBasedOnClaims = (claims: Claims) => Promise<Claims>;

export declare function subjectIs<T>(
  checkAllowedToBasedOnClaims: AllowedToBasedOnClaims,
  claimsLookupStrategy: ClaimsLookupStrategy<T>
): AsyncSpecification<Message<T>>;

export interface AllowedTo {
    fooTheBar: AllowedToBasedOnClaims;
}

export declare type Handler<I, O = unknown> = (input: Message<I>) => Promise<O>;
export declare type Pipe<I, O = unknown> = (input: Message<I>, next: Handler<I, O>) => Promise<O>;

export interface CommandResult {
    version: number | null;
}

export interface Command { type: string }

export declare const CommandHandler: {
    create: <C extends Command>(...pipes: Pipe<C, CommandResult>[]) => Handler<C, CommandResult>;
};

export declare function requireThat<I, O>(specification: AsyncSpecification<Message<I>>): Pipe<I, O>;

export interface RespondToQuestion extends Command {
    type: 'RespondToQuestion'
    subject_id: string
}

export declare const allowedTo: AllowedTo

export declare type ClaimsWithOptionalSubjectId = Omit<Claims, 'subject_id'> & {
    subject_id?: string;
};
export declare const BasedOnClaims: {
    fromMessage<T>(messageBodyToClaims: (message: Message<T, unknown>) => ClaimsWithOptionalSubjectId): ClaimsLookupStrategy<T>;
    fromMessageBody<T>(messageBodyToClaims: (body: T) => ClaimsWithOptionalSubjectId): ClaimsLookupStrategy<T>;
};

Expected behavior:

The type argument RespondToQuestion passed to CommandHandler.create<RespondToQuestion>() should flow all the way through consistently.

Actual behavior:

When using certain patterns, TS will change the type to unknown.

Additionally, it seems it can't make up its' mind. 😅 The error says body can't be inferred, and yet the tip gets it right.

image


image

Playground Link: Playground

Related Issues: These look related: #30134, #16597

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs More InfoThe issue still hasn't been fully clarified

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0