-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Add tags for the family of Observable* types to simplify typechecking #1474
Conversation
// cc @mhegazy |
@Blesh and @kwonoj will probably want you to squash to one commit 😄 With this change things like the following will work as expected? let o: Observable<number> = new Subject<number>(); It's only scenarios where you do not define the input type as part of the constructor, for example: let o: Observable<number> = new Subject(); That makes sense, because we're "pretending" Only question I have is would there be any benefit / negative of using the interface syntax here instead? (Besides it being a little more verbose) export interface FromEventObservable<T, R> {
' tag_class_FromEventObservable': [T, R];
}
export class FromEventObservable<T, R> extends Observable<T> {
... |
|
Fair enough, I just wasn't sure if it would be a plus or a minus (sounds like a minus!) |
... for users that happen to be using TypeScript. Everyone else will be fine. It sounds okay. Two things I can see:
Other than that, it looks good. Thanks for generous contribution @vladima |
@vladima just to clarify, this speeds up compilation because the key starts with a space and when structurally comparing the shapes, TypeScript does so by comparing the properties sorted alpha-numerically + spaces, so including a space as the first characters of these tag properties, |
In pseudo code: (I realize the typesystem works on a meta level, so it's not runtime like this) function typesAreEqual(a, b) {
const keys = keys(a)
.concat(
keys(b)
)
.removeDuplicates()
.sort();
keys.forEach(key => {
if (typeof a[key] === typeof b[key]) {
return false;
}
});
return true;
}
class A {
' first': number;
second() {}
third() {}
}
class B {
' first': boolean;
second() {}
third() {}
}
let a = new A();
let b = new B();
typesAreEqual(a, b);
// false |
compiler processes properties in declaration order. Space in the name is a (kind of hacky) way to hide this property in the completion list - currently we don't include members with such names in the completion list. |
what is the expected shape of |
@vladima something like: interface ISubscribable<T> {
subscribe: (observer: Observer<T>) => Subscription;
} |
so |
yes, it works. I've made this change and tried repro cases that we have - results are just marginally worse than with tagged types. |
#1475 introduces |
cc @IgorMinar as this will be a breaking change on our end if i'm reading @vladima's OP properly |
I think that this PR will be closed in favor of #1475 that does not introduce any breaks |
Yup, closed in favor of #1475 |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Currently shape of
Observable
class and its derived classes is quite complicated. As a consequence it takes a long time for a TypeScript compiler to determine relations between them based on only its structure. This PR adds a unique tag property for every class, type of property reflects the set of type parameters of the class. Presence of these properties has following effects:A
is not a subtype ofB
ifA
does not have tag forB
A
is a subtype ofB
use types of tags to check relations between type arguments.I've used this example to measure the actual impact of this change.
Notes:
.d.ts
files but don't have any runtime impact since they are not initialized.fix is fairly trivial
Reason: since type parameter appear only in function parameters they were treated as bivariant (
A<T1>
is assignable toA<T2>
if eitherT1
is assignable toT2
orT2
is assignable toT1
) which is technically unsafe.Tags keep only covariant step (
A<T1>
is assignable toA<T2>
ifT1
is assignable toT2
) so now these code will be rejected by the compiler.