Skip to content
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

Inconsistent property type inference #49463

Closed
tippfelher opened this issue Jun 9, 2022 · 6 comments
Closed

Inconsistent property type inference #49463

tippfelher opened this issue Jun 9, 2022 · 6 comments
Labels
Duplicate An existing issue was already created

Comments

@tippfelher
Copy link

Section A causes no TS error in the source code below.
Section B causes a TS error in the source code below.
Both sections do the same thing and section B shouldn't yield any errors. (?)
The message "Type 'boolean' is not assignable to type 'string'" also makes no sense because prop is clearly a boolean now. (?)

(?) means: I am pretty sure, but not entirely sure, as I am new to Typescript. ^^

type Template = {
  prop: number;
}

type Keys<Type> = keyof Type | (string & Record<never, never>);

type SomeType<T> = {
  [Property in Keys<T>]?: Property extends keyof T ? boolean : string;
};

// section A: no TS error
let something: SomeType<Template> = {};
something.prop = true;
something.anotherProp = 'value';

// section B: TS error
// Type '{ prop: true; anotherProp: string; }' is not assignable to type 'SomeType<Template>'.
//   Property 'prop' is incompatible with index signature.
//   Type 'boolean' is not assignable to type 'string'
something = {
  prop: true,
  anotherProp: 'value',
};

Here is the playground.

@RyanCavanaugh
Copy link
Member

Simpler version:

type Template = {
  prop: number;
}

type SomeType<T> = {
  [Property in keyof T]?: boolean;
} & { [s: string]: string };

// section A: no TS error
let something: SomeType<Template> = {};
something.prop = true;
something.anotherProp = 'value';

// section B: TS error
// Type '{ prop: true; anotherProp: string; }' is not assignable to type 'SomeType<Template>'.
//   Property 'prop' is incompatible with index signature.
//   Type 'boolean' is not assignable to type 'string'
something = {
  prop: true,
  anotherProp: 'value',
};

It seems like you're trying to emulate rest index signatures, which aren't really supported. See #7765.

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Jun 10, 2022
@tippfelher
Copy link
Author

That's very, very sad because this sounds like a very useful feature when working with types. (at least in my use cases ^.-)

So basically, in order to achieve this, I have to generate a separate file with type definitions from runtime information as relay does.

Thank you very much though.

@fatcerberus
Copy link

fatcerberus commented Jun 11, 2022

The problem with “rest” properties is, given you have { [x: string]: string, foo: number }, what should the type system do when given an arbitrary string key that happens (at runtime) to be "foo"? That’s not something the type system can verify at compile time, so TS doesn’t even try to model this case and instead treats the index signature as universal.

Note that you have to be very careful when using this pattern at runtime for the same reason - if your keys are dynamic (provided by the user, e.g.), you have to make sure they don’t overlap with the named ones.

@tippfelher
Copy link
Author

tippfelher commented Jun 11, 2022

I am not saying { [x: string]: string, foo: number } should work because this would obviously be bad design. But if you write it as

{
  [x in any string but 'foo']: string; // (pseudo syntax)
  foo: number;
}

then there would be no problem to determine the type for property baby or foo. (This prolly cannot be implemented in the current TS architecture)

Edit: I wanted to make use of the (non-existing, because I didn't know it better) rest feature with recursion but recursion doesn't seem to work even if rest properties were supported.

@fatcerberus
Copy link

Well, { foo: number } & { [x: string]: string } (roughly what you have in the OP) is basically the same type as { [x: string]: string, foo: number } and neither one is safe for the above reasons, is what I was getting at. Your pseudocode above would indeed be a solution, but there's currently no way to express "any string except 'foo'" - that would require Negated Types, which, judging by the fact that PR was recently closed, looks like have been taken off the table. 😢

@fatcerberus
Copy link

fatcerberus commented Jun 11, 2022

I am not saying { [x: string]: string, foo: number } should work because this would obviously be bad design.

Yep, I agree - but you'd be surprised how many people still want that exact type to work, and will argue vehemently with you when you try to tell them it's unsafe. It's refreshing to see someone who recognizes that. 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants