chateau
chateau

Reputation: 948

Typescript type declaration for a complex function that returns itself

I have the following javascript function that returns a function containing additional methods given in the object arguments.
I think the code will be more understandable than an explanation:

var scopeFunction = (object) => {

  // create main function if it doesn't exist
  if (!object.main) object.main = function(){
    alert("No main function for this scopeFunction");
  };

  // add all object keys to the main function that will be return
  _.each(object, function(d,i){ object.main[i] = d; });

  // return main function
  return object.main;

};

I want to define properly this code in typescript, this is what I did, but atom typescript (that's where I've tested it) throws errors when I try to access the keys of my returned function object. This is how my current code look:

// TYPES
namespace ScopeFunction {

  export interface Function<Obj extends MakerObject, Key extends keyof Obj>  {
    (): Obj["main"];
    [key: Key]: Obj[Key];
  }
  export interface MainFunction {
    (...args:any[]) : any;
  }
  export interface MakerObject {
    [key: string]: any;
    main?: MainFunction;
  }

  export type Maker = <Obj extends MakerObject, Key extends keyof Obj>(object:Obj) => ScopeFunction.Function<Obj, Key>;

}

// FUNC
var scopeFunction:ScopeFunction.Maker = (object) => {

  // create main function if it doesn't exist
  if (!object.main) object.main = function(){
    alert("No main function for this scopeFunction");
  };

  // add all object keys to the main function that will be return
  _.each(object, function(d,i){ object.main[i] = d; });

  // return main function
  return object.main;

};

// TEST
var test = scopeFunction({
  a: 1,
  b: "3",
  main: () => { console.log("testLog"); return 0; }
})

var test1 = test(); // WORKS OK
var test2 = test.main(); // ALERT: Property 'main' doesn't exist on type 'Function<{ a: number; b: string; main: () => number }, "main" | "a" | "b">'
var test3 = test.a; // ALERT: Property 'a' doesn't exist on type 'Function<{ a: number; b: string; main: () => number }, "main" | "a" | "b">'

Any idea where the problem is in my definition?

Upvotes: 1

Views: 971

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249666

There are several problems with your code:

  1. The definitions don't compile [key: Key]: Obj[Key] is not valid, and indexer argument must be either number or string (those and only those types are valid). You need to use a mapped type instead.
  2. (): Obj["main"] will not be a call signature of the same type as Obj["main"] is will be a function that returns whatever the property of main is.
  3. The type of the main function is too generic, and it will not preserve any argument types.

A solution that does what you expect might be:

namespace ScopeFunction {

    export type Function<Obj extends MakerObject<(...args: any[]) => any>> = Obj['main'] & {
        [P in keyof Obj]: Obj[P];
    }
    export interface MakerObject<TMain extends (...args: any[]) => any> {
        main?: TMain;
    }

    export type Maker = <TObj extends MakerObject<(...args: any[]) => any>>(object: TObj) => ScopeFunction.Function<TObj>;

}

// FUNC
var scopeFunction: ScopeFunction.Maker = (object) => {

    // create main function if it doesn't exist
    if (!object.main) object.main = function () {
        alert("No main function for this scopeFunction");
    };

    // return main function
    return Object.assign(object.main, object);
};


// TEST
var test = scopeFunction({
    a: 1,
    b: "3",
    main: (param: number) => { console.log("testLog"); return param; }
})

var test1 = test(10);
var test2 = test.main(10); 
var test3 = test.a;

Upvotes: 2

Related Questions