3

With interfaces defined like so:

interface IRemoteService {
  createRecord(record: RecordType): ng.IPromise<ICreateResponse<string>>;
}

interface ICreateResponse<T> {
  createdId: T;
}

Why doesn't the following code cause a Typescript compilation error?

class RemoteServiceMock implements IRemoteService {
  public static $inject = ["$q"];

  constructor(private $q: ng.IQService){
  }

  createRecord(record: RecordType): ng.IPromise<ICreateResponse<string>> {
    return this.$q.when({});
  }
}

The type of $q.when is when<T>(value: T): IPromise<T>.

6
  • 1
    Yes... "{}" is of type "Any" because "Any" is not a type... in that it is "any type". Commented Jun 3, 2015 at 15:21
  • Surely {} is of type Object? Commented Jun 3, 2015 at 15:24
  • Yes... but any type can be of type "any". Its the same as saying... "anything".... which "object" also satisfies... obviously. Commented Jun 3, 2015 at 15:27
  • What I meant by that was "Is {} treated like <any>{}", but you're right, that's probably not clear. Edited. Commented Jun 3, 2015 at 15:34
  • What is the definition of ICreateResponse? Commented Jun 3, 2015 at 15:45

2 Answers 2

3

This is according to spec. Here is your example simplified:

interface A{
}
interface B {
  createdId: string;
}

var foo:ng.IPromise<A>;
var bar:ng.IPromise<B>;
bar = foo; // No error

This assignment is allowed if A is a subtype of B or B is a subtype of A. If this is not the case you will get an error as shown below:

interface A {
  breakTypeCompat: number;
}
interface B {
  createdId: string;
}

var foo:ng.IPromise<A>;
var bar:ng.IPromise<B>;
bar = foo; // Error

The reason is the bivariance compatibility of function arguments. See this link for docs + reason why this is the way it is: https://github.com/Microsoft/TypeScript/wiki/Type-Compatibility#function-argument-bivariance

Details

Background

Type compatibility of the interfaces depends upon how you use them. E.g. the following is not an error :

interface IPromise<T>{  
}

interface A{
}
interface B {
  createdId: string;
}

var foo:IPromise<A>;
var bar:IPromise<B>;
bar = foo; // No error

However if the IPromise where to use the type parameter as a member it would error:

interface IPromise<T>{
    member:T    
}

interface A{    
}
interface B {
  createdId: string;
}

var foo:IPromise<A>;
var bar:IPromise<B>;
bar = foo; // Error

Therefore

In the actual promise definition we have something like:

interface IPromise<T> {
    then(successCallback: (promiseValue: T) => any): any;
}

interface A {
}
interface B {
    createdId: string;
}

var foo: IPromise<A>;
var bar: IPromise<B>;
bar = foo; // No Error

Since we are using T as an argument to the a function A and B will be type checked by bivariance. So if A is a subset of B or B is a subset of A they are compatible.

Sign up to request clarification or add additional context in comments.

2 Comments

The example above certainly works. Sorry to ask a question on top of a question, but why is that allowed? A does not implement B, so that would seem to damage the usefulness of interface membership quite a lot.
Thanks! Makes much more sense now. :)
1

I'm not certain why you're not getting an error, but I do have a suggestion on how to get a warning. According to angular.d.ts when is defined like this:

when<T>(value: IPromise<T>): IPromise<T>;
when<T>(value: T): IPromise<T>;
when(): IPromise<void>;

So if you'd like to use when with more typing then use:

return this.$q.when<ICreateResponse<string>>({});

1 Comment

Thanks, this did actually give me an error which is great.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.