Repository navigation
Special bottom type #3076
Copy link
Copy link
Closed
Labels
CommittedThe team has roadmapped this issueThe team has roadmapped this issueFixedA PR has been merged for this issueA PR has been merged for this issueSuggestionAn idea for TypeScriptAn idea for TypeScript
Milestone
Description
I think that it should be special bottom type with the following properties:
- ( T | bottom ) = ( bottom | T ) = T — bottom is unit for type unions;
- expression of type bottom is assignable to any other type;
- bottom type could be used only as return type of the function (out-only);
- if function has return type bottom then all branches of the control flow either throw exceptions or return expression of type bottom;
- if all branches of the control flow either throw exceptions or return expression of type bottom and function has no explicit return type then bottom type inferred as the result type;
two functional types are not assignable to each other if exactly one of them has return type bottom(?);- bottom type can't be used as the actual type parameter for the generic type.
rationale
Let's consider the function that always throws:
function alwaysThrows() {
throw new Error("!");
}and other function that uses it
function test(a: boolean) {
return a ? 123 : alwaysThrow();
}We expect that test should have type number instead of ( number | void )
In more sophisticated example that involves Promise type declarations:
declare class Promise<T> {
catch<U>(
handler: (reason: any) => U | Promise<U>
): Promise<T | U>; // Note the union here (!)
then<U>(
fulfilled?: (result: T) => U | Promise<U>,
rejected?: (reason: any) => U | Promise<U>
): Promise<U>
}We expect that
promiseOfString.catch(function(e) {
throw new BusinessError(e);
});should have the type of Promise<string> instead of Promise<string | void>.
See petkaantonov/bluebird#589 (comment)
Possible explicit annotation that function always throws is using undefined as the type
function alwaysThrows(): undefined {
throw new Error("!");
}Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
CommittedThe team has roadmapped this issueThe team has roadmapped this issueFixedA PR has been merged for this issueA PR has been merged for this issueSuggestionAn idea for TypeScriptAn idea for TypeScript
Activity
Artazor commentedon May 7, 2015
It's obviously related to the #1613
jbondc commentedon May 8, 2015
Also related to removing 'void' from a union type and allowing its use as a type guard #1806
Still find it odd that number|void doesn't collapse to -> number.
Artazor commentedon May 8, 2015
I think that bottom !== void and we can not collapse number|void to number because it represents the state where value is either number or undefined, meanwhile the number means a guaranted number.
Um?
jbondc commentedon May 8, 2015
What do you mean? A number type can have values 'null' or 'undefined', a void type can have values 'null' or 'undefined'. A void type is clearly a subset of the number type, it's redundant in a union.
Artazor commentedon May 8, 2015
I think that you are wrong, but the explanation is tricky. This is the consequence of the "carefully corrupted" type system by Anders Hejlsberg (@ahejlsberg).
null and undefined values are assignable to the number not because of they are part of the numbers domain. They are assignable because of an explicit ad-hoc rule (their type is
any). The type system of the TypeScript purposely allows such assignments, and it is valid to expect thatwill fail when it is called with null or undefined.
type number is not a strict guarantee that the value is a number, it is a hint that in normal circumstances it should be a number. If you purposely pass the null value to it, then you probably know what you do. In opposite (number|void) says that it is normal to expect an "undefined" value.
Anders Hejlsberg (@ahejlsberg), am I right?
Artazor commentedon May 8, 2015
I think that it implicitly correlates with the
typeof nulldrama of the ES6.I'd say that
where "throw" statement is actually an expression of type bottom
spion commentedon May 8, 2015
Since TS has no model for exceptions in the type system, I think this workaround would be nice.
Other than the unions rule, I'm assuming it would behave the same as
void? i.e. if you get a value of typebottom(outside of a union), you cannot call any methods on it...I don't like
voidbeing used for this, as I'd prefer ifvoidgot its 1.4 meaning again (void | T = void)jbondc commentedon May 8, 2015
Anatoly Ressin (@Artazor) Not sure if there's a right or wrong about 'void' at the moment, it's just ambiguous:
We do agree there should be a bottom type, in my world I call it void!null!undefined.
type Bottom = void!null!undefined;Artazor commentedon May 8, 2015
Jon (@jbondc) your type exclusion syntax is very relevant (have you proposed the syntax itself anywehere?)
I'd say that
Nevertheles, both
nullandundefinedhas typeany. This is why they are assignable to any type.So they are not in the domain of the corresponding types. For them type checking is simply off.
Hard decision to type the
void <expression>asany(instead ofvoid) was made only for accommodating for commonvoid 0usage instead ofundefinedfor the purpose of initial value. (facepalm)UPD
unfortunately, it effectively kills the type checking in the following (otherwise idiomatic) simple lambda expression
for example
Has type
Promise<any>instead ofPromise<void>zpdDG4gta8XKpMCd commentedon May 8, 2015
what is wrong with defining alwaysThrow as follows:
it works immediately without requiring the presence of the bottom type which, unfortunately, needs runtime support from javascript to be what it is
Artazor commentedon May 9, 2015
Aleksey-Bykov hm?
I'm talking about purely static type inference.The primary goal for the bottom type is preventing pollution of union types when inferred implicitly.
If I understood your suggestion correctly you are talking about writing
This, unfortunately, kills the idea of type inference (or at least seriously affects its reputation).
Artazor commentedon May 9, 2015
spion (@spion)
Exactly. But you can pass this type to anywhere, because of in real life this passing will never happen because of the exception already thrown.
Here I don't understand. I think the rule of (void | T = void) is very dangerous and irrelevant. Can you elaborate why you ever thought that it would be useful?
I think that this discussion lacks people from the actual TS team, Mohamed Hegazy (@mhegazy)?
Artazor commentedon May 9, 2015
Anyway, now I start to think that bottom type should have no name at all. It should be inferred statically.
And if you want to to constrain your function explicitly that it should throw in every branch of the control flow you should write something like
This is a limited and the strongest form of the checked exceptions which states that the function always throws. And it can be implemented without checked exceptions machinery at all - it simply needs the bottom type internally.
Artazor commentedon May 10, 2015
Jon (@jbondc) excluding would be nice, also I would highly anticipate the intersection type, for the immediate intersection of interfaces:
In fact, I would be happy if TypeScript would have the following type structure:
(I've mentioned this already in petkaantonov/bluebird#589)
The Type System
of my dream for TypeScript
τ ::= tcon | α | (τ τ) | (Λα.τ) | (τ → τ) | (τ × τ) | ⊤ | ⊥ | (τ ⋂ τ) | (τ ⋃ τ) | (!τ) | (τ ^ τ)
where
Either,Maybe,Promise,Intetc.;Maybe Int;any);This system has usual parentheses elimination conventions and two aliases:
Checked exceptions (τresult ^ τerror) have two simple properties:
that makes (τ ^ τ) associative. (there are more rules, of course)
In fact if there will be interest I can write down all the sematic rules with typing context in strict algebraical format (using ASF+SDF or Rascal)
jbondc commentedon May 14, 2015
Anatoly Ressin (@Artazor) Do you have examples of use cases for type negation? Never seen that before, which programming language?
jbondc commentedon May 14, 2015
I guess:
It doesn't seem too difficult to implement, you just pluck it out of your set of union types.
spion commentedon May 14, 2015
Anatoly Ressin (@Artazor) I meant
void | T = voidin terms of supported methods / operations. With a type guardval != nullweswigham commentedon May 10, 2016
The
bottomtype described in this thread is very close to describing the correct behavior of thenothingtype which is now surfaced as a result of control flow analysis, IMO.Artazor commentedon May 10, 2016
Wesley Wigham (@weswigham) exactly! The bottom type was proposed to reflect the result of the control flow analysis, and surely could not be implemented without proper CFA, which now is available. Isn't it?