Comments (14)
Please don't tell me that my stuff does not make sense, you know nothing about my code and you shouldn't pretend to do it.
Our actual types are something like this:
type FrontendEvent = { type: 'click'; data: {} } | { type: 'navigate'; data: {} }
type BackendEvent = { type: string; data: { __nominativeType: true } }
type Event = FrontendEvent | BackendEvent
function track(event: Event) {
sendToOurTracker(event);
}
and we want calls to succeed for all event types, but no one is actually looking inside the data after we have converted it to an Event
.
Can we work around this? Yes we can (for example by saying BackendEvent = { type: string & { __fixTypeScriptIssue: true }}
But still, as I wrote above: It doesn't make sense that there are any types T1 and T2 such that there is any value that is assignable to T1 but not to T1 | T2.
from typescript.
I don't think this is true, because if I remove the string case it is suddenly valid. IOW, this code produces no error, even though it would have the same issue with { foo: "bar" | "baz" }
not being assignable to { foo: "bar" } | { foo: "baz" }
.
type T = { foo: 'bar', data: {} } | { foo: 'baz', data: {} };
function f1(p: 'bar' | 'baz') {
ok({ foo: p, data: {}}) // This is accepted without issue
}
function ok(t: T) {
}
from typescript.
{ foo: "bar" | "baz" }
does appear to be considered structurally equivalent to { foo: "bar" } | { foo: "baz" }
. What of the fact that T.foo
is "bar" | "baz" | "string"
? I'd imagine the fact that that can simplify to string
could muddy the waters.
(T & {foo: 'bar' | 'baz'})['data']
resolves to {} | {x: number}
. I don't think that in theory you could exclude the {x: number}
. someT.foo
could have the value bar
or the value baz
and still be a { foo: string, data: { x: number } }
. This isn't a proper discriminated union.
from typescript.
Whether or not it is a "proper" discriminated union, both { foo: 'bar', data: {}
and { foo: 'baz', data: {} }
are valid values of the type. The reason I think it is a bug rather than a misfeature is that it seems to me the compiler gets confused by the (unrelated) string case.
from typescript.
It doesn't really make sense to me that there exist any types T1
and T2
such that there are values that are assingable to T1
but not to T1 | T2
from typescript.
@ahejlsberg Remember when I wrote this comment #57231 (comment) saying such types probably exist in the wild? Yeah, someone pick up that phone because I called it.
from typescript.
This type doesn't make any sense; there's no way to soundly access { x: number }
, so it's the same as
type T = { foo: string, data: { } }
TypeScript doesn't have negated types and efforts to mimic them will, of course, not succeed.
from typescript.
Please don't tell me that my stuff does not make sense, you know nothing about my code and you shouldn't pretend to do it.
You wrote code thinking it would work a certain way and it doesn't. This is because the type as written doesn't make sense under the rules of the type system. We have no doubt the code makes sense to you, but it doesn't make sense to TypeScript under the rules of the type system as designed. That's what Ryan is saying.
But still, as I wrote above: It doesn't make sense that there are any types T1 and T2 such that there is any value that is assignable to T1 but not to T1 | T2.
I don't know what you mean by this because there indeed is no such type. In fact that's the root of your problem: { foo: "bar" }
is also a legal { foo: string }
. so just knowing that typeof p.foo === 'string'
isn't enough information to rule out the first two cases, and thus TS won't do so because it would be unsound.
What you'd need for this to work properly is a way to express
type T = { foo: string & not "bar" & not "baz", data: { x: number } };
AKA negated types. #4196
from typescript.
This issue has been marked as "Not a Defect" and has seen no recent activity. It has been automatically closed for house-keeping purposes.
from typescript.
But still, as I wrote above: It doesn't make sense that there are any types T1 and T2 such that there is any value that is assignable to T1 but not to T1 | T2.
I don't know what you mean by this because there indeed is no such type.
There is:
type T1 = { foo: 'bar', data: {} } | { foo: 'baz', data: {} };
type T2 = { foo: string, data: { x: number } };
let x: 'bar' | 'baz' = (window as any).x;
const x1: T1 = { foo: x, data: {} }; // This is OK
const x2: T1 | T2 = { foo: x, data: {} }; // But this is not. So the literal is assignable to T1 but not to T1 | T2
@RyanCavanaugh this is the reason I reported it. I know what I'm doing is a little fishy, but there is a value that is assignable to a type T1
but not to T1 | T2
from typescript.
there is a value that is assignable to a type
T1
but not toT1 | T2
The problem is the type T1 | T2
is inherently ambiguous. You could use a type assertion before assigning (or any moral equivalent), but where there is otherwise no proper discriminant, Typescript can't distinguish whether your value is a T1
or a T2
, and thus can't validate it as one or the other. In order to satisfy the type, you need to satisfy all constituent union members.
from typescript.
From an ideal perspective, there is no question about whether the value satisfies T1 | T2
, it 100% does. Why? Because it satisfies T1
, and all types that satisfy a constraint T1
should (as @RyanCavanaugh agreed with earlier), satisfy the constraint T1 | T2
for all types T2
.
Is this worth fixing in TS? I don't know. But it absolutely means that TS deviates from an ideal constraint checker.
from typescript.
To be clear, this isn't an assignability issue. The value is assignable to the type T1 | T2
:
type T1 = { foo: string, data: { x: number } }
type T2 = { foo: 'bar', data: {} } | { foo: 'baz', data: {} }
type T = T1 | T2 ;
function ok(t: T) { }
declare const p: 'bar' | 'baz'
// Validation of the value
const value: T2 = { foo: p, data: {}};
// Assignability of the value
ok(value);
// Does not validate
const value2: T = { foo: p, data: {}};
Where you get hung up is before assignment... This is a validation issue.
Think like excess property checks, except there is a stronger case for an error here because the value can absolutely be construed as a malformed T1
. If you can't distinguish a T1
from a T2
, you can't soundly assume that what you have isn't a malformed T1
. The problem goes away when Typescript does not have to distinguish between a T1
and a T2
itself. And I'm not making the case that this is the most desirable behavior (typescript sometimes values practicality over soundness), but the logic is coherent.
It's not necessarily uncommon for a distinction represented in the types to correlate to another distinction not represented in the types (e.g. brand types). Accepting a value where you can't validly rule out a malformed T1
means an invalid value could move through your plumbing and back out and then violate an invariant of external code.
Is it practical? I don't know. I see the utility of the type you're trying to portray here, but there is still a possible logic error being identified, and a very subtle one at that, so while I might be irritated too if I were bit by this the way you were, ultimately I think this is the desirable behavior and I certainly couldn't find my way to labeling it as incorrect.
from typescript.
Thank you, that was a good explanation.
you can't soundly assume that what you have isn't a malformed T1
This is probably for the better, then. It's probably better to reject some valid programs than to accept more invalid ones.
from typescript.
Related Issues (20)
- A class with only getters satisfies some type without checking for the possibility of changing these fields HOT 2
- "Output generation failed" when transpiling "test.d.abc.ts" HOT 4
- Type literals in declaration files are emitted with comment blocks from the wrong source file
- Declaration emit of private properties should strip jsdoc
- ้่ฆไธไธชๆดไธบ็ฎๅ็ts HOT 2
- Possibly undefined type is not correctly detected in async code HOT 2
- checkJs finds hundreds of errors in .JS produced by TypeScript itself HOT 6
- TS7018 error without file and line number HOT 6
- JSDoc tags get lost when inheriting from a grandparent class
- Smarter String includes/endsWith/startsWith using template literal type predicates HOT 3
- Typed key accessor fails to infer type when used with generic HOT 2
- Upstream file not correctly recognized as output of other project when using path mappings HOT 9
- [nightly][regression] Wrong generic parameter inferred for constructed class instance HOT 1
- [nightly][regression] Some emitted imports are syntactically invalid HOT 5
- Array of numbers wrong sort HOT 9
- Mistake in README file
- Inconsistent Behavior with Equality Check Using `Pick<Readonly<T>, K>` in Mapped Type HOT 2
- Improve Omit field type check HOT 1
- Class parameter property with initializer before required property emits non-nullable parameter for declaration emit
- TypeScript emits incorrect type for setters in JSDoc
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. ๐๐๐
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google โค๏ธ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from typescript.