question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Explicit generic type argument gets lost when attempting to carry through member expression

See original GitHub issue

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

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:7 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
AnyhowStepcommented, Aug 17, 2019

Your handler1 working surprises me, honestly.

1reaction
RyanCavanaughcommented, Aug 16, 2019

This needs a reasonable-sized repro before we can look at it - once things get this long, 99 times out of 100 it is a problem with the code itself, not TS.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Try adding an explicit type like 'dynamic', or enable implicit ...
Missing type arguments for generic type 'MaterialPageRoute'. Try adding an explicit type like 'dynamic', or enable implicit-dynamic in your ...
Read more >
How To Use Generics in TypeScript - DigitalOcean
In this code, you are creating a new generic type called IsStringType that receives a single type parameter, T . Inside the definition...
Read more >
Compiler Error CS0411 - Microsoft Learn
This error occurs if you call a generic method without explicitly providing the type arguments and the compiler cannot infer which type ...
Read more >
Function template - cppreference.com
The function parameters that do not participate in template argument deduction (e.g. if the corresponding template arguments are explicitly specified) are ...
Read more >
Java Generics FAQs - Under The Hood Of The Compiler
"Unchecked" warnings stem either from using generic types in their raw form or from casts whose target type is a type parameter or...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found