Try it out: npm install yortus-typescript-typeof
View the diff: here.
TypeScript's type inference covers most cases very well. However there remain some situations where there is no obvious way to reference an anonymous type, even though the compiler is able to infer it. Some examples:
// Mapping to a complex anonymous type. How to reference the element type?
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
function checkItem(item: typeof data[0] /* ERROR */) {...}
// A statically-typed dictionary. How to reference a property type?
var things = { 'thing-1': 'baz', 'thing-2': 42, ... };
type Thing2Type = typeof things['thing-2']; // ERROR
// A strongly-typed collection with special indexer syntax. How to reference the element type?
var nodes = document.getElementsByTagName('li');
type ItemType = typeof nodes.item(0); // ERROR
// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
class MyAPI {
constructor(http) {...}
foo() {...}
bar() {...}
static id = id;
}
return new MyAPI($http);
}
function augmentAPI(api: MyAPI /* ERROR */) {...}
// Declare an interface DRY-ly and without introducing extra type names
interface MyInterface {
prop1: {
big: {
complex: {
anonymous: { type: {} }
}
}
},
// prop2 shares some structure with prop1
prop2: typeof MyInterface.prop1.big.complex; // ERROR
}
One example is declaring a function that takes an anonymous type as a parameter. We need to reference the type somehow in the parameter's type annotation, otherwise the parameter will have to be typed as any
.
Declare a dummy variable with an initializer that infers the desired type without evaluating the expression (this is important because we don't want runtime side-effects, just type inference). For example:
let dummyReturnVal = null && someFunction(0, ''); // NB: someFunction is never called!
let ReturnType = typeof dummyReturnVal; // Now we have a reference to the return type
This workaround has a few drawbacks:
dummyReturnValue
)_(NB: This solution was already suggested in #4233, but that issue is tagged 'Needs Proposal', and there are several other closely related issues, hence this separate issue.)_
Allow typeof
's operand to be an arbitrary expression. This is already allowed for typeof expr
in a value position like if (typeof foo() === 'string')
. But this proposal also allows an arbitrary expression when typeof
is used in a type position as a type query, eg type ElemType = typeof list[0]
.
This proposal already aligns closely with the current wording of the spec:
Type queries are useful for capturing anonymous types that are generated by various constructs such as object literals, function declarations, and namespace declarations.
So this proposal is just extending that usefulness to the currently unserved situations like in the examples above.
The semantics are exactly as already stated in the spec 4.18.6:
The 'typeof' operator takes an operand of any type and produces a value of the String primitive type. In positions where a type is expected, 'typeof' can also be used in a type query (section 3.8.10) to produce the type of an expression.
The proposed difference relates to section 3.8.10 quoted below, where the struck-through text would be removed and the bold text added:
A type query consists of the keyword typeof followed by
an expression. The expression is restricted to a single identifier or a sequence of identifiers separated by periods. The expression is processed as an identifier expression (section 4.3) or property access expression (section 4.13)a unary expression, the widened type (section 3.12) of which becomes the result. Similar to other static typing constructs, type queries are erased from the generated JavaScript code and add no run-time overhead.
A point that must be emphasized (which I thought was also in the spec but can't find it) is that type queries do not evaluate their operand. That's true currently and would remain true for more complex expressions.
This proposal doesn't introduce any novel syntax, it just makes typeof
less restrictive in the types of expressions it can query.
// Mapping to a complex anonymous type. How to reference the element type?
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
function checkItem(item: typeof data[0]) {...} // OK: item type is {raw:number, square:number}
// A statically-typed dictionary. How to reference a property type?
var things = { 'thing-1': 'baz', 'thing-2': 42, ... };
type Thing2Type = typeof things['thing-2']; // OK: Thing2Type is number
// A strongly-typed collection with special indexer syntax. How to reference the element type?
var nodes = document.getElementsByTagName('li');
type ItemType = typeof nodes.item(0); // OK: ItemType is HTMLLIElement
// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
class MyAPI {
constructor(http) {...}
foo() {...}
bar() {...}
static id = id;
}
return new MyAPI($http);
}
type MyAPI = typeof myAPIFactory(null, 0); // OK: MyAPI is myAPIFactory's return type
function augmentAPI(api: MyAPI) {...} // OK
// Declare an interface DRY-ly and without introducing extra type names
interface MyInterface {
prop1: {
big: {
complex: {
anonymous: { type: {} }
}
}
},
// prop2 shares some structure with prop1
prop2: typeof (<MyInterface>null).prop1.big.complex; // OK: prop2 type is {anonymous: {type: {}}}
}
Against: Poor syntax aesthetics. Alternative syntaxes addressing individual cases have been suggested in #6179, #6239, #4555 and #4640.
For: Other syntaxes may look better for their specific cases, but they are all different from each other and each only solve one specific problem. This proposal solves the problems raised in all those issues, and the developer doesn't need to learn any new syntax(es).
Against: An expression in a type position is confusing.
For: TypeScript already overloads typeof
with two meanings, as a type query it already accepts an expression in a type position and gets its type without evaluating it. This just relaxes the constraints on what that expression can be so that it can solve the problems raised in this issue.
Against: This could be abused to write huge long multi-line type queries.
For: There's no good reason to do that in a type query, but there are good reasons to allow more complex expressions. This is basically Martin Fowler's enabling vs directing.
This is a purely backward-compatible change. All existing code is unaffected. Using the additional capabilities of typeof
is opt-in.
Looking at the diff you can see the changes are very minor. The compiler already knows the types being queried, this just surfaces them to the developer. I would expect negligable performance impact, but I don't know how to test this.
I have set up VS Code to use a version of TypeScript with this proposal implemented as its language service, and all the syntax highlighting and intellisense is flawless as far as I have tested it.
.d.ts
filestypeof
's operand could be any expression, including an IIFE, or a class expression complete with method bodies, etc. I can't think of any reason to do that, it's just no longer an error, even inside a.d.ts
file (typeof
can be used - and is useful - in ambient contexts). So a consequence of this proposal is that "statements cannot appear in ambient contexts" is no longer strictly true.
The compiler seems to already have all the logic in place needed to deal with things like this:
function foo<X,Y>(x: X, y: Y) {
var result: typeof foo(x, y); // ERROR: 'result' is referenced in its own type annotation
return result;
}
It is not ambiguous; it picks the overload that matches the query's expression:
declare function foo(a: boolean): string;
declare function foo(a: number): any[];
type P = typeof foo(0); // P is any[]
type Q = typeof foo(true); // Q is string
For anyone wanting a quick way to play with this in VS Code with intellisense etc, here is a playground repo.
type P = typeof foo(0); // P is any[]
type Q = typeof foo(true); // Q is string
I think using types as argument instead of values is a more valid syntax.
type P = typeof foo(number); // P is any[]
type Q = typeof foo(boolean); // Q is string
It is more clear that the function is not being called, because you provide types and not values as arguments. The other point, is it is less ambiguous. Some people will use typeof foo(false)
, whereas some people will use typeof foo(true)
. If you have types as arguments people can only write typeof foo(boolean)
.
@tinganho exactly!
Though we still can write typeof foo("abc")
with #5185
here "abc"
is the singleton string type
@tinganho I've been giving your idea some thought and I see some things I prefer about this proposal, and other things I prefer about your suggestion. Your suggestion is good for the reasons you gave (simpler clearer syntax, less ambiguous, looks less like a function call). The thing I prefer about my proposal is that it doesn't introduce any novel syntax, so does not add any complications to the parser/checker, and it also supports more complex scenarios where you don't have simple type names for the arguments.
I was thinking, what if there was a very shorthand way of writing something like your foo(number)
syntax but using the existing expression parsing mechanics? So as an experiment I've introduced a new expression: _unary as_. You can just write as T
and that's shorthand for (null as T)
. You are basically saying, _'I don't care about the value but I want the expression to have type X'_.
With this change (which I've implemented in the playground repo), you can write something much closer to your suggested syntax, but it is still parsed as an ordinary expression:
type P = typeof foo(as number); // P is any[]
type Q = typeof foo(as boolean); // Q is string
let prop2: typeof (as MyInterface).prop1.big.complex; // prop2 type is {anonymous: {type: {}}}
This was just a quick experiment. An equivalent syntax could be (but I haven't implemented this):
type P = typeof foo(<number>); // P is any[]
type Q = typeof foo(<boolean>); // Q is string
let prop2: typeof (<MyInterface>).prop1.big.complex; // prop2 type is {anonymous: {type: {}}}
The second syntax might be called a _nullary type assertion_, with the expression <T>
being shorthand for (<T> null)
.
@yortus, we spent some time last week talking about this proposal. sorry for not posting earlier. the consensus was 1. we have a problem of not being able to refer to some types, e.g. return of a function or instance type of a class expression. and 2. adding expressions in a type position is not something we are comfortable with.
@tinganho's proposal was one that we talked about as well. i think it is more palatable, though would probably be more complicated to implement. Adding a new unary operator or using cast syntax is not really elegant as just using the type names.
Discussed for quite a while at the slog today. "Accepting PRs" here is "Accepting PRs assuming the implementation doesn't turn out to be too crazy"
@tinganho 's proposal looks pretty good (relative to the other options, at least) and we'd like to see a tentative PR that implements this.
The tricky thing is that we don't want to have a completely separate codepath for resolving the return type of f(number)
vs f(0)
, but the overload resolution algorithm is totally baked-in with the assumption that it's working with a set of _expressions_ rather than a set of _types_. But we think with a little trickery this should be straightforward.
Basic plan of attack would be:
typeof
to allow things that look like function calls, property access, and indexed property accessparseType
function. This is going to create a TypeNode
, but set a flag on the node that indicates that it was a type parsed in the context of a type querycheckExpression
checks for this flag and calls getTypeFromTypeNode
instead of normal expression processing@mhegazy, @RyanCavanaugh not sure how many corner cases the team discussed, so can I bring up a few here for clarification? I've listed a bunch of examples below and commented each one with what I think should be the result of the typeof
operation, with question marks on questionable cases.
var data = [1, 2, 3];
const LOBOUND = 0;
type Elem1 = typeof data[0]; // number
type Elem2 = typeof data[999999]; // number or ERROR?
type Elem3 = typeof data[1+2]; // ERROR or number?
type Elem4 = typeof data[LOBOUND]; // ERROR or number?
var tuple: [number, string] = [123, 'abc'];
type Elem4 = typeof tuple[0]; // number or number|string?
type Elem5 = typeof tuple[1]; // string or number|string?
type Elem6 = typeof tuple[999999]; // number|string or ERROR?
const ABC: 'a-b-c' = 'a-b-c';
let dict = { 'a-b-c': 123, 'd-e-f': true };
type Prop1 = typeof dict['a-b-c']; // number
type Prop2 = typeof dict['d-e-f']; // boolean
type Prop3 = typeof dict[ABC]; // ERROR or number or any?
// A simple function
declare function f1(n: number): string[];
type Ret1 = typeof f1(number); // string[]
type Ret2 = typeof f1(0); // ERROR or string[]?
// An asynchronous function that either accepts a callback or returns a Promise
declare function f2(n: number): Promise<string[]>;
declare function f2(n: number, cb: (err?: any, result?: string[]) => void): void;
type Ret3 = typeof f2(number); // Promise<string[]>
type Ret4 = typeof f2(number, any); // void
type Ret5 = typeof f2(number, Function); // ERROR: Function not assignable to callback
type Ret6 = typeof f2(number, (err: any, result: string[]) => void); // void
type Ret7 = typeof f2(number, (...args) => any); // void
// A special function-like object
interface Receiver {
(data: string[]): void;
transmogrify(): number[];
}
declare function f3(n: number, receiver: Receiver): Promise<void>;
declare function f3(n: number, callback: (err?: any, result?: string[]) => void): void;
type Ret8 = typeof f3(number, Receiver); // Promise<void>
type Ret9 = typeof f3(number, any); // ambiguous? or picks first overload?
type Ret10 = typeof f3(number, Function); // ERROR
type Ret11 = typeof f3(number, (...args) => any); // void since not assignable to Receiver
// A function with parameter destructuring
interface CartesianCoordinate {/***/}
interface PolarCoordinate {/***/}
declare function f4({ x: number, y: number }): CartesianCoordinate;
declare function f4({ r: number, t: number }): PolarCoordinate;
type Ret12 = typeof f4(any); // ambiguous? or picks first overload?
type Ret13 = typeof f4({x;y}); // CartesianCoordinate
type Ret14 = typeof f4({r;t}); // PolarCoordinate
type Ret15 = typeof f4({x;r;t;y}); // ambiguous? or picks first overload?
// Type-ception: is there anything wrong with typeof-in-typeof?
declare function f5(n: number, receiver: Receiver): Promise<void>;
declare function f5(n: number, callback: (err?: any, result?: string[]) => void): void;
function myCallback(err, result) {/***/}
var myReceiver: Receiver;
type Ret16 = typeof f5(number, typeof myReceiver); // Promise<void>
type Ret17 = typeof f5(number, typeof myCallback); // void
That is, the typeof (<MyInterface> null).prop1.big.complex;
examples above.
I take it from above comments that this is out of scope and will not be supported. Is that correct?
var data = [1, 2, 3];
const LOBOUND = 0;
type Elem1 = typeof data[0]; // number
type Elem2 = typeof data[999999]; // number
type Elem3 = typeof data[1+2]; // ERROR, only literals allowed here
type Elem4 = typeof data[LOBOUND]; // number when const resolution is done, otherwise any
var tuple: [number, string] = [123, 'abc'];
type Elem4 = typeof tuple[0]; // number
type Elem5 = typeof tuple[1]; // string
type Elem6 = typeof tuple[999999]; // number|string
const ABC: 'a-b-c' = 'a-b-c';
let dict = { 'a-b-c': 123, 'd-e-f': true };
type Prop1 = typeof dict['a-b-c']; // number
type Prop2 = typeof dict['d-e-f']; // boolean
type Prop3 = typeof dict[ABC]; // number when const resolution work is done, otherwise any
// A simple function
declare function f1(n: number): string[];
type Ret1 = typeof f1(number); // string[]
type Ret2 = typeof f1(0); // error, 0 is not a type
// An asynchronous function that either accepts a callback or returns a Promise
declare function f2(n: number): Promise<string[]>;
declare function f2(n: number, cb: (err?: any, result?: string[]) => void): void;
type Ret3 = typeof f2(number); // Promise<string[]>
type Ret4 = typeof f2(number, any); // void
type Ret5 = typeof f2(number, Function); // ERROR: Function not assignable to callback
type Ret6 = typeof f2(number, (err: any, result: string[]) => void); // void
type Ret7 = typeof f2(number, (...args) => any); // void
// A special function-like object
interface Receiver {
(data: string[]): void;
transmogrify(): number[];
}
declare function f3(n: number, receiver: Receiver): Promise<void>;
declare function f3(n: number, callback: (err?: any, result?: string[]) => void): void;
type Ret8 = typeof f3(number, Receiver); // Promise<void>
type Ret9 = typeof f3(number, any); // picks first overload
type Ret10 = typeof f3(number, Function); // ERROR
type Ret11 = typeof f3(number, (...args) => any); // void since not assignable to Receiver
// A function with parameter destructuring
interface CartesianCoordinate {/***/}
interface PolarCoordinate {/***/}
declare function f4({ x: number, y: number }): CartesianCoordinate;
declare function f4({ r: number, t: number }): PolarCoordinate;
type Ret12 = typeof f4(any); // picks first overload
type Ret13 = typeof f4({x;y}); // CartesianCoordinate
type Ret14 = typeof f4({r;t}); // PolarCoordinate
type Ret15 = typeof f4({x;r;t;y}); // picks first overload
// Type-ception: is there anything wrong with typeof-in-typeof?
declare function f5(n: number, receiver: Receiver): Promise<void>;
declare function f5(n: number, callback: (err?: any, result?: string[]) => void): void;
function myCallback(err, result) {/***/}
var myReceiver: Receiver;
type Ret16 = typeof f5(number, typeof myReceiver); // Promise<void>
type Ret17 = typeof f5(number, typeof myCallback); // void
I'm curious what happens here:
const number = "number";
type Ret3 = typeof f2(number); // What happens here?
@SaschaNaz good question. A similar situation:
class MyClass { foo; bar; }
declare function f(inst: MyClass): number;
type Ret = typeof f(MyClass); // number (presumably)
In this case it makes sense in typeof f(MyClass)
for the MyClass
_type_ to be considered before the MyClass
_value_ (ie the constructor function). The former leads to Ret = number
, the latter would lead to something like error: MyClass is not a type
.
Would the same logic apply to a name that referred to both a _type_ and a _const value_? In your example that would mean the type number
would always take precedence over the const value number
. Any thoughts @RyanCavanaugh?
Right, we'd resolve this under the usual semantics of a type expression (as if you had written var x: [whatever]
). So you could have typeof f(MyClass)
referring to invoking f
with the instance side, and typeof f(typeof MyClass)
referring to invoking f
with the constructor function.
So then @SaschaNaz's example unambiguously refers to number
as a _type_, not as the _const value_, right?
const number = "number";
type Ret3 = typeof f2(number); // Promise<string[]>
@RyanCavanaugh can you confirm that the third group of use-cases is out of scope? e.g. from the OP:
// Declare an interface DRY-ly and without introducing extra type names
interface MyInterface {
prop1: {
big: {
complex: {
anonymous: { type: {} }
}
}
},
// prop2 shares some structure with prop1
prop2: typeof (<MyInterface>null).prop1.big.complex; // OK: prop2 type is {anonymous: {type: {}}}
}
This use-case (with whatever syntax) will not be supported at this time, is that right?
I thint this should be coverd by allowing this
expressions.
prop2: typeof this.prop1.big.complex;
I think the const
thing should be resolved by another typeof
.
type Ret3 = typeof f2(typeof number); // typeof number is string so error here
... while this would block typeof data[LOBOUND]
.
@mhegazy that's a great idea re typeof this
. I just realised this already works in the forked implementation I made for this proposal. Well, it works for classes. For interfaces there is no error, but the this
type is always inferred as any
. Current output from the forked impl:
class MyClass {
prop1: {
big: {
complex: {
anonymous: { type: {} }
}
}
};
prop2: typeof this.prop1.big.complex; // prop2 type is {anonymous: {type: {}}}
}
interface MyInterface {
prop1: {
big: {
complex: {
anonymous: { type: {} }
}
}
};
prop2: typeof this.prop1.big.complex; // prop2 type is any
}
Is inferring this
inside interface declarations a possibility or would this feature remain limited to classes?
I want to make two points about #6179 and Angular.
// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
class MyAPI {
constructor(token: string) {...}
foo() {...}
bar() {...}
static id = id;
}
return MyAPI;
}
type MyAPIConstructor = typeof myAPIFactory(null, 0); // OK: MyAPI is myAPIFactory's return type
function augmentAPI(api: MyAPIConstructor) {...} // OK
The number of parameters can be big. Let's say 15 parameters. At the same time there is no overloads, and it's only the overloads that the parameters in typeof
are needed for. So for this case, can we probably think of a syntax like following?
type MyAPI = typeof myAPIFactory(...);
The factory function isn't usually assigned to an own global variable. A function expression is used:
angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) { /*...*/ });
That's what it usually looks like. As can be seen, typeof
can't be used here at all.
@thron0 my intuition is that a thing that has more than a few parameters but _also_ returns an anonymous type is going to be very rare. What do you think?
It'll become rare when Angular 1 becomes rare, not earlier.
Basically, what you described is what a typical Angular _factory_ is:
a thing that has more than a few parameters but also returns an anonymous type
It's a function that takes dependencies as its parameters and creates an instance of a _service_. You could think that a big number of parameters can be a hassle, but the thing is those factories are never called directly. The DI container calls them. The factory is called when something requires its return value (the service) as a dependency. And it's called only once as services are always singletons in Angular. However, a service can be anything, so if we need a non-singleton behavior, the factory can return a constructor (or a factory function). Just like in this code example:
angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) {
class MyAPI {
constructor(token: string) {...}
foo() {...}
bar() {...}
static id = id;
}
return MyAPI;
});
That's what my current workaround for this situation looks like:
class MyAPI {
// dependencies (1)
protected $http: HttpSvc;
protected id: number;
constructor(token: string) {...}
foo() {...}
bar() {...}
// Static properties cannot be used with this approach because:
// 1) this class is a global variable so it can be shared by different
// instances of the DI container,
// 2) the id property isn't available here as it's initialized in the subclass
//// static id0 = id;
}
angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) { // (2)
return class extends MyAPI {
$http = $http; // (3)
id = id;
};
});
// this type is needed for type annotations in the services that require MyAPI as a dependency
type MyAPIConstructor = typeof MyAPI;
angular.module('app').factory('someOtherService', function(MyAPI: MyAPIConstructor) {
var api = new MyAPI('qwerty');
/* ... */
});
As you can see, it's totally ugly and painful. I have to list the dependencies three times. If I have more dependencies, it's sometimes simpler to write the class in the usual way (inside the factory function) and to declare an interface for its consumers.
Hi, I want to propose to leave the typeof syntax/semantics intact and instead implement a simple type access algebra:
Let's imagine the type syntax extension:
type ::= ... | literalType | type typeAccess | guarded
guarded ::= "type" id
literalType ::= stringLiteral | numberLiteral | symLiteral | booleanLiteral
typeAccess ::= typeField | typeSubscript | typeCall
typeField ::= "." id
typeSubscript ::= "[" type "]"
typeCall ::= "(" [type {"," type}] ")"
Each identifier in the scope can be simultaneously bound to the two entities:
the type guard extracts only the former. Thus
class A {}
var a: A;
is equivalent to
class A {}
var a: type A;
in this case if we'd have a class
class ABC {
a: number;
b(x: number): string;
c:string[];
d:[number, string];
}
then we could write
var a: (type ABC).a; // number
var b: (type ABC).b(number); // string
var c: (type ABC).c[number]; // string
var d: (type ABC).c[0]; // number
It also covers merging _type_ entities and _value_ entities under the same identifier in lexical scope.
interface SAME {
x: number;
}
namespace SAME: {
export type x = boolean;
export var x: string;
}
var a: SAME.x // boolean
var b: (type SAME).x // number
var b: (typeof SAME).x // string
@RyanCavanaugh, @sandersn can you examine this idea?
How do you capture type of expression with your proposal?
On Thu, 19 May 2016 12:26 Anatoly Ressin, [email protected] wrote:
Hi, I want to propose to leave the typeof syntax/semantics intact and
instead implement a simple type access algebra:Let's imagine the type syntax extension:
type ::= ... | literalType | type typeAccess | guarded
guarded ::= "type" id
literalType ::= stringLiteral | numberLiteral | symLiteral | booleanLiteral
typeAccess ::= typeField | typeSubscript | typeCall
typeField ::= "." id
typeSubscript ::= "[" type "]"
typeCall ::= "(" [type {"," type}] ")"Each identifier in the scope can be simultaneously bound to the two
entities:
- compile time type
- run time value
the _type_ guard extracts only the former. Thus
class A {}
a: Ais equivalent to
class A {}
a: type Ain this case if we'd have a class
class ABC {
a: number;
b(x: number): string;
c:string[];
d:[number, string];
}then we could write
var a: (type ABC).a; // numbervar b: (type ABC).b(number); // stringvar c: (type ABC).c[number]; // stringvar d: (type ABC).c[0]; // number
It also covers merging _type_ entities and _value_ entities under the
same identifier in lexical scope.interface SAME {
x: number;
}namespace SAME: {
export type x = boolean;
export var x: string;
}
var a: SAME.x // booleanvar b: (type SAME).x // numbervar b: (typeof SAME).x // string@RyanCavanaugh https://github.com/RyanCavanaugh, @sandersn
https://github.com/sandersn can you examine this idea?—
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub
https://github.com/Microsoft/TypeScript/issues/6606#issuecomment-220378019
Your example reminds me of that a typeof a dotted expression and type property type #1295 is quite similar proposal. Except one is more static the other one more dynamic.
I like the idea of skipping typeof keyword too.
prop2: this.prop1.big.complex;
@mhegazy in your example you are using typeof
in a this-expression
:
prop2: typeof this.prop1.big.complex;
Since we can do this today:
someProp: this;
In my view, an extrapolation of the syntax above for more dotted properties is:
someProp: this.prop1.prop2.prop3;
And not:
someProp: typeof this.prop1.prop2.prop3;
And to just continue with the extrapolation — why not skip typeof all together?
someProps: foo(number)
why not skip typeof all together?
One reason I can think of is that it would become ambiguous as to whether you are referencing the instance side of a class or the static side:
class C {}
// Does 'x' have the instance type of 'C',
// or does it have the shape of its constructor?
let x: C;
I'd like to reference the type of a property in an interface within another interface, too. Posted my example as #10588 which is now closed in favor of this ticket.
It would be nice to write something like that:
interface Foo {
bar: string;
}
interface Box<T> {
container: T;
}
interface FooWithBoxedProps {
bar: Box<Foo.bar>; // OR: bar: Box<typeof Foo.bar>;
}
which would just translate to this:
interface Foo {
bar: string;
}
interface Box<T> {
container: T;
}
interface FooWithBoxedProps {
bar: Box<string>;
}
Well some of the previously mentioned scenarios can now be expressed with _indexed access types_ (#11929). Just combine this feature with standard typeof
expression and you can get something like this:
interface MyInterface {
prop1: {
big: {
complex: {
anonymous: { type: {} }
}
}
},
// prop2 shares some structure with prop1
prop2: MyInterface["prop1"]["big"]["complex"];
}
and it works! (currently in typescript@next)
I think the next natural thing would be to have something similar but for functions, some "function call types" like this:
interface FunctionLike{
(arg1 : number, arg2 : string) : {a : number; b : string;}
}
type ReturnType = FunctionLike(number, string); // {a : number; b : string;}
so we could combine it naturally with typeof
operator
interface BigThing {
testString: string;
testNumber: number;
testBoolean: boolean;
}
function getSmallThing(thing:BigThing){
return {
testString : thing.testString,
testBoolean : thing.testBoolean
}
}
type SmallThing = typeof getSmallThing(BigThing) // {testString: string; testBoolean : boolean}
And it looks that this is the syntax that already was proposed at beginning of this thread (@tinganho). :)
But it would be more general feature that just nicely compose with current typeof
operator, just like _indexed access types_ do now.
However some questions/doubts still remain:
typeofreturn
or returnof
)Seems like this is technically feasible. It might require refactoring overload resolution / type argument inference so that you don't need to resolve the arguments. I don't think generics poses too much of an additional problem. One question to ask is what happens if no overload matches the call.
I think it is not obvious whether this feature is desirable. If a function returns a very elaborate anonymous type, would it not be more helpful as an author to name the type, so that it does not have to be referenced in this way? I admit this is just based on a stylistic preference. I suppose I am not sure how convenient this will be to use in practice. I think it's fine for the element access though.
type P = typeof foo(0); // P is any[]
type Q = typeof foo(true); // Q is string
W.r.t. this discussion on allowing parameters to be referred to by their types, I would suggest allowing the same for the actual functions as well.
Context: I'd like to type a function to map
over objects:
function map<T, F extends Function>(fn: F, obj: T): { [P in keyof T]: typeof F(T[P]) }
The reason I'd like to be able to refer to the function by its type F
(aside from just its name fn
) would be for cases where its name might be unavailable, e.g. I'd like to be able to say type MapFn<T, F extends Function> = { [P in keyof T]: typeof F(T[P]) }
.
Something that would probably be much easier to implement (and still very useful) would be:
const myFunc = () => ({ x: 10 });
type myType = returnof myFunc; // { x: number; }
In theory, they could be chained as well if you wanted to get the type a few levels deep. Any thoughts?
Edit: Just realized this was mentioned above by @mpawelski 😄
@dehli That wouldn't work for overloaded functions though. Or generic functions where a type parameter is used in the return type.
@JsonFreeman Couldn't overloaded functions just OR
the various return types? And generic functions could require you to specify the type?
It would be possible, but I'm not sure how useful that would be. It seems like people want something more sophisticated.
I really hope this is gonna be implemented soon as it's a real pain.
As the last workaround does not work anymore I currently use this:
// 0 argument function
export default function returnof<T>(fn: () => T): T;
// 1 argument function
// If no ambiguity simply infer the return type
export default function returnof<A, T>(fn: (a: A) => T): T;
// 1 argument function, with possible overload for the argument type.
// Explicitly set the type and use the correct overload
export default function returnof<A, T>(fn: (a: A) => T, a: A): T;
// ...
You do not need to specify arguments if there's no overload of your function.
const hello = (arg: number) => 'World'
const helloReturnValue = returnof(hello)
type helloReturnType = typeof helloReturnValue // string
declare function hello(): void;
declare function hello(a: number): number;
const helloReturnValue = returnof(hello)
type helloReturnType = typeof helloReturnValue // void
const helloReturnValue = returnof(hello, 42)
type helloReturnType = typeof helloReturnValue // number
All definitions are here:
https://github.com/kube/returnof/blob/master/lib/returnof.d.ts
I just created a little NPM package to bundle it easily without polluting your project.
If Optional Generics are added like proposed in https://github.com/Microsoft/TypeScript/issues/2175, it will allow a simple declarative-only workaround:
type Return<T extends () => S, S> = S
And use it like this:
type helloReturnType = Return<typeof hello>
@mhegazy @JsonFreeman Any plan on this feature?
Hi everyone,
I have an alternative possible solution to this problem (sorry if it's already been suggested and I missed it) - I proposed it in issue #13949. I didn't know about the typeof operator at that stage, but I think my solution is more general. Basically, introduce a new syntax something like =MyType
, which can be used wherever you would use MyType
, but instead of declaring the object to be of type MyType
, it creates a type named MyType
from the inferred type of the object. E.g. this is how I would use it in creating a Vue component.
function createData(){
return <=MyData>{
dataProp1: <string>null
}
}
function method1(){
let self: MyComponent = this;
console.log(self.dataProp1);
self.method2();
}
function method2(){
let self: MyComponent = this;
//dostuff
}
type MyComponent = MyData & MyMethods;
let componentOptions = {
data: createData,
methods: <=MyMethods>{method1, method2}
}
//todo: register component...
Note the createData
function could also by written
function createData(): =MyData {
return {
dataProp1: <string>null
}
}
I found a really fun work around that generalizes to any expression:
const varWithRightType = (false as true) && some.deep.access()
type MyType = typeof varWithRightType;
EDIT: STOP LAUGHING AT ME THIS IS SERIOUS TYPESCRIPT
@johnfn this looks fun for simple cases :), but if your function require some params you have to make additional work around like this:
const stateProps = (false as true) && mapStateToProps({} as any);
I'm using a different work around, that's especially useful for React & Redux, when trying to use type inference to get props type from mapStateToProps
function. Then there's no need anymore to manually declare and maintain interface of Props injected by Redux connect
which is tedious to maintain and error prone.
This work around will also handle nicely any given function signature for other use cases.
Example of typeof
operator "React & Redux" use-case:
Below example try to show a common real-project use-case for usage of
typeof
operator to derive a type from a function declaration that would be extremely useful if added to TypeScript.
import { returntypeof } from 'react-redux-typescript';
import { RootState } from '../../store';
...
const mapStateToProps = (state: RootState) => ({
currencies: CurrencyRatesSelectors.getCurrencies(state),
currencyConverter: storeState.currencyConverter,
});
const stateProps = returntypeof(mapStateToProps);
type Props = typeof stateProps & typeof dispatchToProps;
type State = {};
class CurrencyConverterContainer extends React.Component<Props, State> {
...
Source: https://github.com/piotrwitek/react-redux-typescript-patterns#react-connected-components
@kube's solution and their package https://github.com/kube/returnof seems to work as expected! 👍
Hm. I wish returnof
would help type tuple-based map()
from a .d.ts
file, but given its reliance on the expression language (const
not usable in ambient contexts), I fear my use-case with that would be more complicated at least.
Edit: seems optional generics are merged by now, meaning @kube's declarative type language only version (type Return<T extends () => S, S> = S
-> type helloReturnType = Return<typeof hello>
) would become viable. :D
Correction: nope, the merged support for default generic values doesn't seem to allow specifying part of the generics while letting others fall back to their inferred values; Return<typeof hello>
just yields error Generic type 'Return' requires 2 type argument(s).
.
@tycho01 Yeah I was disappointed that it did not solve the problem.
I already opened a related issue about Optional Generic Types Inference:
You can generally get the return type of a function via a combination of a dummy ambient function and type inference:
ambient function returnTypeOf<RT>(fn:(...rest:any[])=>RT):RT {return void 0};
locally var r = returnTypeOf(someFunction);
gets value undefined
But if you then want to reuse that return type, then you must capture it ... so we are back where we started:
type RT = typeof r;
It would be so much easier if we just initially extend the concept of typeof
to allow returntypeof
or even better infer this usage from typeof fn(a,b,c,...)
, which could then capture different signature return types. This return-type comprehension is already performed by TS internally.
typeofexpression ()
would be a kind of return-type recursion mixed with type operations: eg
type E = typeofexpression (f(1) + g("x"))
is
type E = typecomprehensionof (typeof f(1) + typeof g("x"))
which might be comprehended like one of
type E = typecomprehensionof (string + string)
ie string
type E = typecomprehensionof (string + number)
ie string
type E = typecomprehensionof (number + number)
ie number
How difficult this is internally and the performance costs are unknown to me.
EDIT -------------------------------------------------------------
I meant to add ... this is especially important to anyone that has to use Function.bind, Function.apply, Function.call because these currently return type any
and this means that they have to constantly be type-annotated to ensure that they don't fall out of the type-checking process.
Being able to reference the return type of the function argument would be ... bliss ...
@poseidonCore
Being able to reference the return type of the function argument would be ... bliss ...
I like the current typeof <expression>
better, and the return type issue is already covered in #12342.
I was just using typeofexpression
and typecomprehensionof
as a means of differentiating those usages in the explanation. I would prefer typeof (expression)
as the actual syntax.
My point was that comprehending the type of an expression would require comprehending the return type of a function ... f(1)
is an expression too. #12342 is related in this way. They are not mutually exclusive.
We can already do typeof
for variables and so since an expression is an operation over variables and functions, the next requirement is being able to return the type of a function ... and then comprehend the result based on type rules.
Actually, good point. The issue #12342 wants is a way to access the return type as kind of a property-like thing for generic types, so I misunderstood the relation.
How about using expressions for typeof
function's call, as in the proposal, but using types directly in place of parameters?
E.g.
function myFunction<T>(param1: T, param2: string, param3: number): T & {test: string} {
// ...
}
type ResultType = typeof myFunction({hello: string}, string, number)
// ResultType is: {hello: string} & {test: string}
Note that this doesn't stop anyone from using types of locally scoped variables, by using typeof within the calls, i.e.:
type ResultType = typeof myFunction(typeof obj, string, number)
// ResultType is: typeof obj & {test: string}
This seems a bit better then the original proposal, since it works in an ambient context and seems more flexible in general. To me, it also makes it clearer that we are not actually calling the function, just trying to return the type of its return value.
How I did it for my simple case:
interface IAlertMessage {
addedAt: number;
text: string;
type: "error" | "warning" | "info" | "success";
}
declare let alertMessageInterface: IAlertMessage;
const messages: IAlertMessage[] = [];
function addMessage(text: string, type: typeof alertMessageInterface.type): void {
messages.push({addedAt: new Date().getTime(), text, type});
}
addMessage("something", "info"); // <- OK - and also has intellisense for the second parameter (after typing first " of the second parameter, press Ctrl+Space in VSCode)
addMessage("something", "fatal"); // <- Compilation error : error TS2345: Argument of type '"fatal"' is not assignable to parameter of type '"error" | "warning" | "info" | "success"'.
The above also works with members of the interface itself:
declare let myIface: MyInterface;
interface MyInterface {
prop1: {
big: {
complex: {
anonymous: { type: {} }
}
}
},
prop2: typeof myIface.prop1.big.complex.anonymous;
}
The advantage of using declare let ...
instead of type ...
for me is that it doesn't produce anything in the output .js file.
@varadero this does not solve the problem at hand, which is retrieving the return type of a function call. All you're doing is retrieving the type of a property of a type, which is a supported feature since TypeScript 2.2, I believe.
That's actually the type of a property of a (fake) value. The compiler doesn't let you use typeof with "pure" type or interface, it only works on values.
This would be great to have!
since type KEYOF<T extends any[]> = keyof T[0]
already exists, typeof T[0]('something')
would work as well with this implementation, assuming function myFunc<T, K extends KEYOF<T>>(type: K)>{ }
and myFunc([(r: string) => r])
(would return string for typeof T[0]('something')
)?
This would be powerful together with polymorphic this
.
Time for this to happen!
Re-reading this thread, I tried to grasp what we're doing with typeof
syntax-wise and why.
Evidently, in the trivial typeof foo
case already implemented, it gives the type of foo
. (I imagine pet is Fish
is immune for using special syntax.)
In my current reading of what this keyword should do in the current proposal, the keyword itself does no more than what it does now, and the current proposal in fact is unrelated to the typeof
keyword.
I bring this up because it matters in the case I'd mentioned above, where we're applying a function stored as a type (be it through type
or as a generic), rather than as a value.
Given that, I presume that, while typeof fn<A>(string)
needs typeof
to lift expression-level variable fn
for use on the type level, Fn<A>(string)
on the other hand, with Fn
as a generic containing a function, would not require that, and hence could be 'applied' so as to obtain its appropriate return type here without need for typeof
.
In this interpretation, we would check for function calls after any after potential function types: beside typeof fn(...)
/typeof fn<...>(...)
also Fn(...)
/Fn<...>(...)
, if not even function literal ((foo: Foo) => Bar)(Baz)
/ + generics. Otherwise, the plan of attack should remain intact.
Maybe I'm misinterpreting how you guys see this, maybe functions stored in types just hadn't even been considered (as I found no mention of them). Either way, I figured it'd be worth confirming.
If announcing function application becomes yet another semantic overload for typeof
though, in addition to disambiguating class constructors and lifting expression-level variables to the type level (aside from the expression-level typeof
), things seem gradually more convoluted, as already indicated by a few earlier questions in this thread.
Edit: a generic type can already return a function type, which could have generics as well. This means another permutation would be GetFn<A><B>()
, with the first generic set belonging to the generic type invocation, the latter belonging to the function invocation. No GetFn<A><B><C>()
though, though GetFn<A><B>()<C>()
would be legit as well. The earlier conclusion remains unchanged though: any type can contain a function and therefore (potentially) be applied as a function.
Edit 2: I just realized there'd be an unfortunate ambiguity in X<Y>()
. Now, X
would be a type returning a function type.
X
is not generic, this is clear -- <Y>
belongs to the function call.X
is generic and requires the parameter, this is also clear -- it belongs to the type, and the function is not passed any type parameters.X
has an optional generic however, this becomes ambiguous.X
is passed a type parameter, while the function is not.X
is left to use its default type param, while <Y>
would parameterize the function call instead.I'm not yet sure what would be the best solution here.
<>
rather than also allowing to skip this. However, this would be a breaking change.@icholy: yeah, these edits are just further adding to your point, I know.
Edit 3: okay, this feature now trumps the top of my wish list. No other upgrade comes even remotely close in terms of impact for inference.
@icholy: in short, if the 'function' is in a generic/type, would we still write typeof
?
If this is done so it interacts with overloads correctly, it actually gives you "variadic" functions for free (they're just curried instead of having >1 arity), since you can recursively define the return type of a function in terms of the application of one of its overloads, with some base-case overload. This is how things are done in Haskell, and it would be a wonderful feature to have in TypeScript.
@masaeedu: that sounds interesting. And yeah, overloads are definitely what make this proposal so interesting -- they could be used so as to pattern-match over different options, any
fallbacks included. Type checks like that so far weren't possible on the type level yet.
I'll concede I've used Ramda more so than Haskell. But from that background, I thought currying normally didn't combine well with variadic functions, as curry would need to 'know' whether to return the result or another function to deal with additional arguments.
Could you perhaps show some pseudo-code as to how you'd see this idea work for variadic functions like Object.assign
(skipping over details like &
vs. Overwrite
as I used in my PoC for R.mergeAll
)?
@tycho01 I've been playing around with code like this:
interface Pop<TVarStack extends VarStack> {
(a: TVarStack["head"]): Pop<TVarStack["tail"]>
}
interface VarStack<THead = any, TTail extends (void | VarStack) = any> {
head: THead
tail: TTail
<TNew>(a: TNew): VarStack<TNew, this>
(): (a: Pop<this>) => any
}
// Figure out implementation later :)
let stack: VarStack<void, void>;
const pop = stack(new Date())(10)("Bob's")("your")("uncle")()
// a, b, c, d, and e are well-typed
pop(a => b => c => d => e => {
// Need this because can't figure out how to encode base-case in Pop recursion
return stack
})
VarStack
is a "variadic" function type in a sense, in that you can give it an arbitrary number of heterogeneous arguments and it will faithfully record the associated type information using recursion at the type level. Unfortunately TypeScript doesn't have good support for transformation and pattern matching at the type level, in the same way that Haskell does.
If we were to have access to typeof
, I would be able to solve the base-case problem for Pop
, since returnof(overloadedFunction(T))
would essentially give me a way to do pattern-matching at the type level.
@tycho01 I can't do it precisely for Object.assign
, because as I said, this only works for curried functions, but I'll try to create a curried assign
function for you that works roughly the same way.
I wish they would generalize the type algebra a bit, so that e.g (a: string) => (b: number) => void
was just sugar for Func<string, Func<number, void>>
, and (a: string, b: number) => void
was just sugar for Arity<number, Arity<string, void>>
, or something like that. Then we'd just need general purpose tools for transforming types in order to get lots of interesting features emergently.
Apologies all for the triple post. @tycho01 Here's curried, "variadic" assign
:
interface Assign<TCurr extends {} = {}> {
<TAdd extends {}>(a: TAdd): Assign<TCurr & TAdd>
(): TCurr
}
let assign: Assign = undefined // implement somehow
const result = assign({ foo: "bar" })({ baz: 42 })()
@masaeedu: Hah, so I guess it's like having a reducer function :), though that part could maybe get harder if there were non-variadic (?) arguments as well.
I like the idea; I definitely haven't been thinking in the direction of interfaces quite as much.
If we can change the JS for the typings though, perhaps I'll ask TC39 to just change Object.assign
to a non-variadic mergeAll
. :D
Not like my increment-based iteration like that actually worked with functions in practice so far though (#17086)... but either way I'm with you this proposal would have a bigger impact than any other I've seen.
@tycho01 I think you're unlikely to get much traction if you ask for variadic functions to be turned into fixed arity functions, since there is a runtime cost to curried functions or repeated application that JS (or at least existing JS engines and code) is probably not highly optimized for. I think what we need instead is for TypeScript to make it equally easy to build up and deconstruct types for arity > 1 functions recursively. I guess this all comes back to #5453.
Others may find it interesting to learn that c++ has a very similar feature: decltype(expression)
Is this feature ever going make it to TypeScript?
I highly prefer the original proposed form where the argument is an expression, but TS team's intention to have special syntax with types in expression positions just complicates everything and delays from landing it into the language.
We need to keep this topic up.
I'd be really glad if we could get even a subset of this proposal implemented. Namely, this single one:
type A = typeof anotherVariable
. I could do everything with just this one line. interface B extends A
, <B & A>
: B & A
, etc.
I'm seeing a lot of use-cases for this sort of thing with React. When you create higher order component, it's much harder to hint to the component class declaration that it's currently a HOC.
What I ended up doing is creating two classes. One class extends React.Component
and adds all the additional methods, while the class inside of the HOC function manipulates the render()
.
@blindbox type A = typeof anotherVariable
does work already:
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
function checkItem(item: typeof data) {
// item is Array<{ raw: number; square: number; }>
}
@Igorbek Wow, you're right.
Okay, I figured out why it didn't work on my end.
This won't work
interface B {
thisIsB: boolean
}
const typeTest = (type: any) => {
return 1 as any as App & B
}
type A = typeof typeTest(1)
declare const aVal: A; // aVal's type isn't of type App & B, but something else.
This however, will work.
interface B {
thisIsB: boolean
}
const typeTest = (type: any) => {
return 1 as any as App & B
}
const typeTestVal = typeTest(1)
type A = typeof typeTestVal
declare const aVal: A; // aVal's type is of type App & B!
Yeah, so the problem is that it cannot be used in certain use cases, such as when you need to use generic type arguments.
function someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item: T) { ... }
// it is impossible to work this around because there's no chance to create a fake variable that would be parameterized by T
function use<T>(item: typeof someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item)) { ... }
@Igorbek:
I highly prefer the original proposed form where the argument is an expression, but TS team's intention to have special syntax with types in expression positions just complicates everything and delays from landing it into the language.
So since they wrote that, we've gotten literals for strings and numbers, meaning things are getting a bit fuzzier -- those would work either way. Literal array and objects similarly look similar to their type notations, so so far so good.
So then we also have stored values/types, e.g. variables, generics, other types. For things to get useful, we shouldn't just have to settle for part of these here.
What makes a good argument for their type-based approach here is that allowing this mixture sort of comes for free at the type level. That is to say, at the type level we could already do say typeof myVar
, meaning if this function 'application' is added to the type level, we'd automatically be able to plug in both stored types and regular variables.
I'd be interested to see your further thoughts on the originally suggested approach. Admittedly, that might serve as to expose a bit more to the type level: JS-based operators (think !
&&
||
+
-
*
/
instanceof
) as well as TypeScript specific operators (assertion operator !
).
The thing about those JS operators is like... they're pretty useless at the type level as it stands, as allowing them to operate on literals to yield the corresponding literal result types is currently deemed out of scope (ref) -- expression-level 1 + 1
just yields type number
, and similar for the others.
With this in mind I've kinda been down with their type-based proposal myself.
Is this feature ever going make it to TypeScript? [...] We need to keep this topic up.
I suggested this proposal as a more general solution to a smaller symptom here, though to limited success.
@tycho01 My arguments for expression-based variation of typeof
are almost the same as originally stated by @yortus:
typeof
(in type position) is already working with expressions, however constrained to accept a single symbol. So that accepting types or pseudo-calls would introduce much more cumbersome syntax that is much more another syntax branch (in addition to expressions and types).(undefined as any as <any arbitrary type can be here>)
), but sometimes type system lacks types that can however be expressed in expressions (spread and rest types were introduced after the corresponding expressions had been landed).Thank you @tycho01 that you brought that point to promised
PR (I actually came here after your comments there), that really shows how such a simple and generic feature can cover much more complex scenarios in very elegant way without baking in very specific behavior into the language.
I see the extended typeof
as a real game changer for type system expressiveness, most like mapped types/keyof
did that.
@Igorbek: Thank for elaborating, I see where you're coming from then. Perhaps these two approaches serve different use-cases then.
I think you're better demonstrating today's value of the original proposal than the original post does today, as current TS (or, well, Playground's 2.3.3
) can make many of the original scenarios work already:
// I have a strongly-typed collection but the element type is anonymous/unknown, how can I reference the element type? (#3749)
// Mapping to a complex anonymous type. How to reference the element type?
var data = [1, 2, 3].map(v => ({ raw: v, square: v * v }));
declare function checkItem(item: typeof data[0]): any
// ^ no longer errors, needs no further change
// A statically-typed dictionary. How to reference a property type?
var things = { 'thing-1': 'baz', 'thing-2': 42 };
type Thing2Type = typeof things['thing-2'];
// ^ no longer errors, needs no further change
// A strongly-typed collection with special indexer syntax. How to reference the element type?
var nodes = document.getElementsByTagName('li');
type ItemType = typeof nodes.item(0);
// ^ the `.item` access works, but function applications still errors, would be fixed by either version of the proposal.
// A function returns a local/anonymous/inaccessible type, how can I reference this return type? (#4233, #6179, #6239)
// A factory function that returns an instance of a local class
function myAPIFactory($http: HttpSvc, id: number) {
class MyAPI {
constructor(http) {}
foo() {}
bar() {}
static id = id;
}
return new MyAPI($http);
}
declare function augmentAPI(api: typeof myAPIFactory(HttpSvc, number) /* ERROR */): any
// ^ function applications errors, would be fixed by either version of the proposal, if using slightly different syntax.
// I have an interface with a complex anonymous shape, how can I refer to the types of its properties and sub- properties ? (#4555, #4640)
// Declare an interface DRY-ly and without introducing extra type names
type MyInterface = {
prop1: {
big: {
complex: {
anonymous: { type: {} }
}
}
},
// prop2 shares some structure with prop1
prop2: MyInterface['prop1']['big']['complex'];
}
// ^ no longer errors after swapping `.k` access to `['k']`. `typeof` was unnecessary and counter-productive -- MyInterface isn't exposed on the expression level.
I guess these examples are no longer making much of a case for either the expression or type approaches -- most are obsolete, and trivial function application would be enabled by either.
Admittedly type-level based function application, as focused on by tinganho, the TS team and myself can expose expression variables through typeof
, though as you noted, the type level admittedly tends to lag behind the functionality exposed on the expression level. This function application itself as well as the spread syntax you mentioned are obviously primary examples, and I'd definitely love to see them addressed. Perhaps much of the current gap could even be tackled here.
Similarly, an expression-first approach would also allow injecting types using <MyType> whatever
or whatever as MyType
, though as the typeof
in the type approach, it would similarly seem like an afterthought: questions about applying functions stored in types/generics remain, which likely make up for most of the actual added value of that type-based approach (though unmentioned there) -- accurate inference for higher-order functions, as well as say type-based conditionals like in that promised
thread.* Worse yet, unlike the OP's use-case, these have no work-arounds.
I think see where the ideas clash -- current proposals have contradictory views on whether the typeof
keyword would switch its expression to the value level, vs. (in my interpretation) leaving typeof
as-is, but exposing function application syntax on the type level.
In my view, the contradiction is somewhat accidental though. I wouldn't disregard the legitimacy of either use-case; if both were to be implemented I could see an extra keyword avoiding semantics clash. I'm honestly indifferent whether keywords will end up one way or another -- I just wanna type shit.
*: I just realized you'd probably go about higher-order functions by referencing functions in parameters by their parameter names, rather than by capturing their types in generics.
It looks like things can in fact be converted in both directions: typeof
helps lifting from the value level to the type level, while declare let y: x;
helps lift things from the value level to the type level. Inelegant like the OP's workaround, but yeah.
If function types were brought in through e.g. explicit generics though, I guess this would not help -- they'd be on the type level without a way to move them over.
If that sounds like I'm pre-emptively hoping to cover bases on functionality, it's probably because much of my progress on unsolved typing challenges got blocked on this one add-on (with notable mention to 5453). But if we can figure these things out, it'll be worth it.
Edit: I've thought of a few theoretical cases that might be excluded in an expression-based approach now, though no practical occurrences have come to mind yet:
declare function f<G extends (...args: any[]) => R, R extends <T>(foo: T) => Bar<T>>(g: G): typeof R(baz); // error following the expression-based proposal: R is not exposed at the expression level
. These could of course be calculated if you knew and could provide the parameter types of G
, but afaik there is no way to get this info so far (unless maybe #14400?). I suppose this category would include functions operating on those factory functions used in AngularJS mentioned above.@Igorbek I don't understand what you expect with this snippet:
function use<T>(item: typeof someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item)) { ... }
It seems like a circular definition.
@masaeedu sorry, I meant:
(switch to f
instead of someFunctionWithComplexReturnTypeThatUsesGenerics
)
function use<T>(item: typeof f<T>(undefined as any as T)) { ... }
Just to elaborate, that is what currently cannot be worked around by introducing a fake variable:
const _typeofItem = f<T>(undefined as any as T); // no T here
function use<T>(item: typeof _typeofItem) { ... }
BTW, just realized that current proposal conflicts with member type query type operator T[K]
since typeof
has higher priority:
typeof x[K]
currently means (typeof x)[K]
where K
_is_ a typetypeof x[k]
means (typeof x)[k]
where k
is a type; ortypeof x[k]
means typeof (x[k])
where k
is an expressionI would actually prefer typeof (x[k])
because they are semantically equivalent, but is a breaking change for sure.
When I used Chrome's developer tools, I got that the member operator has higher precedence. Where did you find that?
@Igorbek: yeah, that appears to be why that type Thing2Type = typeof things['thing-2'];
from the original post works already.
@dehli: you're comparing JS expression level to TS type level -- they're not the same. One runs in browsers/node, the other in the TS compiler.
I personally find it odd that TypeScript resolves type-level typeof
with a higher precedence than indexed type access. It almost looks like a bug to me, honestly. (I wonder if that case is actually tested.)
@tycho01 Gotcha. I assumed TS would use the same order of precedence as JS.
@dehli the expression levels are the same, yeah. On the type level it's another thing with similar syntax, but returning a type instead of a string.
I'm guessing the precedence might be the result of the limitations they'd envisioned for it to have. I'll concede those considerations may no longer seem as sensible if its function is to be expanded.
On another note, if both versions of the proposal were to be implemented, the brackets would serve as an effective way to keep it explicit what level we'd be in for that as well. I'm having trouble coming up with things that don't sound like compromises, but yeah.
@Igorbek Is this the primary remaining use case for typeof
on arbitrary expressions? If we get #14400, that would give us returnof
using plain user-defined types if I'm not mistaken.
returnof
in its generic form:type Return<T extends () => R, R = any> = R;
// overloaded function
declare function f(item: number): number;
declare function f(item: string): boolean;
type fType1 = Return<typeof f>; // ??
type fType2 = typeof f(1); // number
type fType3 = typeof f('a'); // boolean
// generic function
declare function g<T>(item: T): T;
type gType1 = Return<typeof g>; // ??
type gType2 = Return<typeof g<number>>; // not supported syntax
type gType3 = typeof g(1); // number
type gType4 = typeof g<number>(1); // number
type gType5 = typeof g('a' as 'a'); // 'a'
I'm not aware of all the remaining use-cases, they seem not yet been discovered yet, but the most unbeatable and attractive to me are:
typeof
would use standard overload resolution; @tycho01 has shown an elegant example in promised
types PR #17077 @masaeedu: Good question. The short version is pretty much what @Igorbek said; for a bit more detail of concrete challenges solved, you'll find a short list at my 'top features needed' list in #16392, where I've tried to tie unsolved challenges to the proposals that could enable them.
This includes:
ReturnType
.reduce
/ map
/ filter
/ find
-- anything involving iteration and checks. stdlib.d.ts
would benefit there, and so would FP libs like Ramda/Lodash. In practice not all inputs may be known (more list-like arrays than tuples), but map
and filter
operations on objects could be typed better than just Partial
as well. This is necessary to better type state management libraries such as redux (see https://github.com/piotrwitek/react-redux-typescript/issues/1) and Angular's ngrx, which until then are forced keep their user API low-level because without a well-typed map
operation there's no way for them to calculate the desired result on the type level from DRY'er input data.flatMap
-like operations such as that promised
proposal0
divisors -- essentially a hack to subtract from types, the topic of #4183. I'm sure I haven't imagined most use-cases there yet, but that could be done with this, e.g. function div<B extends number, NotZero = { (v: '1') => 'whatever'; }({ (v: 0) => '0'; (v: number) => '1'; }(B))>(a: number, b: B)
. Normally Idris language advertises with length-safe vector/matrix operations, but that only needs this for higher-order functions.Union<T>
/ NonUnion<T>
constraints on input types using the above constraints + IsUnion
.curry
, Function.prototype.bind
, Promise.all
(not an exhaustive list, just some functions other people raised as challenging).switch
logic proposed in #13500, here resolved through function overload pattern matchingpromised
), aka type-level type checks. One would be inclined to wonder whether this could be resolved by loosening the type-level is
keyword for use in type expressions -- currently it can be used for the tutorial pet is Fish
, but in no other position or context. However, that wouldn't solve it -- even if that were to be usable to produce boolean literals anywhere today, until this #6606 lands we still had no way to operate on / do conditionals based on such boolean literals on the type level yet anyway.Omit
, Overwrite
from #12215), see my comment in #12424toString
, toLocaleString
) will resolve to the string index rather than to the prototype methods, potentially fixing glitches related to toString
object access across all other type operations that previously dependend on the glitched built-in object property access.edit: crossed out use-cases filled by conditional types (#21496) since the time of writing.
@Igorbek's proposal of an expression-first approach would (at the cost of maybe breaking some unknown applications of part of the above list -- I've yet to think of anything concrete though I'm scared of getting painted into a corner) additionally promise to close the gap between value and type levels, which currently include things like this function application (this #6606), spread/rest (#5453), assertion operator !
(#17370), union type subtraction through type guards (#4183, otherwise achievable through the constraints mentioned above), and possibly more I can't think of off the top of my head.
I guess this whole trade-off might raise some questions like why make all the same functionality accessible in two ways (actual type level, expression level accessed embedded in type level).
Edit: updated my comparison comment above with an extra potential challenge.
Edit 2:
It's unfortunate here is that the original post would have you believe this would only allow writing some edge cases without some null &
workaround, while in reality this is the critical feature holding TS back right now with no known workarounds for ambient contexts.
That means it affects TS written in separate .d.ts
files, which is the norm not only for stdlib.d.ts
, but also for all DT typings written separately from their original JS projects, which is rarely likely to change while JS libs want to remain open to alternatives like Flow too.
(This isn't much better for .ts
typings, in that types using the OP's 'workaround' can not be parameterized such as to compose into higher-level reusable types without influencing the expression level.)
@tycho01 Thanks for the list; there's a lot of links in there that I need to digest. I really want pattern matching on types as well, and #6606 is a good, pragmatic solution to the problem. However it seems to me that there is an increasing confusion between things at the value and type levels, and #6606 will not improve things in this area.
The reason this feature is needed is because we have no way of constructing type expressions that correspond to:
typeof K[L]
) the type of a property on an object type that corresponds to a key of a string literal typeI feel like it should in fact be possible to construct purely type level expressions for these, without resorting to mixing together value-level constructs and type-level expressions. It would be nice if types like (a: A) => B
or A | B
were sugar on simple parametrized types like Func<A, B>
, Union<A, B>
, and we had general tools for manipulating parametrized types (HKTs, fundeps or type families, etc.).
I know an excessive focus on soundness isn't one of the goals of TypeScript, but there are now a lot of type-level concepts that interact together in opaque ways. Some kind of formalization of what a type is, and a way of prescribing mechanical rules for how a type interacts with other types (subtyping, destructuring etc.) would go a long way.
I feel like it should in fact be possible to construct purely type level expressions for these, without resorting to mixing together value-level constructs and type-level expressions.
Oh, yeah, I personally didn't really intend to resort to using value-level constructs at all -- this is all I'm trying. If the value level were indispensable, I might have been in the expression-based camp here myself. :P
I guess the gap between value and type levels is mostly expected to narrow (does TC39 move faster than TS?), and afaik the gaps do have outstanding type-level proposal already (see the bottom of my previous post).
Heck, I realize constructing typings for quite some of the functions is gonna be outside the experience of most users.
The way I look at this is, I want the standard library and FP libs to be typed so well that TS users could just write those and have inference automatically taken care of for them.
TS has only a few type-level operators, but solving actual problems with them (prime example is #12215's Overwrite
/ Omit
) might well seem little short of rocket science to casual web devs. Heck, it's taken us until recently too, and they're not even prototype / index / symbol -proof yet.
It would be nice if types like (a: A) => B or A | B were sugar on simple parametrized types like Func, Union
We can flip it around and create the parameterized types as aliases / type constructors. To a type operation taking a Foo<Bar>
, it doesn't matter whether your thing simplifies to one -- it just checks whether it fits the description.
This is pretty much what stdlib.d.ts
does -- you have a foo[]
, but it satisfies the Array<Foo>
description and therefore works with its Array.prototype
typings.
Not like that actually helps though:
type Union2<A, B> = A | B;
type TuplizeA<Tpl extends Union2<any, any>, A, B> = [Tpl[0], Tpl[1]];
// ^ sorry, no way to access union members through e.g. [0]
// type a = TuplizeA<1 | 2>;
type TuplizeB<Tpl extends any | any> = [Tpl[0], Tpl[1]];
// ^ sorry, no way to access union members through e.g. [0]
// type b = TuplizeB<1 | 2>;
type TuplizeC<Tpl extends Union2<A, B>, A, B> = [A, B];
type c = TuplizeC<1 | 2>;
// ^ need 3 arguments, maybe fixable with #14400
type TuplizeD<Tpl extends A | B, A, B> = [A, B];
// ^ need 3 arguments, maybe fixable with #14400
type d = TuplizeD<1 | 2>;
So um yeah, haven't solved it but just realized kube's #14400 could actually help there. And I just saw you there earlier today too!
Same thing for functions actually -- which is a good reminder that #14400 might not just do function return types there but param types as well.
This approach would for the moment need to use overloads, which is unfortunate since it doesn't scale, but yeah. To make it generic again, we'd essentially use these #6606 pattern-matching overloads to trigger the right option for different arities.
I suppose one could use that to generically convert them into tuple types, then iterate over them using my increment approach to do operate on them somehow.
For unions I'd been hoping for a prettier way to convert to tuple types. It'd add an arbitary order though, and can't think of a pretty syntax/keyword either.
Edit: to go over those expression/type level functionality gaps again:
!
(#17370): after this #6606 this is solved!
above, with this #6606 also achievable through constraints (e.g. NotZero
above).ArrayLike
s can, see the List
operations in my gist. with this #6606 I now think we could extract params from unapplied functions, though extracting them at the moment of application (i.e. to get their precise input values), would still need 5453.In short, if this proposal were to land, I'd say a functionality gap between expression and type levels would not make a super big argument toward the expression-flavored proposal here. If 5453 were in too, I could no longer think of any there. And note that for rare exceptions, the workaround noted in the original post here would still be valid.
Now, an argument that can still easily be made for it would be that with the expression-flavored variant, even before the type level catches up in mirroring operators, this functionality would be exposed under the same syntax as in JS (without workarounds), reducing learning curve.
Edit 2:
I just realized that the expression proposal's trick of bringing types to the expression level, 1 as any as MyType
, should logically work for the function itself as well.
This probably means that actual functionality enabled by both flavors seems somewhat similar, outward differences mostly consisting of typeof myVar
(type flavor) vs. myVar
(expression flavor) to use variables in function application; to use types in them MyType
(type flavor) vs. 1 as any as MyType
(expression flavor, alternative declare let a: any;
then <MyType>a
).
AST changes of either seem pretty manageable as well. Expression flavor just needs the typeof
conjunct to point to a value expression instead; type flavor would copy existing function application syntax (fn<T>(arg)
) from expression to type level, hooking into existing implementation as proposed by Ryan above.
I think it then comes down to the following:
Case for expression flavor:
typeof expr
with JS syntax without workaround ahead of TS type level supportCase for type flavor:
MyType
instead of 1 as any as MyType
: no type level in your expression level in your type level in your expression level.One related topic so far left untouched here has been how to provide this
bindings in this type-level function 'application'. Now, JS delegates overwriting this to the Function.prototype
methods, but as it stands, those lack a way to handle this at the type level.
Random example syntax, given a function type F
(this: Foo, a: number, b: string) => SomeReturnType
that might otherwise have been invoked as F(MyA, MyB)
: F(this: MyFoo, MyA, MyB)
.
Calculating the return type without overwriting the this
binding would still be like F(MyA, MyB)
, mirroring how the type-level this
argument normally gets ignored if you try to use such a function at the expression level.
Pros of this example syntax:
Cons of this example syntax:
So, turns out, this is already in the language!
Don't get too excited.
@DanielRosenwasser just pointed me to #12146, a bug that allows function calls on literals to be used in place of types.
_5 minutes later_
Ta dah! A horrible evil thing we should never use in production. But it's tempting...
interface String {
passthrough<T>(v: T): T;
}
// All work
type ANumber = "".passthrough(10 * 10);
type AString = "".passthrough("hello" + "world");
type AHelloWorld = "".passthrough("hello world");
type AnArbitraryThing = "".passthrough(Object.assign({hello: "world"}, {foo: "bar"}));
type ThisCraziness = "".passthrough((() => "cows are big dogs"));
~That makes the Effort: Difficult
on this issue look a little dubious, looks like they did it by accident over there.~ I read more and feel silly, this _is_ difficult to do nicely.
Have fun @tycho01.
@TheOtherSamP I tried this with TypeScript 2.4.2 and all of the those types are inferred to be any
.
@pelotom Huh, it's working here on 2.4.2 and 2.5.0-dev.20170803. Target es6 and and strict mode.
Looks like they've just fixed it though, I fear that may be my fault. #17628
@TheOtherSamP Nope, no dice. Oh well.
@pelotom That's strange, it's working in a completely fresh project for me, I don't know what would be different about our setups.
@TheOtherSamP: haha, that's pretty funny.
Time-wise looks like they started the fix there a bit before your comment though. Oh well.
@pelotom:
I tried this with TypeScript 2.4.2 and all of the those types are inferred to be any.
His snippet seems to work in Playground (2.3.2). On a recent-ish version outside of it (^2.5.0-dev.20170626
) I'm having trouble reproducing too.
That makes the
Effort: Difficult
on this issue look a little dubious, looks like they did it by accident over there.
They were referring to a type-based implementation, which would mean some changes, while this seems to use expression language (-> +
, Object.assign
, further function calls).
@tycho01 I _think_ it all got started by my drawing attention to it in #17618. Oh well, that'll teach me to semi-seriously consider using it in production.
They were referring to a type-based implementation, which would mean some changes, while this seems to use expression language (-> +, Object.assign, further function calls).
Yeah, I was an idiot and didn't read through this whole issue until after saying that. It's a shame, that probably is a better version of the feature, but I wish we could have it now. I push the limits of the type system a lot, and either this or #12424 would open up so many options.
Opened a PR at #17961.
@yortus Does this cover case of 'typeof literal'?
TypeScript today doesn't allow to write "const x: typeof 1 = 1;"
@NN--- the original proposal covers all expressions, but as I understand it the 'approved' part only covers property access and function return types.
Even if typeof 1
was allowed, I'm not sure whether it would give the literal type (1
) or the wider type (number
).
TypeScript today doesn't allow to write "const x: typeof 1 = 1;"
Why not const x: 1 = 1;
?
@SaschaNaz I wanted to write something like
const a = {q:1};
const b = {q:1};
const x: ReadonlyArray<typeof a> = [a,b];
But similar doesn't work with literals:
const x: ReadonlyArray<typeof 1> = [1,2,3];
@yortus Good point about exact type. Didn't think about literal types..
@NN---: I believe your example works already.
@tycho01 Flow now have $Call
type to get return type of function https://github.com/facebook/flow/commit/ac7d9ac68acc555973d495f0a3f1f97758eeedb4
Wouldn't allowing only typeof fn(...)
be the same as allowing typeof
of an arbitrary expression?
function fn() {
return /** whatever expression you like */;
}
type a = typeof fn();
except you are now creating a runtime function for no other purpose than to figure out a type?
Not really. You're doing a type evaluation of an expression, not an execution of the expression.
@dyst5422 typeof fn()
wouldn't actually evaluate the expression, it would just give you the return type.
EDIT: maybe that's what you were trying to say, not sure. But I think @sky87 was talking about _defining_ a function for no purpose except using it in a type expression, not _evaluating_ it.
@dyst5422 , as @pelotom said, I didn't mean that you would execute the function. To elaborate more: if you don't allow the typeof
of arbitrary expressions but you allow the typeof
of the return type of a function, what I will do to figure out the type of more complicated expressions is to wrap them in a function so I can ask for its return type. That creates a function at runtime though just to get it's return type, and it's more boilerplate to write.
EDIT: you can actually figure out the type of arbitrary expressions already, this is ugly but works
const dummy = (false as true) && /* arbitrary exp */;
type TypeOfExp = typeof dummy;
I honestly don't know which hack I would prefer. I think the best thing would to be able to directly ask for the type using typeof
.
Ah, I follow now. Yeah, I think the preferable way to go would be to be able to use it as
type TypeOfExp = typeof (
false &
"false" &
0
)
to be able to arbitrarily do expression type evaluation
Will it be possible to query the return type of a new
invocation? My use case: I want to write type annotations for a function that accepts a reference to any PromiseConstructorLike
implementation (e.g. $q or Bluebird) and returns a Promise constructed by that implementation.
declare function wait<P extends PromiseConstructorLike>(time: number, implementation: P): typeof new implementation<void>((res: any, rej: any) => void);
const bluebirdPromise = wait(1e3, Bluebird);
// typeof bluebirdPromise should be instance of Bluebird
Will it be possible to query for return types without typeof
, or will we have to null as FnType
?
interface Fn {
(a: string): string;
(a: number): boolean;
}
type Ret = Fn(number); // Ret = boolean
type Ret = typeof (null as Fn)(number);
Sorry if these questions have already been answered; I couldn't find them.
Whats the point of new
here, wouldn't you just want typeof implementation()
?
No, because implementation()
is not a valid call. PromiseConstructorLike
can only be invoked via new
, per its type declarations. typeof implementation()
is a type error, just like (typeof implementation)['foobar']
would be a type error.
Is it possible to introduce inferable generic types like what FlowType did? At least, it can solve the problem for getting the type of return value of functions.
type _ExtractReturn<B, F: (...args: any[]) => B> = B;
type ExtractReturn<F> = _ExtractReturn<*, F>;
@Cryrivers: see #14400 for that approach. It doesn't actually solve the problem where the output type depends on the input though.
Ended up needing this again today to hint what a call to a function would return dynamically, sure wish it would get priority.
A ReturnType<T>
operator is being added to lib.d.ts
in TS 2.8, powered by conditional types.
As ReturnType<T>
has yet to take into account return types dependent on argument types, for reference, here is Flow's $Call
type implementation.
edit: sorry @goodmind, I hadn't realized you'd already linked to exactly that.
I updated my earlier post of use-cases of this proposal (or its type call interpretation) based on the recent TS additions.
Pattern matching use-cases are now covered by #21496, leaving... the cases where we wanna calculate types based on lambdas provided by the user, e.g. curry
, function composition, map
, reduce
, lenses editing based on a lambda... the fun stuff. :)
P.S. @thorn0: I think your Angular use-case can be filled with ReturnType
(#21496) now!
I thint this should be coverd by allowing this expressions.
prop2: typeof this.prop1.big.complex;
@mhegazy Is there a separate issue for tracking this ?
It is annoying that typeof works for locals but not for properties, while working for static properties.
class A {
x: number;
static y: number;
f() {
const a: number = 1;
const b: typeof a = 2; // OK
const c: this.x = 3; // No :(
const d: this['x'] = 3; // OK
const e: typeof A.y = 4 // OK
}
}
@NN--- you can always use indexed types for this:
this['x']
@cameron-martin Doesn't work. Playground
@tycho01 the new type infering and conditionals are awesome, but also super annoying that they don't work for function overloading. The reason given was it needs something like this typeof
to resolve the function type out of the possible ones.
@NN just use const d: this['x'] = 3;
Oh, nice :)
@NN--- or use
class A {
x: number;
static y: number;
f() {
const self = this;
const a: number = 1;
const b: typeof a = 2; // OK
const c: typeof self.x = 3; // OK
const d: typeof self['x'] = 3; // OK
const e: typeof A.y = 4 // OK
}
}
@tsofist I am aware that local works, but I find this ugly.
It is same as manual saving 'this' for 'function(){}' callback instead of using lambda with implicit capture.
@NN
this ugly.
yep, this is just an option :)
Conditional types now make this largely irrelevant since you can write ReturnTypeOf<T>
along with specific other aliases if you want to validate a particular set of arguments. They can't do overload resolution but we don't think this feature is worth the complexity just for that use case.
@RyanCavanaugh I believe you mean ReturnType<T>
?
@RyanCavanaugh that is unfortunate - the overload resolution is what I really needed. Is there another issue to track adding overload resolution to conditional / infer types?
You should be able to write it:
type Return1<A1, T extends (a: A1) => any> = T extends (a: A1) => infer R ? R : any;
type Return2<A1, A2, T extends (a: A1, a: A2) => any> = T extends (a: A1, a: A2) => infer R ? R : any;
declare function something(a: number): number;
declare function something(a: string): string;
declare function something(a: number, b: string): boolean;
type A = Return1<number, something>; // number
type B = Return1<string, something>; // string
type C = Return2<number, string, something>; // boolean
I haven't tested it though, and you would need a separate helper for each number of args.
@ForbesLindesay: the something
is currently an expression-level variable -- referencing it with e.g. typeof
here (or declaring it as an interface) fixes that. I'm not actually managing to get it to yield appropriate return types though (on 2.8.0-dev.20180318
).
@ForbesLindesay unfortunatly I don't believe that works; the infer mechanism will choose the _last_ method overload:
type Funcs = ((p1: string, p2: string) => void) & ((p1: number) => void);
type FuncPromise1<T> = T extends (p1: infer P1) => void ? (p1: P1) => Promise<[P1]> : never;
type FuncPromise2<T> = T extends (p1: infer P1, p2: infer P2) => void ? (p1: P1, p2: P2) => Promise<[P1, P2]> : never;
let foo: FuncPromise1<Funcs> & FuncPromise2<Funcs>;
However, the infer mechanism _is_ able to deal with tuple unions:
type Tuples = [string, string] | [number];
type TuplePromise1<T> = T extends [infer P1] ? (p1: P1) => Promise<[P1]> : never;
type TuplePromise2<T> = T extends [infer P1, infer P2] ? (p1: P1, p2: P2) => Promise<[P1, P2]> : never;
let foo: TuplePromise1<Tuples> & TuplePromise2<Tuples>;
maybe we need something to allow overload -> object, and function -> object, unwrapping. Do the type mapping and inference there, then wrap back up to a function and overload.
@MeirionHughes:
maybe we need something to allow overload -> object, and function -> object, unwrapping. Do the type mapping and inference there, then wrap back up to a function and overload.
Like (a: number, b?: string) => boolean
-> { a: number, b?: string }
? We can't get param names yet like that, but conceptually this gets tougher for rest params ((a: number, ...b: string[]) => boolean
), also since we can't use object types to do order.
Order probably matters more than names, and we can convert between params and tuples. Spreads/optionals may still complicate it a bit as well.
This reduces the problem to extracting the overloads. Overloads should be an intersection of function types like ((a: number) => 123) & ((s: string) => 'hi')
, so then the problem is how to 'unwrap' an intersection type (into e.g. a tuple type) -- as of now, we don't have that.
I find this path unsatisfying as it'd address the overload use-case yet not say generics, but yeah. Intersection unwrapping was still a gap either way.
Since this issue is now closed, is there a new proposal yet for the parts that are still missing? Like a way to deal with return type depending on arguments?
Since this issue is now closed, is there a new proposal yet for the parts that are still missing?
none that i am aware of.
Like a way to deal with return type depending on arguments?
do not think we have an issue tracking call types.
Is there preliminary support for the idea of just adding type level function application? I could write up a proposal for that. Syntactically I think that's the easiest path.
type MyType<A> = {
foo: A
}
type Wrap = {
<T>(maybe: MyType<T>): MyType<T>;
(maybe: any): MyType<any>;
}
type Naive = ReturnType<Wrap>; // Naive = { foo: any }
type Proposed1 = Wrap(maybe: number); // Proposed1 = { foo: number }
type Proposed2 = Wrap(maybe: MyType<number>); // Proposed2 = { foo: number }
type Proposed3 = (<T>(maybe: T) => MyType<T>)(maybe: number) // Proposed3 = { foo: number }
Edge cases:
const foo = <T>(a: T) => T:
type Edge1 = (typeof foo)(a: number) // Probably trivial?
type Foo = {
<T>(a: string): T
}
type Edge2 = Foo<number>(a: string) // Should this be allowed? Probably not, because:
type Bar<A> = {
(a: string): A
}
type Edge3 = Bar<number>(a: string) // Things are getting strange
interface Baz<A> {
<T>(a: T): T | A
}
type Edge4 = Baz<number>(a: string) // What is this?
Is there preliminary support for the idea of just adding type level function application? I could write up a proposal for that. Syntactically I think that's the easiest path.
Not at the time being. We really do not want to put overload resolution in higher-order-type space; the process is rather involved, and includes multiple passes to infer type parameters, and contextually type arguments, etc.. doing this in higher order is first a lot of work, and second will pose performance challenges that we are not prepared to handle at the time being.
@mhegazy has the team's stance on this changed at all, given the recent work in #24897 ??
There seems to be quite a few issues around whose solutions could be reduced to a $Call
type, and a $Call
type would open the door to a relatively straightforward way of emulating higher-kinded types; see https://gist.github.com/hallettj/0fde5bd4c3ce5a5f6d50db6236aaa39e for example (replace the use of $PropertyType
and $ObjMap
with $Call
). EDIT: Additional example: https://github.com/facebook/flow/issues/30#issuecomment-346674472
Such a feature would arguably be in line with TypeScript's track record of finding a reasonable common solution to many problems, no?
Conditional types now make this largely irrelevant since you can write
ReturnTypeOf<T>
along with specific other aliases if you want to validate a particular set of arguments. They can't do overload resolution but we don't think this feature is worth the complexity just for that use case.
@RyanCavanaugh @mhegazy
I agree that it is possible to do things using conditional types. I think that it would not bring much additional complexity in the compiler if we would rewrite User.avatar
to User extends { avatar: infer T } ? T : never
? So for example we could write
export type Avatar = User extends { avatar: infer T } ? T : never;
as
export type Avatar = User.avatar;
to improve readability.
Suppose we load and transform some data, and end up with a function findUser like this
export function findUser() {
return {
username: 'johndoe',
avatar: {
lg: '1.jpg',
s: '2.jpg'
},
repos: [
{
name: 'ts-demo',
stats: {
stars: 42,
forks: 4
},
pull_requests: [
{ date: '2019-08-19', tags: ['bug', 'agreed-to-cla'] },
{ date: '2019-08-10', tags: ['bug', 'includes-tests'] },
{ date: '2019-08-07', tags: ['feature'] }
]
}
]
};
}
Thanks to the inference from mapped types, we can extract the type from the function like so:
export type User = ReturnType<typeof findUser>;
export type Avatar = User extends { avatar: infer T } ? T : never;
Suggestion: this should evaluate to the same thing
export type Avatar = User.avatar;
Additionally, we could even assert that User.avatar
must not be of type never
.
More examples
export type Repositories = User extends { repos: infer T } ? T : never;
export type Repository = User extends { repos: (infer T)[] } ? T : never;
export type RepositoryStats = Repository extends { stats: infer T } ? T : never;
export type PullRequests = Repository extends { pull_requests: (infer T)[] } ? T : never;
export type PullRequest = Repository extends { pull_requests: (infer T)[] } ? T : never;
export type Tags = PullRequest extends { tags: infer T } ? T : never;
export type Tag = PullRequest extends { tags: (infer T)[] } ? T : never;
export type Repositories = User.repos;
export type Repository = User.repos[];
export type RepositoryStats = User.repos[].stats;
export type PullRequests = User.repos[].pull_requests;
export type PullRequest = User.repos[].pull_requests[];
export type Tags = User.repos[].pull_requests[].tags;
export type Tag = User.repos[].pull_requests[].tags[];
When mapping a nested property in one go, it is not very clear what is happening
export type Tag2 = User extends { repos: { pull_requests: { tags: (infer T)[] }[] }[] } ? T : never;
This would clearify it a lot
export type Tag = User.repos[].pull_requests[].tags[];
export class Hello {
static world = 'world';
world = 42;
}
export type ThisWillBeANumber = Hello extends { world: infer T } ? T : never;
export type ThisWillBeANumber = Hello.world;
export type ThisWillBeAString = (typeof Hello) extends { world: infer T } ? T : never;
export type ThisWillBeAString = (typeof Hello).world;
@lukaselmer It seems like you just want
export type Avatar = User["avatar"];
which works today
@lukaselmer It seems like you just want
export type Avatar = User["avatar"];
which works today
That's exactly what I was looking for. I was searching for it in the documentation, but didn't find it. Thank you!
Is this part of the handbook, or is there any official documentation on how this works? Im pretty familiar myself on how to use it, but when I try to direct people to documentation, all I can find is typeof guards, which is really completely different
So, I have noticed that this proposal bounced around from 2015 and one of the original goals was to somehow get the type of a single property of an interface.
interface a {
foo: bar;
/* more types */
}
const example = (fooNeeded: [magic] a.foo ) => {};
am I correct to assume that this is still not possible 5 years later?
@MFry I think you're looking for this syntax: a['foo']
Do we know if there is a solution for this yet?
I'm trying to get something like this:
declare function something<A, B>(): void;
type Payload = string;
const hello = something<{}, Payload>();
declare function doThing<T extends ReturnType<typeof something>>(arg: T): { payload: unknown };
doThing(hello).payload === 123; // this should validate to a string aka type Payload
https://www.typescriptlang.org/play/index.html?ts=4.0.0-dev.20200512#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwGccBbEDACy1QHMAeAQQBp4AhAPgAoBKALngDccWYAG4AUGIwBPAA4IAClCkQcUYPAC8hDDCrVxYsHgIZ45EBBWbCJMpRq0A3gF9mi5auCcuB0JFgIKOjYePDAOAAq9nQR8CAAHhggqMAE8ABKZMgwqBGyILTScjiINqQUemycsNR8EbzwjvAySipqfGgA1qg4AO74zr6R0RzmljhcAHQtHmqaGloAjABMAMwi8AD0m-AVaQTkOMgQ6vxQEMJQSbs48FDaujR3nfdFCq2eYkA
Hi @maraisr I'm not 100% sure what you are trying to achieve. In your example something
takes two types but does not use them, and hello
is the return value of something which will always be void
; So doThing
never sees the type string
at any point.
Maybe something like below is what you want?
declare function something<ReturnType>(): ReturnType;
type Payload = string;
const hello = () => something<Payload>();
declare function doThing<F extends () => any>(f: F): { payload: ReturnType<F> };
doThing(hello).payload === 'a string';
Ah yeah - so sorry about that. Thank you for the prompt response!! :100: @acutmore
The void
was just to indicate that the returntype of that function is irrelevant. Those 2 types get forwarded onto other generic types, which ultimately get used in arguments.
something like:
declare function something<A, B>(a: MyComplexGeneric<A>, b: B[]): { somethingA: number, somethingB: number };
// Those 2 generics influence the return object so they do get used as such. And the 2 arguments are roughly that. Its an object and an array, more-or-less.
My doThing
function doesn't really care what the first (A
) generic is, but it does care what the second (B
) on is.
See that something
in my own usecase does a sideeffect which get read by the doThing
.
So I can't simply just get the ReturnType of a function - i need to somehow suck out the generic of a created function.
If you feel this query is beyond the scope of this issue, ill continue my journey on StackOverflow!
@maraisr thanks for the extra info.
If you want to doThing
to be able to get the original B
type from something
then it needs to be passed to hello
in someway. TypeScript is only looking at hello
and without some help it won't know that it is the return type of something
.
This is one way that this can be done:
/** Create a type that can store some extra type information **/
interface SomethingResult<T> {
__$$__: T;
somethingA: number;
somethingB: number;
}
declare function something<A, B>(): SomethingResult<B>;
type Payload = string;
const hello = something<{}, Payload>();
declare function doThing<Result extends SomethingResult<any>>(arg: Result): { payload: Result['__$$__'] };
doThing(hello).payload === 1123; // error because `payload` is of type string
interface User {
avatar: string;
}
interface UserData {
someAvatar: User['avatar'];
}
@RyanCavanaugh Why is this closed? Conditional Types don't solve this and many other use cases, and if this gets merged it would make so many things possible.
I'm working on a function that can turn any method call into a "point-free" version (example: [].map(() => n > 5)
turns into map(() => n > 5)([])
and the only thing missing is that Conditional Types and infer
can't detect generics, so in generic functions some types will come out as unknown
.
If I could "call" the functions to get the type ( typeof myFunc(() => Either<string,number>)
) it would be possible to have this functionality (that is currently impossible), and make many other things much easier to do (HKTs, etc...)
Is the complexity very high to be able to $Call
a function (like in flow)? I feel like typescript already does it automatically.
@nythrox we don't feel that the syntactic confusion that this could lead to is outweighed by the cases where you need it to get to some type. The specific case of resolving a call expression is tracked elsewhere; the proposal in the OP of "allow any expression whatsoever" isn't something we think would be a good fit for the language.
@RyanCavanaugh oh okay, I understand. Thanks for the response, do you know what issues are tracking resolving a function call?
I have searched around a bit and have not found an issue for a function call utility type; the only reference to one I found was in #20352, which just linked back to this issue.
The specific case of resolving a call expression is tracked elsewhere
@RyanCavanaugh Mind linking to elsewhere? 🙂
@tjjfvi #37181 is more specically about resolving a function based on its inputs. Might be what you are looking for.
@acutmore That is somewhat along the lines of what I was looking for, though I was specifically talking about a flow-esque $Call
utility, or other syntax to be able to implement such. The approach suggested there is strange, but thanks for the link.
Most helpful comment
Opened a PR at #17961.