-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Type guard question #1892
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
Comments
Any assignment to a variable effectively "turns off" type guards on that variable. |
Spec part:
|
Hmm interesting. Thanks for the quick replies. Looks like it happens here: Will think about it some more |
Consider something like this: function blah(x: string|number) {
// Coerce numbers to string
if(typeof x === 'number') {
x = x.toString();
}
// do something with x now that it's a string
} If we narrowed |
Makes sense but feel like there should be an alternative to: module foo {
export function on(event: string|Event) {
var evt: Event;
if (typeof event === "string") {
evt = new Event(event)
} else {
evt = event
}
}
class Event {
constructor(name: string) {}
}
} It's a pretty common pattern in JS. |
@jbondc Your code compiles well in the current TS 1.4: |
@RyanCavanaugh So widening on assignment 'By Design' is good This is really a different feature of adding a check at the end of an if block statement and keeping track of type assignments. Some ~ pseudo-code: module foo {
export function on(event: string|Event) {
if (typeof event === "string") {
event = new Event(String(event))
// on assignment, event type widens to 'string|Event' (good)
// *but* keep track of assigned type Event
/* guardTypeInfo.typeAssigns.push(Event) */
}
// checker.ts
// If all types in the set 'string|Event' have guard assignments of the same type,
// *narrow* to the assigned type
/*
if(guardTypeInfo) {
var countAssign = guardTypeInfo.typeAssigns.length;
if(guardTypeInfo.noElseClause && guardTypeInfo.unionType.types.length -1 == countAssign) {
var inferMissingType = ...
guardTypeInfo.typeAssigns.push(inferMissingType);
countAssign++
}
if(countAssign == guardTypeInfo.unionType.types.length) {
// If all types in 'guardTypeInfo.typeAssigns' are the same
narrowType = guardTypeInfo.typeAssigns[0];
}
}
*/
// event type is narrowed to 'Event'
console.log(event.target)
}
class Event {
constructor(name: string) {}
}
} A couple more examples: function blah(x: string|number) {
if(typeof x === 'number') {
x = x.toString();
// x widens to 'string|number
// x is assigned a type 'string'
}
// end of if block
// infer that missing else clause contains a guard assignment of type of 'string'
// the number of assigned types (2) [string,string] == union types (2) [number,string]
// narrow the type to 'string' since assignments the same [string,string]
}
function blah(x: string|number) {
if(typeof x === 'number') {
x = x.toString();
// x widens to string|number
// x is assigned a type 'string'
} elseif(typeof x === 'string') {
}
// narrow the type to 'string' since assignments the same [string,string]
}
function blah(x: string|number|boolean) {
if(typeof x === 'number') {
x = x.toString();
// x widens to string|number|boolean
// x is assigned a type 'string'
}
// number of assignments (2) [string,string] < (3) [string|number|boolean]
// type x is still string|number|boolean
}
function blah(x: string|number|boolean) {
if(typeof x === 'number') {
x = true;
// x widens to string|number|boolean
// x is assigned a type 'boolean'
} elseif(typeof x === 'string') {
x = false;
// x widens to string|number|boolean
// x is assigned a type 'boolean'
}
// end of if block
// infer that missing else clause contains a guard & assignment of type of 'boolean'
// the number of assigned types (3) [boolean|boolean|boolean] == union types (3) [string,number,boolean]
// narrow the type to 'boolean' since assignments the same [boolean,boolean,boolean]
}
function blah(x: string|number|boolean) {
if(typeof x === 'number') {
// x narrows to number
} elseif(typeof x === 'string') {
x = false;
// x widens to string|number|boolean
// x is assigned a type 'boolean'
}
// end of if block
// infer that missing else clause contains a guard & assignment of type of 'boolean'
// the number of assigned types (3) [number,boolean,boolean] == union types (3) [string,number,boolean]
// type x is still string|number|boolean since assignments not the same [number,boolean,boolean]
} Using a nullish guard: function blah(x: string|number|boolean) {
if(x == null) {
// x narrows to void type
// no assignments
x = null // ok
x = void 0 // ok
} elseif(typeof x === 'number') {
// x narrows to number
} elseif(typeof x === 'string') {
// x narrows to string
} else {
// x narrows to boolean
}
// end of if block
// the number of assigned types (3) == union types (3) [string,number,boolean]
// type x is still string|number|boolean since assignments not the same [number,string,boolean]
x = null // ok
x = void 0 // ok
} Using type constraints (related to #185): function blah(x: string|number|boolean) {
if(x == null) {
// x narrows to void type
// adds constraint x!null!undefined
x = null // error
x = void 0 // error
} elseif(typeof x === 'number') {
// x narrows to number!null!undefined
} elseif(typeof x === 'string') {
// x narrows to string!null!undefined
} else {
// x narrows to boolean!null!undefined
}
// type x is string|number|boolean & !null!undefined
x = null // error
x = void 0 // error
} Does this make sense? Can start with a partial patch. |
see #1769 |
Is this a bug, known issue?
Using the latest tsc from master, get the errors:
(a) error TS2345: Argument of type 'string | Event' is not assignable to parameter of type 'string'.
(b) error TS2339: Property 'target' does not exist on type 'string | Event'.
The text was updated successfully, but these errors were encountered: