-
Notifications
You must be signed in to change notification settings - Fork 60
void and undefined confusion with incremental clutz #695
Comments
is legal JS code at runtime, and probably the intended use of this API, so I think returning Rather, I think this is just another case of inference where we should add an annotation on the subclass. |
If the input is: /** @interface */
var MyI;
/** @return {string | undefined} */
MyI.prototype.foo = function() {};
/** @constructor */
var MyClassThatImplementsI;
/** @return {undefined} */
MyClassThatImplementsI.prototype.foo = function() {
// no return;
}
exports.MyI = MyI;
exports.MyClassThatImplementsI = MyClassThatImplementsI; we still emit: declare namespace ಠ_ಠ.clutz.module$exports$bare$reexport {
class MyClassThatImplementsI extends MyClassThatImplementsI_Instance {
}
class MyClassThatImplementsI_Instance {
foo ( ) : void ;
}
interface MyI {
foo ( ) : string | undefined ;
}
}
declare module 'goog:bare.reexport' {
import alias = ಠ_ಠ.clutz.module$exports$bare$reexport;
export = alias;
} Because, we convert |
And to answer my question - "why this works in total mode" - it works because with plainly |
One way to solve this would be to add void to methods that have type unions that include undefined. Thus /** @return {string | undefined} */
MyI.prototype.foo Would be translated to: declare interface I {
f(): string | undefined | void;
} And implementing that interface with a void method would be fine. |
I have reservations because of this situation. Users might already have fn that accept declare interface I {
f(): string | undefined | void;
}
function someOtherF(x: string | undefined) {
}
function f(x: I) {
someOtherF(x.f()); // type error here
} |
Two other answers here are:
I am leaning towards the second answer. |
Fundamentally if you have the JS equivs of these in different compilation units:
Then there's no way we can rely on inference to pick the proper return type of |
Sorry, I am not following, isn't the correct type output: class A {
foo(): A|undefined {...}
}
class B extends A {
foo(): undefined;
} This is valid TS, a subclass can change the return type as long it is a subtype (functions are covariant in the return position). |
Ah you are right. It's only if foo has no return statement and we infer void where this will break. |
Yep, which means one answer could be add 'return undefined', which has the same downside that some of these cases are in libraries that oppose changes just so that they are consumed by clutz. |
The following snippet of closure:
When run over the class and interface separately produces:
That is an error in TypeScript, because 'void' is not 'undefined' assignable. One answer is to emit 'string | void' for the interface too. Generalizing, if the type is union in a return position it need to use 'void' instead of 'undefined'. Note that as far as I can tell closure does not differentiate void and undefined (they are pure type synonims).
The text was updated successfully, but these errors were encountered: