David Gregory
David Gregory

Reputation: 73

Why doesn't this code cause a TypeScript type error?

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>.

Upvotes: 3

Views: 412

Answers (2)

basarat
basarat

Reputation: 276229

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.

Upvotes: 3

Brocco
Brocco

Reputation: 64853

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>>({});

Upvotes: 1

Related Questions