1

Consider the following class and a factory method with two implementations

class SomeClass {
  public someId: number;
  public someProp: string;
  constructor(input) {
    // do some internal stuff with input;
  }
}
function factory(singleInput: {}): SomeClass;
function factory(multipleInput: Array<{}>): Array<SomeClass>;
function factory(singleOrMultipleInput: {} | Array<{}>): SomeClass | Array<SomeClass> {
  if (singleOrMultipleInput instanceof Array) {
    // case multiple input: Array<{}>
    const arrayOfSomeClass = singleOrMultipleInput.map(ro => new SomeClass(singleOrMultipleInput)); // indeed is shown as SomeClass[]
    return arrayOfSomeClass;
  } else {
    // case single input: {}
    const singleSomeClass = new SomeClass(singleOrMultipleInput); // indeed is shown as SomeClass
    return singleSomeClass;
  }
}

In words: when calling factory with a {} as parameter (a plain object), I want it to return an instance of SomeClass. When calling factory with an array of objects, [{}, {}, {}], I want it to return a array of instances of SomeClass, like [SomeClass, SomeClass, SomeClass].

The snippet below shows that the TypeScript interpreted doesn't recognise it as such.

const singleInput: {} = {};
const multipleInput: Array<{}> = [{}, {}, {}];
let singleResult = factory({}); // is SomeClass, as expected
const multipleResult = factory([{}]); // no longer is SomeClass[] ?!
singleResult.someId = 123;
multipleResult.someId = 456; // the interpreter should flag this as wrong
singleResult = multipleResult; // also shouldn't be possible without an warning from the interpreter

However, within the factory method, arrayOfSomeClass and singleSomeClass are typed correctly. Why does multipleResult not show as SomeClass[] by the interpreter?

Follow up question: how can it be implemented such that the interpreter shows the types as I would expected? (preferably having the factory method as a static (generic?) method of SomeClass (I realise this can probably be achieved by avoiding overloading, but thats not what I'm looking for)

1 Answer 1

1

I'm not 100% sure, but seems instanceof checks must be done in same order as overloads.

function factory(multipleInput: Array<{}>): Array<SomeClass>;
function factory(singleInput: {}): SomeClass;
function factory(singleOrMultipleInput: {} | Array<{}>): SomeClass | Array<SomeClass> 

It is working as expected.

EDIT: compiler is taking first overload and is trying to validate it.

So it can pass to else { } block using array of objects, without erros, as for compiler Array is a case of {}.

ONE MORE UPDATE:

From this perspective it seems to be a bug.

function acceptsObjectType(obj:{}) {
  return obj;
}

let a = [];
a = acceptsObjectType(a);

But this is ok.

function acceptsObjectType(obj:{}) {
  return obj;
}

let a:{} = [];
a = acceptsObjectType(a);
Sign up to request clarification or add additional context in comments.

5 Comments

Jeez... you're right. That seems like a TypeScript bug. Going to wait for a little longer to see if someone can come up with a proper explanation as to why, if not I'll mark your answer as correct.
I don't see a TypeScript bug anywhere... what's the issue?
In typescript 2.9 I'm getting an error "Type {} is not assignable to type any[]", but type any[] is assignable to {}. So it is not a bug.
I feel that it shouldn't matter in what order you declare the overloads (your solution compared to my initial example). Might not be a bug, but (at least to me) it is definitely unexpected behaviour; afaik it wouldn't behave like this in e.g. C#. Thanks for the input y'all!
From the TypeScript handbook: "[The compiler] looks at the overload list, and proceeding with the first overload attempts to call the function with the provided parameters. If it finds a match, it picks this overload as the correct overload. For this reason, its customary to order overloads from most specific to least specific."

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.