Skip to content

Mapped conditional type's type information not used to constrain type of indexed access #42882

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

Closed
sigurdschneider opened this issue Feb 19, 2021 · 6 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@sigurdschneider
Copy link

Bug Report

Hi, I have a case where I'm unsure whether I'm holding things wrong, or whether this is a bug, or whether this is the expected behavior.

🔎 Search Terms

Generics, type inference, type checking, type constraints

🕗 Version & Regression Information

4.2-dev, no regression range information. This is the behavior in every version I tried, and I reviewed the FAQ for entries about apply and functions.

⏯ Playground Link

Playground link with relevant code

💻 Code

type NamedFunctionProperties<T> = { [F in keyof T]: T[F] extends CallableFunction ? F : never }[keyof T];

class Observable<Observer> {
    constructor(private observer:Observer) {}

    // This is what I would like to do, but my understanding is that this doesn't work because it requires a dependent type (i.e. a type that depends on a value).
    // This is what works from the outside, but it isn't clear to me why the apply doesn't typecheck.
    observeShouldWork<F extends NamedFunctionProperties<Observer>>(methodName: F, ...args: Parameters<Observer[F]>) {
        const f = this.observer[methodName];
        f.apply(this.observer, args); // Property 'apply' does not exist on type 'Observer[F]'.(2339)
    }    
}

🙁 Actual behavior

TS complains that property 'apply' does not exist on type 'Observer[F]'.(2339) in f.apply above.

🙂 Expected behavior

Since F is constrained to NamedFunctionProperties<Observer>, and TS does derive the type Observer[F] for f (that is this.observer[methodName]), I would have expected that this is sufficient to know that f extends CallableFunction. Maybe I'm missing something.


Thanks for taking the time to look at this.

@MartinJohns
Copy link
Contributor

F could refer to multiple different methods at once. Then this wouldn't be sound at all. There are many issues about this.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Feb 23, 2021
@RyanCavanaugh
Copy link
Member

type Observer = {
    s(s: string): void;
    n(n: number): void;
}

const p = new Observable(null as any as Observer);
const key: keyof Observer = Math.random() > 0.5 ? "s" : "n";
// Passes a number to a string function half the time
p.observeShouldWork(key, 42);

See also #27808

@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@sigurdschneider
Copy link
Author

Thanks for taking a look. I was aware of the fact that F only upper-bounds the method name, which is why included the following code lines (which demonstrate the unsoundness problem you point out) in the TypeScript playground code I attached:

// The following is working as expected:
test.observeShouldWork<"foo" | "bar">("foo", true);

However, my bug report does not pertain to that problem: My bug report is about the failure of the type system to derive that f (which has type Observer[F]) in the function body of observe must denote a Function. I think that TypeScript should be able to derive that from the upper bound on F.

I just tried 4.3.0-dev.20210219 and the problem seems to persist. I also saw that Generalized Index Signatures are on the iteration plan for 4.3. Maybe this problem can be solved as part of that work?

@RyanCavanaugh
Copy link
Member

failure of the type system to derive that f (which has type Observer[F]) in the function body of observe must denote a Function

What you get here is a type that's assignable to Function, but is a deferred lookup time so doesn't act as a function in an expression position since we don't know what the actually-valid members of it are and can't provide generic deferred types in this manner.

You can alias it to Function if you want:

        const f: Function = this.observer[methodName];
        // OK
        f.apply(this.observer, args)

@sigurdschneider
Copy link
Author

Thanks for the clarification Ryan, your suggestion solved the problem. I thought that deferred lookup types were an implementation detail; the error message (Property 'apply' does not exist on type 'Observer[F]'.(2339)) also doesn't point to the problem (or how to solve it, for that matter).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants