Typescript: Proposition : Obtenir le type de n'importe quelle expression avec typeof

Créé le 25 janv. 2016  ·  157Commentaires  ·  Source: microsoft/TypeScript

Mise en œuvre opérationnelle de cette proposition

Essayez-le : npm install yortus-typescript-typeof

Voir le diff : ici .

Scénario de problème

L'inférence de type de TypeScript couvre très bien la plupart des cas. Cependant, il reste certaines situations où il n'existe aucun moyen évident de référencer un type anonyme, même si le compilateur est capable de le déduire. Quelques exemples:

J'ai une collection fortement typée mais le type d'élément est anonyme/inconnu, comment puis-je référencer le type d'élément ? (#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 }));
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
Une fonction retourne un type local/anonyme/inaccessible, comment référencer ce type de retour ? (#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);
}
function augmentAPI(api: MyAPI /* ERROR */) {...}
J'ai une interface avec une forme anonyme complexe, comment puis-je faire référence aux types de ses propriétés et sous-propriétés ? (#4555, #4640)
// 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
}

Pourquoi avons-nous besoin de référencer des types anonymes/déduits ?

Un exemple est la déclaration d'une fonction qui prend un type anonyme comme paramètre. Nous devons référencer le type d'une manière ou d'une autre dans l'annotation de type du paramètre, sinon le paramètre devra être saisi comme any .

Solutions de contournement actuelles

Déclarez une variable factice avec un initialiseur qui déduit le type souhaité sans évaluer l'expression (c'est important car nous ne voulons pas d'effets secondaires d'exécution, tapez simplement l'inférence). Par exemple:

let dummyReturnVal = null && someFunction(0, ''); // NB: someFunction is never called!
let ReturnType = typeof dummyReturnVal;           // Now we have a reference to the return type

Cette solution de contournement présente quelques inconvénients :

  • très clairement un gâchis, peu clair pour les lecteurs
  • identifiant pollution (doit introduire une variable comme dummyReturnValue )
  • ne fonctionne pas dans des contextes ambiants, car il nécessite une déclaration impérative

Solution proposée

_(NB : Cette solution a déjà été suggérée dans #4233, mais ce problème est étiqueté "Nécessite une proposition", et il existe plusieurs autres problèmes étroitement liés, d'où ce problème distinct.)_

Autoriser l'opérande de typeof à être une expression arbitraire. Ceci est déjà autorisé pour typeof expr dans une position de valeur comme if (typeof foo() === 'string') . Mais cette proposition permet également une expression arbitraire lorsque typeof est utilisé dans une position de type en tant que requête de type, par exemple type ElemType = typeof list[0] .

Cette proposition s'aligne déjà étroitement sur le libellé actuel de la spécification :

Les requêtes de type sont utiles pour capturer des types anonymes qui sont générés par diverses constructions telles que des littéraux d'objet, des déclarations de fonction et des déclarations d'espace de noms.

Cette proposition ne fait donc qu'étendre cette utilité aux situations actuellement non desservies, comme dans les exemples ci-dessus.

Syntaxe et sémantique

La sémantique est exactement celle déjà indiquée dans la spécification 4.18.6 :

L'opérateur 'typeof' prend un opérande de n'importe quel type et produit une valeur de type primitif String. Aux positions où un type est attendu, 'typeof' peut également être utilisé dans une requête de type (section 3.8.10) pour produire le type d'une expression.

La différence proposée concerne la section 3.8.10 citée ci-dessous, où le texte barré serait supprimé et le texte en gras ajouté :

Une requête de type se compose du mot-clé typeof suivi d' une expression. L'expression est traitée comme une expression d'identification (section 4.3) ou une expression d'accès à la propriété (section 4.13) une expression unaire dont le type élargi (section 3.12) devient le résultat. Semblables à d'autres constructions de typage statique, les requêtes de type sont effacées du code JavaScript généré et n'ajoutent aucune surcharge d'exécution.

Un point qui doit être souligné (que je pensais être également dans la spécification mais que je ne peux pas trouver) est que les requêtes de type n'évaluent pas leur opérande. C'est vrai actuellement et le resterait pour des expressions plus complexes.

Cette proposition n'introduit aucune nouvelle syntaxe, elle rend simplement typeof moins restrictif dans les types d'expressions qu'il peut interroger.

Exemples

// 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: {}}}
}

Discussion des avantages/inconvénients

Contre : Mauvaise esthétique de la syntaxe. Des syntaxes alternatives traitant des cas individuels ont été suggérées dans #6179, #6239, #4555 et #4640.

Pour : D'autres syntaxes peuvent sembler meilleures pour leurs cas spécifiques, mais elles sont toutes différentes les unes des autres et chacune ne résout qu'un problème spécifique. Cette proposition résout les problèmes soulevés dans tous ces problèmes, et le développeur n'a pas besoin d'apprendre de nouvelle(s) syntaxe(s).

Contre : Une expression dans une position de type prête à confusion.

Pour : TypeScript surcharge déjà typeof avec deux significations, en tant que requête de type, il accepte déjà une expression dans une position de type et obtient son type sans l'évaluer. Cela assouplit simplement les contraintes sur ce que peut être cette expression afin qu'elle puisse résoudre les problèmes soulevés dans ce numéro.

Contre : Cela pourrait être abusé pour écrire d'énormes requêtes longues de type multi-lignes.

Pour : Il n'y a aucune bonne raison de faire cela dans une requête de type, mais il y a de bonnes raisons d'autoriser des expressions plus complexes. Il s'agit essentiellement de l' habilitation contre la réalisation de Martin Fowler.

Impact de la conception, questions et travaux supplémentaires

Compatibilité

Il s'agit d'une modification purement rétrocompatible. Tout le code existant n'est pas affecté. L'utilisation des fonctionnalités supplémentaires de typeof est facultative.

Performance

En regardant le diff, vous pouvez voir que les changements sont très mineurs. Le compilateur connaît déjà les types interrogés, cela les expose simplement au développeur. Je m'attendrais à un impact négligeable sur les performances, mais je ne sais pas comment tester cela.

Outillage

J'ai configuré VS Code pour utiliser une version de TypeScript avec cette proposition implémentée en tant que service de langage, et toute la coloration syntaxique et intellisense est sans faille pour autant que je l'ai testé.

Des expressions complexes peuvent apparaître dans les fichiers .d.ts

L'opérande typeof peut être n'importe quelle expression, y compris un IIFE, ou une expression de classe complète avec des corps de méthode, etc. Je ne vois aucune raison de le faire, ce n'est tout simplement plus une erreur, même à l'intérieur un fichier .d.ts ( typeof peut être utilisé - et est utile - dans des contextes ambiants). Ainsi, une conséquence de cette proposition est que "les déclarations ne peuvent pas apparaître dans des contextes ambiants" n'est plus strictement vrai.

Les types récursifs sont gérés de manière robuste

Le compilateur semble déjà avoir mis en place toute la logique nécessaire pour gérer des choses comme celle-ci :

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;
}
Peut interroger le type de retour d'une fonction surchargée

Ce n'est pas ambigu; il sélectionne la surcharge qui correspond à l'expression de la requête :

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
Suggestion Too Complex

Commentaire le plus utile

A ouvert un PR au # 17961.

Tous les 157 commentaires

Pour tous ceux qui veulent un moyen rapide de jouer avec cela dans VS Code avec intellisense, etc., voici un dépôt de terrain de jeu .

type P = typeof foo(0); // P est n'importe quel []
type Q = typeof foo(vrai); // Q est une chaîne

Je pense que l'utilisation de types comme argument au lieu de valeurs est une syntaxe plus valide.

type P = typeof foo(number);    // P is any[]
type Q = typeof foo(boolean); // Q is string

Il est plus clair que la fonction n'est pas appelée, car vous fournissez des types et non des valeurs comme arguments. L'autre point, c'est qu'il est moins ambigu. Certaines personnes utiliseront typeof foo(false) , tandis que d'autres utiliseront typeof foo(true) . Si vous avez des types comme arguments, les gens ne peuvent écrire que typeof foo(boolean) .

@tinganho exactement !
Bien que nous puissions toujours écrire typeof foo("abc") avec #5185
ici "abc" est le type de chaîne singleton

@tinganho J'ai réfléchi à votre idée et je vois certaines choses que je préfère dans cette proposition, et d'autres choses que je préfère dans votre suggestion. Votre suggestion est bonne pour les raisons que vous avez données (syntaxe plus simple et plus claire, moins ambiguë, ressemble moins à un appel de fonction). Ce que je préfère dans ma proposition, c'est qu'elle n'introduit aucune nouvelle syntaxe, donc n'ajoute aucune complication à l'analyseur/vérificateur, et elle prend également en charge des scénarios plus complexes où vous n'avez pas de noms de type simples pour les arguments.

Je pensais, et s'il y avait une manière très abrégée d'écrire quelque chose comme votre syntaxe foo(number) mais en utilisant les mécanismes d'analyse d'expression existants? Donc, à titre expérimental, j'ai introduit une nouvelle expression : _unary as_. Vous pouvez simplement écrire as T et c'est un raccourci pour (null as T) . Vous dites essentiellement, _'Je me fiche de la valeur mais je veux que l'expression ait le type X'_.

Avec ce changement (que j'ai implémenté dans le dépôt playground ), vous pouvez écrire quelque chose de beaucoup plus proche de votre syntaxe suggérée, mais il est toujours analysé comme une expression ordinaire :

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: {}}}

Ce n'était qu'une expérience rapide. Une syntaxe équivalente pourrait être (mais je n'ai pas implémenté ceci):

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: {}}}

La deuxième syntaxe peut être appelée une _assertion de type nul_, l'expression <T> étant un raccourci pour (<T> null) .

@yortus , nous avons passé du temps la semaine dernière à parler de cette proposition. désolé de ne pas avoir posté plus tôt. le consensus était 1. nous avons un problème de ne pas pouvoir faire référence à certains types, par exemple le retour d'une fonction ou le type d'instance d'une expression de classe. et 2. l'ajout d'expressions dans une position de type n'est pas quelque chose avec lequel nous sommes à l'aise.

La proposition de @tinganho en était une dont nous avons également parlé. Je pense que c'est plus acceptable, même si ce serait probablement plus compliqué à mettre en œuvre. L'ajout d'un nouvel opérateur unaire ou l'utilisation de la syntaxe cast n'est pas vraiment élégant car il suffit d'utiliser les noms de type.

Discuté pendant un bon moment au slog aujourd'hui. "Accepter les PR" ici est "Accepter les PR en supposant que la mise en œuvre ne s'avère pas trop folle"

La proposition de @tinganho semble plutôt bonne (par rapport aux autres options, au moins) et nous aimerions voir un PR provisoire qui implémente cela.

La chose délicate est que nous ne voulons pas avoir un chemin de code complètement séparé pour résoudre le type de retour de f(number) vs f(0) , mais l'algorithme de résolution de surcharge est totalement intégré avec l'hypothèse que cela fonctionne avec un ensemble de _expressions_ plutôt qu'un ensemble de _types_. Mais nous pensons qu'avec un peu de ruse, cela devrait être simple.

Le plan d'attaque de base serait :

  • Développez la grammaire lors de l'analyse typeof pour autoriser des choses qui ressemblent à des appels de fonction, à l'accès aux propriétés et à l'accès aux propriétés indexées
  • Lors de l'analyse des arguments d'un appel de fonction dans une requête de type (appelons-les _psuedocalls_ / _psuedoarguments_), utilisez la fonction parseType . Cela va créer un TypeNode , mais définir un indicateur sur le nœud qui indique qu'il s'agissait d'un type analysé dans le contexte d'une requête de type
  • Dans le vérificateur, checkExpression recherche cet indicateur et appelle getTypeFromTypeNode au lieu du traitement d'expression normal

@mhegazy , @RyanCavanaugh ne sais pas combien de cas critiques l'équipe a discutés, alors puis-je en soulever quelques-uns ici pour plus de précisions ? J'ai énuméré un tas d'exemples ci-dessous et commenté chacun avec ce que je pense devrait être le résultat de l'opération typeof , avec des points d'interrogation sur les cas douteux.


Notation d'indexation
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?

Notation du type de retour de fonction
// 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


Extraction d'une partie d'un type de classe/interface

Autrement dit, les exemples typeof (<MyInterface> null).prop1.big.complex; ci-dessus.

Je déduis des commentaires ci-dessus que cela est hors de portée et ne sera pas pris en charge. Est-ce exact?

Notation d'indexation
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

Notation du type de retour de fonction
// 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

Je suis curieux de savoir ce qui se passe ici :

const number = "number";
type Ret3 = typeof f2(number); // What happens here?

@SaschaNaz bonne question. Une situation similaire :

class MyClass { foo; bar; }
declare function f(inst: MyClass): number;
type Ret = typeof f(MyClass);     // number (presumably)

Dans ce cas, il est logique dans typeof f(MyClass) que le _type_ MyClass soit considéré avant le _value_ MyClass (c'est-à-dire la fonction constructeur). Le premier mène à Ret = number , le second mènerait à quelque chose comme error: MyClass is not a type .

La même logique s'appliquerait-elle à un nom faisant référence à la fois à un _type_ et à une _const value_ ? Dans votre exemple, cela signifierait que le type number aurait toujours priorité sur la valeur const number . Des pensées @RyanCavanaugh?

Bon, nous résoudrions cela sous la sémantique habituelle d'une expression de type (comme si vous aviez écrit var x: [whatever] ). Ainsi, vous pourriez avoir typeof f(MyClass) faisant référence à l'appel f avec le côté instance, et typeof f(typeof MyClass) faisant référence à l'appel f avec la fonction constructeur.

Alors, l'exemple de @SaschaNaz fait référence sans ambiguïté à number en tant que _type_, et non en tant que _const value_, n'est-ce pas ?

const number = "number";
type Ret3 = typeof f2(number); // Promise<string[]>

@RyanCavanaugh pouvez-vous confirmer que le troisième groupe de cas d'utilisation est hors de portée ? par exemple depuis l'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: {}}}
}

Ce cas d'utilisation (quelle que soit la syntaxe) ne sera pas pris en charge pour le moment, n'est-ce pas ?

Je pense que cela devrait être couvert en autorisant les expressions this .

   prop2: typeof this.prop1.big.complex;

Je pense que la chose const devrait être résolue par un autre typeof .

type Ret3 = typeof f2(typeof number); // typeof number is string so error here

... alors que cela bloquerait typeof data[LOBOUND] .

@mhegazy c'est une excellente idée pour typeof this . Je viens de réaliser que cela fonctionne déjà dans l'implémentation fourchue que j'ai faite pour cette proposition. Eh bien, cela fonctionne pour les cours. Pour les interfaces, il n'y a pas d'erreur, mais le type this est toujours déduit comme any . Sortie de courant de l'impl fourchu :

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
}

Est-il possible de déduire this à l'intérieur des déclarations d'interface ou cette fonctionnalité resterait-elle limitée aux classes ?

Je veux faire deux remarques sur #6179 et 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
  1. Le nombre de paramètres peut être important. Disons 15 paramètres. En même temps, il n'y a pas de surcharges, et ce ne sont que les surcharges pour lesquelles les paramètres de typeof sont nécessaires. Donc, pour ce cas, pouvons-nous probablement penser à une syntaxe comme suit ?

    type MyAPI = typeof myAPIFactory(...);
    
  2. La fonction d'usine n'est généralement pas affectée à une variable globale propre. Une expression de fonction est utilisée :

    angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) { /*...*/ });
    

    C'est à ça que ça ressemble habituellement. Comme on peut le voir, typeof ne peut pas du tout être utilisé ici.

@thron0 mon intuition est qu'une chose qui a plus que quelques paramètres mais qui renvoie _également_ un type anonyme va être très rare. Qu'est-ce que tu penses?

Cela deviendra rare lorsque Angular 1 deviendra rare, pas plus tôt.

Fondamentalement, ce que vous avez décrit est ce qu'est une _factory_ angulaire typique :

une chose qui a plus que quelques paramètres mais renvoie également un type anonyme

C'est une fonction qui prend les dépendances comme paramètres et crée une instance d'un _service_. Vous pourriez penser qu'un grand nombre de paramètres peut être un problème, mais le fait est que ces usines ne sont jamais appelées directement. Le conteneur DI les appelle. La fabrique est appelée lorsque quelque chose requiert sa valeur de retour (le service) en tant que dépendance. Et il n'est appelé qu'une seule fois car les services sont toujours des singletons dans Angular. Cependant, un service peut être n'importe quoi, donc si nous avons besoin d'un comportement non singleton, la fabrique peut renvoyer un constructeur (ou une fonction de fabrique). Comme dans cet exemple de code :

angular.module('app').factory('MyAPI', function($http: HttpSvc, id: number) {
    class MyAPI {
        constructor(token: string) {...}
        foo() {...}
        bar() {...}
        static id = id;
    }
    return MyAPI;
});

Voilà à quoi ressemble ma solution de contournement actuelle pour cette situation :

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');
    /* ... */
});

Comme vous pouvez le voir, c'est totalement moche et douloureux. Je dois lister les dépendances trois fois. Si j'ai plus de dépendances, il est parfois plus simple d'écrire la classe de la manière habituelle (à l'intérieur de la fonction factory) et de déclarer une interface pour ses consommateurs.

Salut, je veux proposer de laisser le type de syntaxe/sémantique intact et d'implémenter à la place une simple algèbre d'accès aux types :

Imaginons l'extension de syntaxe de type :

type          ::=  ... |  literalType | type typeAccess | guarded
guarded    ::= "type" id
literalType   ::= stringLiteral | numberLiteral | symLiteral | booleanLiteral 
typeAccess    ::= typeField | typeSubscript | typeCall
typeField     ::= "." id
typeSubscript ::= "[" type "]"
typeCall      ::= "(" [type {"," type}] ")"

Chaque identifiant du périmètre peut être lié simultanément aux deux entités :

  • type de temps de compilation
  • valeur de temps d'exécution

le type garde n'extrait que le premier. Ainsi

class A {}
var a: A;

est équivalent à

class A {}
var a: type A;

dans ce cas si nous aurions une classe

class ABC {
    a: number;
    b(x: number): string;
    c:string[];
    d:[number, string];
}

alors on pourrait écrire

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

Il couvre également la fusion des entités _type_ et des entités _value_ sous le même identifiant dans la portée lexicale.

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 pouvez-vous examiner cette idée ?

Comment capturez-vous le type d'expression avec votre proposition ?

Le jeudi 19 mai 2016 à 12h26, Anatoly Ressin, [email protected] a écrit :

Salut, je veux proposer de laisser le type de syntaxe/sémantique intact et
à la place, implémentez une simple algèbre d'accès aux types :

Imaginons l'extension de syntaxe de type :

taper ::= ... | typelittéral | type typeAccès | gardé
gardé ::= identifiant "type"
literalType ::= stringLiteral | nombreLittéral | symLitéral | booléenLittéral
typeAccess ::= typeField | typeIndice | typeAppel
typeField ::= "." identifiant
typeIndice ::= "[" type "]"
typeCall ::= "(" [type {"," type}] ")"

Chaque identifiant de la portée peut être lié simultanément aux deux
entités :

  • type de temps de compilation
  • valeur de temps d'exécution

le _type_ guard n'extrait que le premier. Ainsi

Classe A {}
un : un

est équivalent à

Classe A {}
un : type A

dans ce cas si nous aurions une classe

classe ABC {
un numéro;
b(x : nombre) : chaîne ;
C chaîne[];
d:[nombre, chaîne] ;
}

alors on pourrait écrire

var a : (type ABC).a ; // nombrevar b: (type ABC).b(nombre); // stringvar c: (type ABC).c[nombre] ; // stringvar d : (type ABC).c[0] ; // numéro

Il couvre également la fusion d'entités _type_ et d'entités _value_ sous la
même identifiant dans la portée lexicale.

interface MÊME {
x : nombre ;
} espace de noms SAME : {
type d'exportation x = booléen ;
export var x : chaîne ;
}
var a : SAME.x // var booléen b : (type SAME).x // nombrevar b : (typeof SAME).x // chaîne

@RyanCavanaugh https://github.com/RyanCavanaugh , @sandersn
https://github.com/sandersn pouvez-vous examiner cette idée ?


Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/6606#issuecomment -220378019

Votre exemple me rappelle qu'un type d'expression pointillée et le type de propriété de type #1295 est une proposition assez similaire. Sauf que l'un est plus statique l'autre plus dynamique.

J'aime aussi l'idée de sauter le type de mot-clé.

   prop2: this.prop1.big.complex;

@mhegazy dans votre exemple, vous utilisez typeof dans un this-expression :

prop2: typeof this.prop1.big.complex;

Puisque nous pouvons le faire aujourd'hui :

someProp: this;

À mon avis, une extrapolation de la syntaxe ci-dessus pour des propriétés plus pointillées est :

someProp: this.prop1.prop2.prop3;

Et pas:

someProp: typeof this.prop1.prop2.prop3;

Et pour continuer avec l'extrapolation - pourquoi ne pas sauter typeof tous ensemble ?

someProps: foo(number)

pourquoi ne pas sauter typeof tous ensemble ?

Une raison à laquelle je peux penser est qu'il deviendrait ambigu de savoir si vous faites référence au côté instance d'une classe ou au côté statique:

class C {}

// Does 'x' have the instance type of 'C',
// or does it have the shape of its constructor?
let x: C;

J'aimerais également faire référence au type d'une propriété dans une interface au sein d'une autre interface. J'ai posté mon exemple sous le numéro 10588 qui est maintenant fermé en faveur de ce ticket.

Ce serait bien d'écrire quelque chose comme ça :

interface Foo {
  bar: string;
}

interface Box<T> {
  container: T;
}

interface FooWithBoxedProps {
  bar: Box<Foo.bar>; // OR: bar: Box<typeof Foo.bar>;
}

ce qui se traduirait simplement par ceci :

interface Foo {
  bar: string;
}

interface Box<T> {
  container: T;
}

interface FooWithBoxedProps {
  bar: Box<string>;
}

Eh bien, certains des scénarios mentionnés précédemment peuvent maintenant être exprimés avec des _types d'accès indexés_ (#11929). Combinez simplement cette fonctionnalité avec l'expression standard typeof et vous obtiendrez quelque chose comme ceci :

interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },

    // prop2 shares some structure with prop1
    prop2: MyInterface["prop1"]["big"]["complex"];
}

et il fonctionne! (actuellement en typescript@next)

Je pense que la prochaine chose naturelle serait d'avoir quelque chose de similaire mais pour les fonctions, certains "types d'appels de fonction" comme celui-ci :

interface FunctionLike{
    (arg1 : number, arg2 : string) : {a : number; b : string;}
}

type ReturnType = FunctionLike(number, string); // {a : number; b : string;} 

afin que nous puissions le combiner naturellement avec l'opérateur typeof

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}

Et il semble que c'est la syntaxe qui a déjà été proposée au début de ce fil (@tinganho). :)
Mais ce serait une fonctionnalité plus générale qui composerait bien avec l'opérateur typeof actuel, tout comme les _types d'accès indexés_ le font maintenant.

Cependant quelques questions/doutes subsistent :

  • Cette fonctionnalité gérera-t-elle d'autres scénarios comme le font les _types d'accès indexés_ ? Si ce n'est pas le cas, vaut-il la peine de le mettre en œuvre ?
  • Je suppose que dans la plupart des cas, il serait bien de ne pas avoir besoin de spécifier du tout les types d'arguments, car le scénario le plus courant est "Je veux juste obtenir le type de cette fonction qui n'a pas d'autres surcharges". un nouvel opérateur spécifique pour cela serait bien (par exemple typeofreturn ou returnof )

Cela semble techniquement faisable. Cela peut nécessiter une refactorisation de la résolution de surcharge / de l'inférence d'argument de type afin que vous n'ayez pas besoin de résoudre les arguments. Je ne pense pas que les génériques posent trop de problème supplémentaire. Une question à se poser est de savoir ce qui se passe si aucune surcharge ne correspond à l'appel.

Je pense qu'il n'est pas évident que cette fonctionnalité soit souhaitable. Si une fonction renvoie un type anonyme très élaboré, ne serait-il pas plus utile en tant qu'auteur de nommer le type, afin qu'il n'ait pas à être référencé de cette manière ? J'avoue que c'est juste basé sur une préférence stylistique. Je suppose que je ne sais pas à quel point ce sera pratique à utiliser. Je pense que c'est bien pour l'accès aux éléments.

type P = typeof foo(0); // P est n'importe quel []
type Q = typeof foo(vrai); // Q est une chaîne

Dans cette discussion sur l'autorisation de référencer les paramètres par leurs types, je suggérerais d'autoriser la même chose pour les fonctions réelles également.
Contexte : j'aimerais taper une fonction à map sur des objets :
function map<T, F extends Function>(fn: F, obj: T): { [P in keyof T]: typeof F(T[P]) }
La raison pour laquelle j'aimerais pouvoir faire référence à la fonction par son type F (à part son nom fn ) serait pour les cas où son nom pourrait ne pas être disponible, par exemple I' J'aimerais pouvoir dire type MapFn<T, F extends Function> = { [P in keyof T]: typeof F(T[P]) } .

Quelque chose qui serait probablement beaucoup plus facile à mettre en œuvre (et toujours très utile) serait :

const myFunc = () => ({ x: 10 });
type myType = returnof myFunc;    // { x: number; }

En théorie, ils pourraient également être enchaînés si vous vouliez obtenir le type à quelques niveaux de profondeur. Des pensées?

Edit : Je viens de réaliser que cela a été mentionné ci-dessus par @mpawelski 😄

@dehli Cela ne fonctionnerait pas pour les fonctions surchargées. Ou des fonctions génériques où un paramètre de type est utilisé dans le type de retour.

@JsonFreeman Impossible de surcharger les fonctions juste OR les différents types de retour? Et les fonctions génériques pourraient vous obliger à spécifier le type ?

Ce serait possible, mais je ne sais pas à quel point cela serait utile. Il semble que les gens veulent quelque chose de plus sophistiqué.

J'espère vraiment que cela sera mis en œuvre bientôt car c'est une vraie douleur.

solution de contournement

Comme la dernière solution de contournement ne fonctionne plus, j'utilise actuellement ceci:

// 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;

// ...

Vous n'avez pas besoin de spécifier d'arguments s'il n'y a pas de surcharge de votre fonction.

const hello = (arg: number) => 'World'

const helloReturnValue = returnof(hello)
type helloReturnType = typeof helloReturnValue // string

Fonctions surchargées

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

Toutes les définitions sont ici :
https://github.com/kube/returnof/blob/master/lib/returnof.d.ts

Je viens de créer un petit package NPM pour le regrouper facilement sans polluer votre projet.

Si des génériques facultatifs sont ajoutés comme proposé dans https://github.com/Microsoft/TypeScript/issues/2175 , cela permettra une simple solution de contournement uniquement déclarative :

type Return<T extends () => S, S> = S

Et utilisez-le comme ceci :

type helloReturnType = Return<typeof hello>

@mhegazy @JsonFreeman Avez-vous prévu cette fonctionnalité ?

Salut tout le monde,
J'ai une solution alternative possible à ce problème (désolé si cela a déjà été suggéré et que je l'ai manqué) - je l'ai proposé dans le numéro 13949. Je ne connaissais pas le type d'opérateur à ce stade, mais je pense que ma solution est plus générale. Fondamentalement, introduisez une nouvelle syntaxe quelque chose comme =MyType , qui peut être utilisée partout où vous utiliseriez MyType , mais au lieu de déclarer l'objet comme étant de type MyType , il crée un type nommé MyType partir du type déduit de l'objet. Par exemple, voici comment je l'utiliserais pour créer un composant Vue.

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

Notez que la fonction createData peut également être écrite

function createData(): =MyData {
  return {
    dataProp1: <string>null
  }
}

J'ai trouvé un travail vraiment amusant qui se généralise à n'importe quelle expression:

const varWithRightType = (false as true) && some.deep.access()
type MyType = typeof varWithRightType;

EDIT : ARRÊTEZ DE RIRE DE MOI, C'EST SÉRIEUX

@johnfn cela a l'air amusant pour les cas simples :), mais si votre fonction nécessite des paramètres, vous devez faire un travail supplémentaire comme ceci :

const stateProps = (false as true) && mapStateToProps({} as any);

J'utilise un travail différent, particulièrement utile pour React & Redux, lorsque j'essaie d'utiliser l'inférence de type pour obtenir le type d'accessoires à partir de la fonction mapStateToProps . Ensuite, il n'est plus nécessaire de déclarer et de maintenir manuellement l'interface des Props injectés par Redux connect , ce qui est fastidieux à maintenir et sujet aux erreurs.
Cette solution de contournement gérera également correctement toute signature de fonction donnée pour d'autres cas d'utilisation.

Exemple de cas d'utilisation de l'opérateur typeof "React & Redux" :

L'exemple ci-dessous essaie de montrer un cas d'utilisation courant de projet réel pour l'utilisation de l'opérateur typeof pour dériver un type d'une déclaration de fonction qui serait extrêmement utile s'il était ajouté à 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

La solution de @kube et leur package https://github.com/kube/returnof semblent fonctionner comme prévu ! 👍

Hum. J'aimerais que returnof aide à taper map() basé sur tuple à partir d'un fichier .d.ts , mais compte tenu de sa dépendance au langage d'expression ( const non utilisable dans des contextes ambiants ), je crains que mon cas d'utilisation avec cela ne soit au moins plus compliqué.

Modifier : il semble que les génériques facultatifs soient désormais fusionnés , ce qui signifie que la version uniquement en langage de type déclaratif de @kube ( type Return<T extends () => S, S> = S -> type helloReturnType = Return<typeof hello> ) deviendrait viable. :RÉ

Correction : non, le support fusionné des valeurs génériques par défaut ne semble pas permettre de spécifier une partie des génériques tout en laissant les autres se rabattre sur leurs valeurs inférées ; Return<typeof hello> ne produit que l'erreur Generic type 'Return' requires 2 type argument(s). .

@ tycho01 Ouais, j'ai été déçu que cela n'ait pas résolu le problème.

J'ai déjà ouvert un problème connexe sur l' inférence de types génériques facultatifs :

14400

Vous pouvez généralement obtenir le type de retour d'une fonction via une combinaison d'une fonction ambiante factice et d'une inférence de type :

ambiant function returnTypeOf<RT>(fn:(...rest:any[])=>RT):RT {return void 0};
localement var r = returnTypeOf(someFunction); obtient la valeur undefined

Mais si vous souhaitez ensuite réutiliser ce type de retour, vous devez le capturer ... nous sommes donc de retour là où nous avons commencé :

type RT = typeof r;

Ce serait tellement plus facile si nous étendions initialement le concept de typeof pour autoriser returntypeof ou même mieux déduire cette utilisation de typeof fn(a,b,c,...) , qui pourrait alors capturer différents types de retour de signature . Cette compréhension de type retour est déjà effectuée par TS en interne.

typeofexpression () serait une sorte de récursivité de type retour mélangée à des opérations de type : par exemple

type E = typeofexpression (f(1) + g("x"))

est

type E = typecomprehensionof (typeof f(1) + typeof g("x"))

qui pourrait être compris comme l'un des

type E = typecomprehensionof (string + string) soit string
type E = typecomprehensionof (string + number) soit string
type E = typecomprehensionof (number + number) soit number

À quel point cela est difficile en interne et les coûts de performance me sont inconnus.

ÉDITER ------------------------------------------------- ------------

Je voulais ajouter ... c'est particulièrement important pour quiconque doit utiliser Function.bind, Function.apply, Function.call car ceux-ci renvoient actuellement le type any et cela signifie qu'ils doivent constamment être tapés- annotés pour s'assurer qu'ils ne tombent pas hors du processus de vérification de type.

Pouvoir référencer le type de retour de l'argument de la fonction serait ... du bonheur ...

@poseidonCore

Pouvoir référencer le type de retour de l'argument de la fonction serait ... du bonheur ...

J'aime mieux le typeof <expression> actuel, et le problème du type de retour est déjà traité dans #12342.

J'utilisais simplement typeofexpression et typecomprehensionof comme moyen de différencier ces usages dans l'explication. Je préférerais typeof (expression) comme syntaxe réelle.

Mon point était que comprendre le type d'une expression nécessiterait de comprendre le type de retour d'une fonction ... f(1) est aussi une expression. #12342 est lié de cette façon. Ils ne sont pas mutuellement exclusifs.

Nous pouvons déjà faire typeof pour les variables et donc puisqu'une expression est une opération sur des variables et des fonctions, la prochaine exigence est de pouvoir retourner le type d'une fonction ... puis comprendre le résultat en fonction des règles de type .

En fait, bon point. Le problème # 12342 veut est un moyen d'accéder au type de retour comme une sorte de chose semblable à une propriété pour les types génériques, donc j'ai mal compris la relation.

Que diriez-vous d'utiliser des expressions pour l'appel de la fonction typeof , comme dans la proposition, mais en utilisant des types directement à la place des paramètres ?

Par exemple

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}

Notez que cela n'empêche personne d'utiliser des types de variables à portée locale, en utilisant typeof dans les appels, c'est-à-dire :

type ResultType = typeof myFunction(typeof obj, string, number)
// ResultType is: typeof obj & {test: string} 

Cela semble un peu mieux que la proposition originale, car cela fonctionne dans un contexte ambiant et semble plus flexible en général. Pour moi, cela rend également plus clair le fait que nous n'appelons pas réellement la fonction, mais essayons simplement de renvoyer le type de sa valeur de retour.

Comment je l'ai fait pour mon cas simple:

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

Ce qui précède fonctionne également avec les membres de l'interface elle-même :

declare let myIface: MyInterface;

interface MyInterface {
    prop1: {
        big: {
            complex: {
                anonymous: { type: {} }
            }
        }
    },
    prop2: typeof myIface.prop1.big.complex.anonymous;
}

L'avantage d'utiliser declare let ... au lieu de type ... pour moi est qu'il ne produit rien dans le fichier de sortie .js.

@varadero cela ne résout pas le problème actuel, qui récupère le type de retour d'un appel de fonction. Tout ce que vous faites est de récupérer le type d'une propriété d'un type, qui est une fonctionnalité prise en charge depuis TypeScript 2.2, je crois.

C'est en fait le type d'une propriété d'une (fausse) valeur. Le compilateur ne vous permet pas d'utiliser typeof avec un type ou une interface "pur", il ne fonctionne que sur les valeurs.

Ce serait génial d'avoir!

puisque type KEYOF<T extends any[]> = keyof T[0] existe déjà, typeof T[0]('something') fonctionnerait aussi avec cette implémentation, en supposant que function myFunc<T, K extends KEYOF<T>>(type: K)>{ } et myFunc([(r: string) => r]) (renverrait une chaîne pour typeof T[0]('something') ) ?

Ce serait puissant avec le polymorphe this .

Il est temps que cela se produise !

En relisant ce fil, j'ai essayé de comprendre ce que nous faisons avec la syntaxe typeof et pourquoi.

Évidemment, dans le cas trivial typeof foo déjà implémenté, cela donne le type de foo . (J'imagine que pet is Fish est immunisé contre l'utilisation d'une syntaxe spéciale.)

Dans ma lecture actuelle de ce que ce mot-clé devrait faire dans la proposition actuelle, le mot-clé lui-même ne fait pas plus que ce qu'il fait maintenant, et la proposition actuelle n'est en fait pas liée au mot-clé typeof .

J'en parle parce que c'est important dans le cas que j'ai mentionné ci-dessus, où nous appliquons une fonction stockée en tant que type (que ce soit via type ou en tant que générique), plutôt qu'en tant que valeur.

Compte tenu de cela, je suppose que, alors que typeof fn<A>(string) a besoin typeof pour lever la variable de niveau expression fn à utiliser au niveau du type, Fn<A>(string) d'autre part , avec Fn comme générique contenant une fonction, n'exigerait pas cela, et pourrait donc être "appliqué" de manière à obtenir son type de retour approprié ici sans avoir besoin de typeof .

Dans cette interprétation, nous vérifierions les appels de fonction après tout type de fonction potentiel : à côté typeof fn(...) / typeof fn<...>(...) également Fn(...) / Fn<...>(...) , si ce n'est même pas la fonction littéral ((foo: Foo) => Bar)(Baz) / + génériques. Sinon, le plan d'attaque devrait rester intact.

Peut-être que j'interprète mal la façon dont vous voyez cela, peut-être que les fonctions stockées dans des types n'avaient même pas été prises en compte (car je n'en ai trouvé aucune mention). Quoi qu'il en soit, je me suis dit que cela valait la peine d'être confirmé.

Si l'annonce de l'application de la fonction devient une autre surcharge sémantique pour typeof , en plus de désambiguïser les constructeurs de classe et de relever les variables au niveau de l'expression au niveau du type (à part le niveau d'expression typeof ), les choses semblent progressivement plus alambiquées, comme déjà indiqué par quelques questions précédentes dans ce fil.

Edit : un type générique peut déjà renvoyer un type de fonction, qui peut également avoir des génériques. Cela signifie qu'une autre permutation serait GetFn<A><B>() , avec le premier ensemble générique appartenant à l'invocation de type générique, le dernier appartenant à l'invocation de fonction. Pas GetFn<A><B><C>() cependant, même si GetFn<A><B>()<C>() serait également légitime. La conclusion précédente reste cependant inchangée : n'importe quel type peut contenir une fonction et donc (potentiellement) être appliqué en tant que fonction.

Edit 2 : Je viens de réaliser qu'il y aurait une ambiguïté malheureuse dans X<Y>() . Maintenant, X serait un type renvoyant un type de fonction.

  • Si X n'est pas générique, c'est clair -- <Y> appartient à l'appel de fonction.
  • Si X est générique et nécessite le paramètre, cela est également clair -- il appartient au type et la fonction ne reçoit aucun paramètre de type.
  • Si X a un générique optionnel cependant, cela devient ambigu.

    • Dans une interprétation, X reçoit un paramètre de type, alors que la fonction ne l'est pas.

    • Dans une autre interprétation, X est laissé pour utiliser son paramètre de type par défaut, tandis que <Y> paramétrerait l'appel de fonction à la place.

Je ne sais pas encore quelle serait la meilleure solution ici.

  • Une option serait de forcer les invocations de type en s'appuyant entièrement sur les paramètres de type par défaut pour utiliser explicitement le <> vide plutôt que de permettre également de l'ignorer. Cependant, ce serait un changement radical.
  • Une alternative serait d'imposer une telle restriction sur l'invocation de fonction au niveau du type à la place - comme ce serait nouveau, aucun changement de rupture ne serait introduit. Cependant, cette approche serait inélégante dans la mesure où elle introduirait une asymétrie entre la syntaxe d'invocation de fonction entre les langages d'expression et de type, ce qui pourrait la rendre moins intuitive.

@icholy : oui, ces modifications ne font qu'ajouter à votre point de vue, je sais.

Edit 3 : d'accord, cette fonctionnalité l'emporte désormais sur le haut de ma liste de souhaits. Aucune autre mise à niveau ne se rapproche même à distance en termes d'impact pour l'inférence.

wat

@icholy : bref, si la 'fonction' est dans un générique/type, écrirait-on quand même typeof ?

Si cela est fait pour qu'il interagisse correctement avec les surcharges, cela vous donne en fait des fonctions "variadiques" gratuitement (elles sont juste curry au lieu d'avoir> 1 arité), puisque vous pouvez définir de manière récursive le type de retour d'une fonction en termes de l'application de l'une de ses surcharges, avec une certaine surcharge du cas de base. C'est ainsi que les choses se font dans Haskell, et ce serait une fonctionnalité merveilleuse à avoir dans TypeScript.

@masaeedu : ça a l'air intéressant. Et oui, les surcharges sont certainement ce qui rend cette proposition si intéressante - elles pourraient être utilisées pour faire correspondre les modèles sur différentes options, any fallbacks inclus. Les vérifications de type comme celle-ci n'étaient pas encore possibles au niveau du type.

J'admets que j'ai utilisé Ramda plus que Haskell. Mais à partir de ce contexte, je pensais que le curry ne se combinait normalement pas bien avec les fonctions variadiques, car le curry aurait besoin de "savoir" s'il fallait renvoyer le résultat ou une autre fonction pour traiter des arguments supplémentaires.

Pourriez-vous peut-être montrer un pseudo-code sur la façon dont vous verriez cette idée fonctionner pour des fonctions variadiques comme Object.assign (en sautant des détails comme & vs. Overwrite comme je l'ai utilisé dans mon PoC pour R.mergeAll ) ?

@ tycho01 J'ai joué avec un code comme celui-ci :

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 est un type de fonction "variadique" dans un sens, en ce sens que vous pouvez lui donner un nombre arbitraire d'arguments hétérogènes et il enregistrera fidèlement les informations de type associées en utilisant la récursivité au niveau du type. Malheureusement, TypeScript n'a pas un bon support pour la transformation et la correspondance de modèles au niveau du type, de la même manière que Haskell.

Si nous devions avoir accès à typeof , je serais capable de résoudre le problème du cas de base pour Pop , puisque returnof(overloadedFunction(T)) me donnerait essentiellement un moyen de faire pattern- correspondance au niveau du type.

@ tycho01 Je ne peux pas le faire précisément pour Object.assign , car comme je l'ai dit, cela ne fonctionne que pour les fonctions curry, mais je vais essayer de créer une fonction curry assign pour vous qui fonctionne à peu près de la même façon.

J'aimerais qu'ils généralisent un peu l'algèbre de type, de sorte que, par exemple, (a: string) => (b: number) => void n'était que du sucre pour Func<string, Func<number, void>> , et (a: string, b: number) => void n'était que du sucre pour Arity<number, Arity<string, void>> , ou quelque chose comme ca. Ensuite, nous aurions juste besoin d'outils à usage général pour transformer les types afin d'obtenir de nombreuses fonctionnalités intéressantes de manière émergente.

Toutes mes excuses pour le triple post. @tycho01 Voici du curry "variadique" 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, donc je suppose que c'est comme avoir une fonction de réduction :), même si cette partie pourrait peut-être devenir plus difficile s'il y avait aussi des arguments non variadiques (?).

J'aime l'idée; Je n'ai certainement pas autant pensé à la direction des interfaces.
Si nous pouvons changer le JS pour les typages, je demanderai peut-être à TC39 de simplement changer Object.assign en un mergeAll non variadique. :RÉ

Ce n'est pas comme si mon itération basée sur l'incrémentation fonctionnait jusqu'à présent avec des fonctions dans la pratique (# 17086) ... mais de toute façon, je suis avec vous, cette proposition aurait un impact plus important que toute autre que j'ai vue.

@ tycho01 Je pense qu'il est peu probable que vous obteniez beaucoup de traction si vous demandez que les fonctions variadiques soient transformées en fonctions d'arité fixe, car il y a un coût d'exécution pour les fonctions curry ou l'application répétée que JS (ou au moins les moteurs et le code JS existants) n'est probablement pas très optimisé pour. Je pense que ce dont nous avons besoin à la place, c'est que TypeScript facilite la création et la déconstruction des types pour les fonctions arité> 1 de manière récursive. Je suppose que tout cela revient à # 5453.

D'autres peuvent trouver intéressant d'apprendre que c++ a une fonctionnalité très similaire : decltype(expression)

Cette fonctionnalité va-t-elle arriver à TypeScript ?
Je préfère fortement la forme proposée originale où l'argument est une expression, mais l'intention de l'équipe TS d'avoir une syntaxe spéciale avec des types dans les positions d'expression complique tout et retarde son intégration dans le langage.
Nous devons garder ce sujet d'actualité.

Je serais vraiment heureux si nous pouvions mettre ne serait-ce qu'un sous-ensemble de cette proposition en œuvre. A savoir, celui-ci seul :
type A = typeof anotherVariable . Je pourrais tout faire avec une seule ligne. interface B extends A , <B & A> : B & A , etc.

Je vois beaucoup de cas d'utilisation pour ce genre de chose avec React. Lorsque vous créez un composant d'ordre supérieur, il est beaucoup plus difficile d'indiquer à la déclaration de classe du composant qu'il s'agit actuellement d'un HOC.

Ce que j'ai fini par faire, c'est créer deux classes. Une classe étend React.Component et ajoute toutes les méthodes supplémentaires, tandis que la classe à l'intérieur de la fonction HOC manipule le render() .

@blindbox type A = typeof anotherVariable fonctionne déjà :

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, tu as raison.

D'accord, j'ai compris pourquoi cela n'a pas fonctionné de mon côté.
Cela ne fonctionnera pas

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.

Ceci cependant fonctionnera.

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!

Oui, le problème est donc qu'il ne peut pas être utilisé dans certains cas d'utilisation, par exemple lorsque vous devez utiliser des arguments de type générique.

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 :

Je préfère fortement la forme proposée originale où l'argument est une expression, mais l'intention de l'équipe TS d'avoir une syntaxe spéciale avec des types dans les positions d'expression complique tout et retarde son intégration dans le langage.

Donc, depuis qu'ils ont écrit cela, nous avons des littéraux pour les chaînes et les nombres, ce qui signifie que les choses deviennent un peu plus floues - cela fonctionnerait dans les deux sens. Le tableau littéral et les objets ressemblent de la même manière à leurs notations de type, jusqu'ici tout va bien.

Nous avons donc également des valeurs/types stockés, par exemple des variables, des génériques, d'autres types. Pour que les choses deviennent utiles, nous ne devrions pas simplement nous contenter d'une partie de celles-ci ici.
Ce qui constitue un bon argument en faveur de leur approche basée sur les types ici, c'est que permettre ce mélange est en quelque sorte gratuit au niveau du type. C'est-à-dire qu'au niveau du type, nous pourrions déjà dire typeof myVar , ce qui signifie que si cette fonction 'application' est ajoutée au niveau du type, nous serions automatiquement en mesure de brancher à la fois les types stockés et les variables régulières .

Je serais intéressé de voir vos réflexions supplémentaires sur l'approche suggérée à l'origine. Certes, cela pourrait servir à exposer un peu plus au niveau du type : opérateurs basés sur JS (pensez ! && || + - * / instanceof ) ainsi que des opérateurs spécifiques à TypeScript (opérateur d'assertion ! ).
La chose à propos de ces opérateurs JS est comme... ils sont assez inutiles au niveau du type tel qu'il est, car leur permettre d'opérer sur des littéraux pour produire les types de résultats littéraux correspondants est actuellement considéré comme hors de portée ( ref ) -- expression -level 1 + 1 donne juste le type number , et similaire pour les autres.
Dans cet esprit, j'ai moi-même été un peu déçu par leur proposition basée sur le type.

Cette fonctionnalité va-t-elle arriver à TypeScript ? [...] Nous devons garder ce sujet d'actualité.

J'ai suggéré cette proposition comme une solution plus générale à un symptôme plus petit ici , mais avec un succès limité.

@ tycho01 Mes arguments pour la variation basée sur l'expression de typeof sont presque les mêmes que ceux initialement indiqués par @yortus :

  • cohérence : typeof existant (en position de type) fonctionne déjà avec des expressions, mais contraint d'accepter un seul symbole. De sorte qu'accepter des types ou des pseudo-appels introduirait une syntaxe beaucoup plus lourde qui est beaucoup plus une autre branche de syntaxe (en plus des expressions et des types).

    • les utilisateurs n'ont pas besoin d'apprendre une nouvelle syntaxe

  • simplicité : TypeScript semble déjà avoir toutes les facilités nécessaires pour implémenter cette fonctionnalité de manière discrète (cela a été prouvé par le PR)
  • complétude : les expressions ont au moins la même expressivité que les types, car nous pouvons toujours utiliser des assertions de type ( (undefined as any as <any arbitrary type can be here>) ), mais parfois le système de types manque de types qui peuvent cependant être exprimés dans des expressions (les types spread et rest ont été introduits après les expressions correspondantes avaient été posées).

Merci @tycho01 d'avoir apporté ce point à promised PR (je suis en fait venu ici après vos commentaires là-bas), cela montre vraiment comment une fonctionnalité aussi simple et générique peut couvrir des scénarios beaucoup plus complexes de manière très élégante sans cuisson dans un comportement très spécifique dans la langue.

Je vois le typeof étendu comme un véritable changeur de jeu pour l'expressivité du système de type, la plupart comme les types mappés/ keyof l'ont fait.

@Igorbek : Merci d'avoir élaboré, je vois d'où vous venez alors. Peut-être que ces deux approches servent alors des cas d'utilisation différents.

Je pense que vous démontrez mieux la valeur actuelle de la proposition originale que le message original ne le fait aujourd'hui, car le TS actuel (ou, eh bien, le 2.3.3 de Playground) peut déjà faire fonctionner bon nombre des scénarios originaux :

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

Je suppose que ces exemples ne plaident plus en faveur des approches d'expression ou de type - la plupart sont obsolètes et une application de fonction triviale serait activée par l'un ou l'autre.

Certes, l'application de fonction basée sur le niveau de type, sur laquelle se concentrent tinganho, l'équipe TS et moi-même pouvons exposer des variables d'expression via typeof , bien que, comme vous l'avez noté, le niveau de type ait certes tendance à être à la traîne par rapport à la fonctionnalité exposée sur l'expression niveau. Cette application de fonction elle-même ainsi que la syntaxe de diffusion que vous avez mentionnée sont évidemment des exemples primaires, et j'aimerais vraiment les voir abordés. Peut-être qu'une grande partie de l'écart actuel pourrait même être abordé ici.

De même, une approche d'expression d'abord permettrait également d'injecter des types en utilisant <MyType> whatever ou whatever as MyType , bien que comme le typeof dans l'approche de type, cela semblerait également être une réflexion après coup : questions sur l'application des fonctions stockées dans les types/génériques, qui constituent probablement la majeure partie de la valeur ajoutée réelle de cette approche basée sur les types (bien qu'elle n'y soit pas mentionnée) - inférence précise pour les fonctions d'ordre supérieur, ainsi que, par exemple, les conditions basées sur les types comme dans ce fil promised .* Pire encore, contrairement au cas d'utilisation de l'OP, ceux-ci n'ont pas de solutions de contournement.

Je pense voir où les idées s'affrontent - les propositions actuelles ont des points de vue contradictoires sur la question de savoir si le mot-clé typeof changerait son expression au niveau de la valeur, par rapport à (selon mon interprétation) en laissant typeof tel quel , mais en exposant la syntaxe d'application de la fonction au niveau du type.

À mon avis, la contradiction est quelque peu accidentelle. Je ne négligerais pas la légitimité de l'un ou l'autre cas d'utilisation; si les deux devaient être implémentés, je pourrais voir un mot-clé supplémentaire évitant les conflits de sémantique. Honnêtement, je suis indifférent à savoir si les mots-clés finiront d'une manière ou d'une autre - je veux juste taper de la merde.

* : Je viens de réaliser que vous utiliseriez probablement des fonctions d'ordre supérieur en référençant les fonctions dans les paramètres par leurs noms de paramètres, plutôt qu'en capturant leurs types dans des génériques.
Il semble que les choses puissent en fait être converties dans les deux sens : typeof aide à passer du niveau de la valeur au niveau du type, tandis que declare let y: x; aide à faire passer les choses du niveau de la valeur au niveau du type. Inélégant comme la solution de contournement de l'OP, mais oui.
Si les types de fonctions étaient introduits via, par exemple, des génériques explicites, je suppose que cela n'aiderait pas - ils seraient au niveau du type sans moyen de les déplacer.
Si cela donne l'impression que j'espère de manière préventive couvrir les bases de la fonctionnalité, c'est probablement parce qu'une grande partie de mes progrès sur les défis de frappe non résolus a été bloquée sur ce module complémentaire (avec une mention notable à 5453). Mais si nous pouvons comprendre ces choses, ça en vaudra la peine.

Edit : J'ai pensé à quelques cas théoriques qui pourraient être exclus dans une approche basée sur l'expression maintenant, bien qu'aucun événement pratique ne me soit encore venu à l'esprit :

  • fonctions transmises via un générique explicitement fourni.
  • fonctions renvoyées par les fonctions paramètres, à capturer dans un générique, c'est-à-dire (en utilisant la syntaxe de la proposition basée sur l'expression) 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 . Ceux-ci pourraient bien sûr être calculés si vous saviez et pouviez fournir les types de paramètres de G , mais autant que je sache, il n'y a aucun moyen d'obtenir cette information jusqu'à présent (à moins que peut-être #14400 ?). Je suppose que cette catégorie inclurait les fonctions opérant sur les fonctions d'usine utilisées dans AngularJS mentionnées ci-dessus.

@Igorbek Je ne comprends pas ce que vous attendez avec cet extrait :

function use<T>(item: typeof someFunctionWithComplexReturnTypeThatUsesGenerics<T>(item)) { ... }

Cela ressemble à une définition circulaire.

@masaeedu désolé, je voulais dire :

(passez à f au lieu de someFunctionWithComplexReturnTypeThatUsesGenerics )

function use<T>(item: typeof f<T>(undefined as any as T)) { ... }

Juste pour élaborer, c'est ce qui ne peut actuellement pas être contourné en introduisant une fausse variable :

const _typeofItem = f<T>(undefined as any as T); // no T here
function use<T>(item: typeof _typeofItem) { ... }

BTW, vient de réaliser que la proposition actuelle est en conflit avec l'opérateur de type de requête de type membre T[K] puisque typeof a une priorité plus élevée :

  • typeof x[K] signifie actuellement (typeof x)[K]K _est_ un type
  • si la variation basée sur l'expression est prise, une ambiguïté serait introduite :

    • typeof x[k] signifie (typeof x)[k]k est un type ; ou

    • typeof x[k] signifie typeof (x[k])k est une expression

Je préférerais en fait typeof (x[k]) parce qu'ils sont sémantiquement équivalents, mais c'est un changement radical à coup sûr.

Lorsque j'ai utilisé les outils de développement de Chrome, j'ai compris que l'opérateur membre avait une priorité plus élevée. Où as-tu trouvé ça?

image

@Igorbek : oui, cela semble être la raison pour laquelle ce type Thing2Type = typeof things['thing-2']; du message d'origine fonctionne déjà.

@dehli : vous comparez le niveau d'expression JS au niveau de type TS - ils ne sont pas identiques. L'un s'exécute dans les navigateurs/nœuds, l'autre dans le compilateur TS.

Personnellement, je trouve étrange que TypeScript résolve le niveau de type typeof avec une priorité plus élevée que l'accès au type indexé. Cela ressemble presque à un bug pour moi, honnêtement. (Je me demande si ce cas est réellement testé.)

@ tycho01 J'ai compris . J'ai supposé que TS utiliserait le même ordre de priorité que JS.

@dehli les niveaux d'expression sont les mêmes, ouais. Au niveau du type, c'est autre chose avec une syntaxe similaire, mais renvoyant un type au lieu d'une chaîne.

Je suppose que la priorité pourrait être le résultat des limites qu'ils avaient envisagées. Je concéderai que ces considérations peuvent ne plus sembler aussi raisonnables si sa fonction doit être élargie.

Sur une autre note, si les deux versions de la proposition devaient être mises en œuvre, les crochets seraient un moyen efficace de garder explicite le niveau auquel nous serions pour cela également. J'ai du mal à trouver des choses qui ne ressemblent pas à des compromis, mais oui.

@Igorbek Est-ce le principal cas d'utilisation restant pour typeof sur des expressions arbitraires ? Si nous obtenons #14400, cela nous donnerait returnof en utilisant des types simples définis par l'utilisateur si je ne me trompe pas.

14400 ne nous donnera pas returnof sous sa forme générique :

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'

Je ne connais pas tous les cas d'utilisation restants, ils ne semblent pas encore découverts, mais les plus imbattables et les plus attrayants pour moi sont :

  • _types mappés conditionnels_ qui sont beaucoup plus puissants que ceux que nous pourrions obtenir avec #12424, car typeof utiliserait une résolution de surcharge standard ; @ tycho01 a montré un exemple élégant dans les types promised PR #17077
  • obtenir des types de retours de fonction/méthode dans le contexte de types génériques (comme je l'ai montré précédemment)

@masaeedu : Bonne question. La version courte est à peu près ce que @Igorbek a dit; pour un peu plus de détails sur les défis concrets résolus, vous trouverez une courte liste dans ma liste des "fonctionnalités principales nécessaires" dans # 16392, où j'ai essayé de lier les défis non résolus aux propositions qui pourraient les permettre.

Ceci comprend:

  • fonctions d'usine telles qu'utilisées par exemple par AngularJS, voir les trois fils liés dans le message d'origine ici. - de manière réaliste, cela devrait convenir étant donné le ReturnType nouvellement intégré.
  • Étant donné les entrées connues, vous pouvez soudainement calculer les types de retour exacts pour des choses comme reduce / map / filter / find -- tout ce qui implique des itérations et des vérifications. stdlib.d.ts en bénéficieraient, tout comme les bibliothèques FP comme Ramda/Lodash. En pratique, toutes les entrées ne sont peut-être pas connues (plus de tableaux de type liste que de tuples), mais les opérations map et filter sur les objets pourraient également être mieux typées que Partial . Cela est nécessaire pour mieux typer les bibliothèques de gestion d'état telles que redux (voir https://github.com/piotrwitek/react-redux-typescript/issues/1) et ngrx d'Angular, qui jusque-là sont forcées de garder leur API utilisateur de bas niveau car sans une opération map bien typée, il n'y a aucun moyen pour eux de calculer le résultat souhaité au niveau du type à partir des données d'entrée de DRY'er.
  • Actuellement, les typages de composition de fonction ne sont pas non plus en mesure de prendre en compte les types de retour dépendant de l'entrée.
  • Cela rendrait soudainement également viable de commencer à taper des choses comme des lentilles. - Je suppose que techniquement, cela n'est encore bloqué ici que sur les opérations d'édition avec des types de retour dépendant de l'entrée. qui est vraiment un luxe. modifier les tuples nécessite toujours (une partie de) # 5453 cependant.
    Il permet également :
  • fonctionnant sur des littéraux booléens au niveau du type, ce qui était jusqu'à présent impossible
  • types de déballage pour les opérations de type flatMap telles que cette proposition promised
  • ajouter des contraintes à l'entrée de type/fonction, le sujet de # 15347 et une idée que j'ai tirée du livre 'Type-driven development with Idris' sur les types dépendants. Cela pourrait aider à dire interdire les diviseurs 0 - essentiellement un hack pour soustraire des types, le sujet de # 4183. Je suis sûr que je n'ai pas encore imaginé la plupart des cas d'utilisation, mais cela pourrait être fait avec ceci, par exemple function div<B extends number, NotZero = { (v: '1') => 'whatever'; }({ (v: 0) => '0'; (v: number) => '1'; }(B))>(a: number, b: B) . Normalement, le langage Idris annonce des opérations vectorielles/matrices de longueur sûre, mais cela n'en a besoin que pour les fonctions d'ordre supérieur.
  • Un moyen d'exprimer les contraintes Union<T> / NonUnion<T> sur les types d'entrée en utilisant les contraintes ci-dessus + IsUnion . - ce type est en quelque sorte tombé en panne et je ne sais plus comment m'y prendre.
  • étant donné # 5453 également, cela aide également les fonctions de type telles que curry , Function.prototype.bind , Promise.all (liste non exhaustive, juste quelques fonctions que d'autres personnes ont soulevées comme difficiles).
  • Logique switch proposée dans #13500, ici résolue par la correspondance de modèle de surcharge de fonction
  • ces types conditionnels mappés qui font l'objet de #12424 et #17077 ( promised ), c'est-à-dire les vérifications de type au niveau du type. Cependant, cela ne le résoudrait pas - même si cela devait être utilisable pour produire des littéraux booléens n'importe où aujourd'hui, jusqu'à ce que ce # 6606 débarque, nous n'avions toujours aucun moyen d'opérer / faire des conditions basées sur de tels littéraux booléens au niveau du type encore en tous cas.
  • vérification de la présence d'index de chaîne sur les types d'objets, de manière à préserver cela à travers les opérations (telles que Omit , Overwrite partir de # 12215), voir mon commentaire dans # 12424
  • Sur la base des vérifications d'index de chaîne ci-dessus, un moyen de corriger l'accès à la propriété de type d'objet de sorte que les vérifications des chaînes correspondant aux noms des méthodes de prototype d'objet (par exemple toString , toLocaleString ) se résoudra plutôt à l'index de chaîne qu'aux méthodes prototypes, corrigeant potentiellement les problèmes liés à l'accès à l'objet toString dans toutes les autres opérations de type qui dépendaient auparavant de l'accès à la propriété d'objet intégrée glitched.

edit : cas d'utilisation barrés remplis par des types conditionnels (#21496) depuis le moment de l'écriture.

La proposition de @Igorbek d'une approche axée sur l'expression serait (au prix peut-être de casser certaines applications inconnues d'une partie de la liste ci-dessus - je n'ai encore pensé à rien de concret même si j'ai peur d'être peint dans un coin ) promettent en outre de combler l'écart entre les niveaux de valeur et de type, qui incluent actuellement des choses comme cette application de fonction (ce #6606), propagation/reste (#5453), opérateur d'assertion ! (#17370), type d'union soustraction via des gardes de type (# 4183, autrement réalisable via les contraintes mentionnées ci-dessus), et peut-être plus, je ne peux pas penser au sommet de ma tête.

Je suppose que tout ce compromis pourrait soulever des questions telles que pourquoi rendre toutes les mêmes fonctionnalités accessibles de deux manières (niveau de type réel, niveau d'expression accessible intégré au niveau de type).

Edit : mise à jour de mon commentaire de comparaison ci-dessus avec un défi potentiel supplémentaire.

Édition 2 :

Il est regrettable ici que le message d'origine veuille vous faire croire que cela ne permettrait d'écrire que certains cas extrêmes sans une solution de contournement null & , alors qu'en réalité c'est la fonctionnalité critique qui retient TS en ce moment sans solutions de contournement connues pour les contextes ambiants .

Cela signifie que cela affecte TS écrit dans des fichiers .d.ts séparés, ce qui est la norme non seulement pour stdlib.d.ts , mais aussi pour tous les typages DT écrits séparément de leurs projets JS d'origine, ce qui est rarement susceptible de changer tandis que les bibliothèques JS veulent également rester ouvertes à des alternatives comme Flow.

(Ce n'est pas beaucoup mieux pour les typages .ts , dans la mesure où les types utilisant la "solution de contournement" de l'OP ne peuvent pas être paramétrés de manière à composer des types réutilisables de niveau supérieur sans influencer le niveau d'expression.)

@ tycho01 Merci pour la liste ; il y a beaucoup de liens là-dedans que j'ai besoin de digérer. Je veux aussi vraiment une correspondance de motifs sur les types, et # 6606 est une bonne solution pragmatique au problème. Cependant, il me semble qu'il y a une confusion croissante entre les choses au niveau de la valeur et du type, et #6606 n'améliorera pas les choses dans ce domaine.

La raison pour laquelle cette fonctionnalité est nécessaire est que nous n'avons aucun moyen de construire des expressions de type qui correspondent à :

  • le type de retour d'un type de fonction, lorsqu'il est appliqué avec des arguments de certains types
  • (avant typeof K[L] ) le type d'une propriété sur un type d'objet qui correspond à une clé d'un type littéral de chaîne
  • (peut-être d'autres, je dois revoir votre liste de plus près)

J'ai l'impression qu'il devrait en fait être possible de construire des expressions purement au niveau du type pour celles-ci, sans avoir recours au mélange de constructions au niveau de la valeur et d'expressions au niveau du type. Ce serait bien si des types comme (a: A) => B ou A | B étaient du sucre sur des types paramétrés simples comme Func<A, B> , Union<A, B> , et nous avions des outils généraux pour manipuler les types paramétrés (HKTs, fundeps ou familles de types, etc.).

Je sais qu'une concentration excessive sur la solidité n'est pas l'un des objectifs de TypeScript, mais il existe maintenant de nombreux concepts au niveau du type qui interagissent ensemble de manière opaque. Une sorte de formalisation de ce qu'est un type, et une façon de prescrire des règles mécaniques sur la façon dont un type interagit avec d'autres types (sous-typage, déstructuration, etc.) iraient loin.

J'ai l'impression qu'il devrait en fait être possible de construire des expressions purement au niveau du type pour celles-ci, sans avoir recours au mélange de constructions au niveau de la valeur et d'expressions au niveau du type.

Oh, oui, personnellement, je n'avais pas vraiment l'intention de recourir à des constructions de niveau valeur - c'est tout ce que j'essaie. Si le niveau de valeur était indispensable, j'aurais peut-être été moi-même dans le camp basé sur l'expression ici. :P

Je suppose que l'écart entre les niveaux de valeur et de type devrait généralement se réduire (le TC39 se déplace-t-il plus vite que le TS ?), Et autant que je sache, les écarts ont déjà une proposition de niveau de type exceptionnelle (voir le bas de mon article précédent).

Heck, je me rends compte que la construction de typages pour certaines fonctions va être en dehors de l'expérience de la plupart des utilisateurs.
La façon dont je vois cela est que je veux que la bibliothèque standard et les bibliothèques FP soient si bien typées que les utilisateurs de TS puissent simplement les écrire et que l'inférence soit automatiquement prise en charge pour eux.
TS n'a que quelques opérateurs au niveau du type, mais résoudre des problèmes réels avec eux (le meilleur exemple est Overwrite / Omit #12215) pourrait bien sembler un peu sorcier pour les développeurs Web occasionnels. Heck, cela nous a pris jusqu'à récemment aussi, et ils ne sont même pas encore à l'épreuve des prototypes / index / symboles.

Ce serait bien si des types comme (a: A) => B ou A | B étaient du sucre sur des types paramétrés simples comme Func , Union

Nous pouvons le retourner et créer les types paramétrés en tant qu'alias / constructeurs de types. Pour une opération de type prenant un Foo<Bar> , peu importe si votre chose se simplifie en un - il vérifie simplement s'il correspond à la description.
C'est à peu près ce que fait stdlib.d.ts -- vous avez un foo[] , mais il satisfait la description Array<Foo> et fonctionne donc avec ses typages Array.prototype .
Ce n'est pas comme ça qui aide vraiment:

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

Donc, euh ouais, je n'ai pas résolu le problème, mais je viens de réaliser que le n ° 14400 de kube pourrait réellement aider là-bas. Et je viens de vous voir là-bas plus tôt aujourd'hui aussi!
Même chose pour les fonctions en fait - ce qui est un bon rappel que # 14400 pourrait non seulement faire des types de retour de fonction, mais aussi des types de paramètres.

Cette approche nécessiterait pour le moment d'utiliser des surcharges, ce qui est regrettable car elle ne s'adapte pas, mais oui. Pour le rendre à nouveau générique, nous utiliserions essentiellement ces surcharges de correspondance de modèle #6606 pour déclencher la bonne option pour différentes arités.
Je suppose que l'on pourrait l'utiliser pour les convertir de manière générique en types de tuples, puis les parcourir en utilisant mon approche d'incrémentation pour les opérer d'une manière ou d'une autre.
Pour les syndicats, j'espérais une meilleure façon de convertir en types de tuple. Cela ajouterait cependant un ordre arbitraire, et ne peut pas non plus penser à une jolie syntaxe/mot-clé.

Modifier : pour revoir ces lacunes de fonctionnalité au niveau de l'expression/du type :

  • application de la fonction : ce #6606
  • opérateur d'assertion ! (#17370) : après ce #6606, ceci est résolu
  • soustraction de type union via des gardes de type (#4183) : juste un cas général de ! ci-dessus, avec ce #6606 également réalisable via des contraintes (par exemple, NotZero ci-dessus).
  • spread/rest (#5453) - même si les tuples ne peuvent pas être manipulés jusqu'à ce que cela atterrisse, les ArrayLike similaires le peuvent, voir les opérations List dans my gist . avec ce # 6606, je pense maintenant que nous pourrions extraire les paramètres des fonctions non appliquées, bien que les extraire au moment de l'application (c'est-à-dire pour obtenir leurs valeurs d'entrée précises), nécessiterait toujours 5453.

En bref, si cette proposition devait atterrir, je dirais qu'un écart de fonctionnalité entre les niveaux d'expression et de type ne constituerait pas un très gros argument en faveur de la proposition à saveur d'expression ici. Si 5453 étaient dedans aussi, je ne pouvais plus penser à aucun là-bas. Et notez que pour de rares exceptions, la solution de contournement indiquée dans le message d'origine ici serait toujours valable.

Maintenant, un argument qui peut encore être facilement avancé serait qu'avec la variante à saveur d'expression, avant même que le niveau de type ne rattrape son retard dans les opérateurs de mise en miroir, cette fonctionnalité serait exposée sous la même syntaxe que dans JS (sans solutions de contournement), réduisant la courbe d'apprentissage.

Édition 2 :

Je viens de réaliser que l'astuce de la proposition d'expression consistant à amener les types au niveau de l'expression, 1 as any as MyType , devrait logiquement fonctionner également pour la fonction elle-même.

Cela signifie probablement que la fonctionnalité réelle activée par les deux saveurs semble quelque peu similaire, les différences extérieures consistant principalement en typeof myVar (saveur de type) par rapport myVar (saveur d'expression) pour utiliser des variables dans l'application de la fonction ; pour utiliser des types en eux MyType (saveur de type) vs. 1 as any as MyType (saveur d'expression, alternative declare let a: any; puis <MyType>a ).

Les modifications AST de l'un ou l'autre semblent également assez gérables. La saveur de l'expression a juste besoin de la conjonction typeof pour pointer vers une expression de valeur à la place ; la saveur de type copierait la syntaxe d'application de fonction existante ( fn<T>(arg) ) de l'expression au niveau du type, en s'accrochant à l'implémentation existante comme proposé par Ryan ci-dessus.

Je pense que cela se résume alors à ceci :

Etui pour saveur d'expression :

  • typeof expr avec la syntaxe JS sans solution de contournement avant la prise en charge du niveau de type TS

Cas pour saveur de type:

  • aucun changement de rupture sur la priorité
  • expressions de valeur toujours capturables via une solution de contournement (ou via TS si sous une syntaxe différente jusqu'à ce que les opérateurs rattrapent)
  • MyType au lieu de 1 as any as MyType : pas de niveau de type dans votre niveau d'expression dans votre niveau de type dans votre niveau d'expression.

Un sujet connexe jusqu'ici laissé intact ici a été de savoir comment fournir des liaisons this dans cette fonction de niveau type "application". Maintenant, JS délègue cela aux méthodes Function.prototype , mais dans l'état actuel des choses, celles-ci n'ont pas de moyen de gérer cela au niveau du type.

Syntaxe d'exemple aléatoire, étant donné un type de fonction F (this: Foo, a: number, b: string) => SomeReturnType qui aurait autrement pu être appelé comme F(MyA, MyB) : F(this: MyFoo, MyA, MyB) .

Le calcul du type de retour sans écraser la liaison this serait toujours comme F(MyA, MyB) , reflétant la façon dont l'argument de niveau type this est normalement ignoré si vous essayez d'utiliser une telle fonction à le niveau d'expression.

Avantages de cet exemple de syntaxe :

  • reflète la syntaxe de déclaration

Inconvénients de cet exemple de syntaxe :

  • reflète la syntaxe de déclaration

Donc, il s'avère que c'est déjà dans la langue !

Ne soyez pas trop excité.

@DanielRosenwasser vient de m'indiquer # 12146, un bogue qui permet d'utiliser des appels de fonction sur des littéraux à la place des types.

_5 minutes plus tard_

Et voilà! Une horrible chose maléfique que nous ne devrions jamais utiliser dans la production. Mais c'est tentant...

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"));

~Cela rend les Effort: Difficult sur ce problème un peu douteux, on dirait qu'ils l'ont fait par accident là-bas.~ J'en lis plus et je me sens idiot, c'est difficile à faire correctement.

Amusez-vous @tycho01.

@TheOtherSamP J'ai essayé cela avec TypeScript 2.4.2 et tous ces types sont supposés être any .

@pelotom Huh, ça marche ici sur 2.4.2 et 2.5.0-dev.20170803. Cible es6 et mode strict.

image

On dirait qu'ils viennent de le réparer, je crains que ce ne soit de ma faute. #17628

@TheOtherSamP Non, pas de dés. Tant pis.

@pelotom C'est étrange, cela fonctionne dans un projet complètement nouveau pour moi, je ne sais pas ce qui serait différent dans nos configurations.

@TheOtherSamP : haha, c'est assez drôle.
En ce qui concerne le temps, il semble qu'ils aient commencé le correctif un peu avant votre commentaire. Tant pis.

@pelotom :

J'ai essayé cela avec TypeScript 2.4.2 et tous ces types sont supposés être quelconques.

Son extrait semble fonctionner dans Playground (2.3.2). Sur une version récente en dehors de celle-ci ( ^2.5.0-dev.20170626 ), j'ai aussi du mal à reproduire.

Cela rend le Effort: Difficult sur ce problème un peu douteux, on dirait qu'ils l'ont fait par accident là-bas.

Ils faisaient référence à une implémentation basée sur le type, ce qui signifierait quelques changements, alors que cela semble utiliser un langage d'expression (-> + , Object.assign , autres appels de fonction).

@ tycho01 Je _pense_ que tout a commencé en attirant l'attention dessus dans # 17618. Eh bien, cela m'apprendra à envisager sérieusement de l'utiliser en production.

Ils faisaient référence à une implémentation basée sur le type, ce qui signifierait quelques changements, alors que cela semble utiliser un langage d'expression (-> +, Object.assign, autres appels de fonction).

Ouais, j'étais un idiot et je n'ai pas lu tout ce numéro avant d'avoir dit ça. C'est dommage, c'est probablement une meilleure version de la fonctionnalité, mais j'aimerais que nous puissions l'avoir maintenant. Je repousse beaucoup les limites du système de type, et ceci ou #12424 ouvrirait tellement d'options.

A ouvert un PR au # 17961.

@yortus Cela couvre-t-il le cas de 'typeof literal'?
Aujourd'hui, TypeScript ne permet pas d'écrire "const x: typeof 1 = 1;"

@NN--- la proposition originale couvre toutes les expressions, mais si je comprends bien, la partie "approuvée" ne couvre que l'accès à la propriété et les types de retour de fonction.

Même si typeof 1 était autorisé, je ne sais pas si cela donnerait le type littéral ( 1 ) ou le type plus large ( number ).

Aujourd'hui, TypeScript ne permet pas d'écrire "const x: typeof 1 = 1;"

Pourquoi pas const x: 1 = 1; ?

@SaschaNaz Je voulais écrire quelque chose comme

const a = {q:1};
const b = {q:1};
const x: ReadonlyArray<typeof a> = [a,b];

Mais similaire ne fonctionne pas avec les littéraux :

const x: ReadonlyArray<typeof 1> = [1,2,3];

@yortus Bon point sur le type exact. Je n'ai pas pensé aux types littéraux ..

@NN--- : je crois que votre exemple fonctionne déjà.

@ tycho01 Flow a maintenant le type $Call pour obtenir le type de fonction de retour https://github.com/facebook/flow/commit/ac7d9ac68acc555973d495f0a3f1f97758eeedb4

Autoriser seulement typeof fn(...) ne serait-il pas la même chose que d'autoriser typeof d'une expression arbitraire ?

function fn() {
  return /** whatever expression you like */;
}
type a = typeof fn();

sauf que vous créez maintenant une fonction d'exécution dans le seul but de déterminer un type ?

Pas vraiment. Vous faites une évaluation de type d'une expression, pas une exécution de l'expression.

@dyst5422 typeof fn() n'évaluerait pas réellement l'expression, cela vous donnerait simplement le type de retour.

EDIT : c'est peut-être ce que vous essayez de dire, pas sûr. Mais je pense que @ sky87 parlait de _définir_ une fonction sans autre but que de l'utiliser dans une expression de type, pas de l'évaluer.

@dyst5422 , comme l'a dit @pelotom , je ne voulais pas dire que vous exécuteriez la fonction. Pour en savoir plus : si vous n'autorisez pas le typeof des expressions arbitraires, mais que vous autorisez le typeof du type de retour d'une fonction, ce que je ferai pour déterminer le type de plus compliqué expressions est de les envelopper dans une fonction afin que je puisse demander son type de retour. Cela crée une fonction au moment de l'exécution, mais juste pour obtenir son type de retour, et c'est plus passe-partout à écrire.

EDIT : vous pouvez déjà déterminer le type d'expressions arbitraires, c'est moche mais ça marche

const dummy = (false as true) && /* arbitrary exp */;
type TypeOfExp = typeof dummy;

Honnêtement, je ne sais pas quel hack je préférerais. Je pense que la meilleure chose serait de pouvoir demander directement le type en utilisant typeof .

Ah, je suis maintenant. Oui, je pense que la meilleure façon de procéder serait de pouvoir l'utiliser comme

type TypeOfExp = typeof (
  false &
  "false" &
  0
)

être capable de faire arbitrairement une évaluation de type d'expression

Sera-t-il possible d'interroger le type de retour d'une invocation new ? Mon cas d'utilisation : je souhaite écrire des annotations de type pour une fonction qui accepte une référence à toute implémentation PromiseConstructorLike (par exemple, $q ou Bluebird) et renvoie une Promise construite par cette implémentation.

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

Sera-t-il possible d'interroger les types de retour sans typeof , ou devrons-nous null as FnType ?

interface Fn {
    (a: string): string;
    (a: number): boolean;
}
type Ret = Fn(number); // Ret = boolean
type Ret = typeof (null as Fn)(number);

Désolé si ces questions ont déjà été répondues; Je n'ai pas pu les trouver.

Quel est l'intérêt de new ici, ne voudriez-vous pas simplement typeof implementation() ?

Non, car implementation() n'est pas un appel valide. PromiseConstructorLike ne peut être appelé que via new , selon ses déclarations de type. typeof implementation() est une erreur de type, tout comme (typeof implementation)['foobar'] serait une erreur de type.

Lien aire de jeux

Est-il possible d'introduire des types génériques déductibles comme ce que FlowType a fait ? Au moins, cela peut résoudre le problème d'obtention du type de valeur de retour des fonctions.

type _ExtractReturn<B, F: (...args: any[]) => B> = B;
type ExtractReturn<F> = _ExtractReturn<*, F>;

@Cryrivers : voir #14400 pour cette approche. Cependant, cela ne résout pas le problème où le type de sortie dépend de l'entrée.

J'ai fini par en avoir besoin à nouveau aujourd'hui pour indiquer ce qu'un appel à une fonction renverrait dynamiquement, je souhaite bien sûr qu'elle obtienne la priorité.

Un opérateur ReturnType<T> est ajouté à lib.d.ts dans TS 2.8, alimenté par des types conditionnels.

Comme ReturnType<T> n'a pas encore pris en compte les types de retour dépendant des types d'arguments, pour référence, voici l'implémentation du type $Call de Flow.

edit: désolé @goodmind , je n'avais pas réalisé que vous aviez déjà lié exactement à cela.

J'ai mis à jour mon article précédent sur les cas d'utilisation de cette proposition (ou son interprétation d'appel de type) sur la base des récents ajouts de TS.
Les cas d'utilisation de correspondance de modèles sont maintenant couverts par #21496, laissant... les cas où nous voulons calculer des types basés sur les lambdas fournis par l'utilisateur, par exemple curry , composition de fonctions, map , reduce , montage de lentilles basé sur un lambda... le truc amusant. :)

PS @thorn0 : Je pense que votre cas d'utilisation angulaire peut être rempli de ReturnType (#21496) maintenant !

Je pense que cela devrait être couvert en autorisant ces expressions.
prop2 : type de this.prop1.big.complex ;

@mhegazy Existe-t-il un problème distinct pour le suivi de cela ?
Il est ennuyeux que ce type de travail fonctionne pour les locaux mais pas pour les propriétés, tout en travaillant pour les propriétés statiques.

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--- vous pouvez toujours utiliser des types indexés pour cela :

this['x']

@cameron-martin Ne fonctionne pas. Terrain de jeux

@ tycho01 le nouveau type d'inférence et les conditionnels sont géniaux, mais aussi super ennuyeux qu'ils ne fonctionnent pas pour la surcharge de fonctions. La raison invoquée était qu'il fallait quelque chose comme ça typeof pour résoudre le type de fonction parmi les possibles.

@NN utilise simplement const d: this['x'] = 3;

Oh sympa :)

@NN--- ou utilisez

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 Je suis conscient que les locaux fonctionnent, mais je trouve cela moche.
Cela revient à enregistrer manuellement 'this' pour le rappel 'function(){}' au lieu d'utiliser lambda avec une capture implicite.

@NN

ce vilain.

oui ce n'est qu'une option :)

Les types conditionnels rendent désormais cela largement hors de propos puisque vous pouvez écrire ReturnTypeOf<T> avec d'autres alias spécifiques si vous souhaitez valider un ensemble particulier d'arguments. Ils ne peuvent pas faire de résolution de surcharge, mais nous ne pensons pas que cette fonctionnalité vaut la complexité juste pour ce cas d'utilisation.

@RyanCavanaugh Je crois que tu veux dire ReturnType<T> ?

@RyanCavanaugh c'est malheureux - la résolution de surcharge est ce dont j'avais vraiment besoin. Existe-t-il un autre problème pour suivre l'ajout d'une résolution de surcharge aux types conditionnels/inférés ?

Vous devriez pouvoir l'écrire :

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

Je ne l'ai pas testé cependant, et vous auriez besoin d'une aide distincte pour chaque nombre d'arguments.

@ForbesLindesay : le something est actuellement une variable au niveau de l'expression - le référencer avec par exemple typeof ici (ou le déclarer comme une interface) corrige cela. Cependant, je ne parviens pas à obtenir les types de retour appropriés (sur 2.8.0-dev.20180318 ).

@ForbesLindesay malheureusement, je ne crois pas que cela fonctionne ; le mécanisme d'inférence choisira la surcharge de méthode _last_ :

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

image

Cependant , le mécanisme d'inférence _est_ capable de gérer les unions de tuple :

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

image

peut-être avons-nous besoin de quelque chose pour autoriser la surcharge -> objet et la fonction -> objet, le déballage. Effectuez le mappage de type et l'inférence ici, puis revenez à une fonction et à une surcharge.

@MeirionHughes :

peut-être avons-nous besoin de quelque chose pour autoriser la surcharge -> objet et la fonction -> objet, le déballage. Effectuez le mappage de type et l'inférence ici, puis revenez à une fonction et à une surcharge.

Comme (a: number, b?: string) => boolean -> { a: number, b?: string } ? Nous ne pouvons pas encore obtenir de noms de paramètres comme ça, mais conceptuellement, cela devient plus difficile pour les paramètres de repos ( (a: number, ...b: string[]) => boolean ), également puisque nous ne pouvons pas utiliser les types d'objets pour faire l'ordre.

L'ordre compte probablement plus que les noms, et nous pouvons convertir les paramètres et les tuples. Les spreads/facultatifs peuvent encore le compliquer un peu aussi.

Cela réduit le problème à l'extraction des surcharges. Les surcharges devraient être une intersection de types de fonctions comme ((a: number) => 123) & ((s: string) => 'hi') , donc le problème est de savoir comment "déballer" un type d'intersection (par exemple, un type de tuple) - pour l'instant, nous n'avons pas cela.

Je trouve ce chemin insatisfaisant car il traiterait le cas d'utilisation de la surcharge sans parler des génériques, mais oui. Le déballage de l'intersection était toujours un écart dans les deux sens.

Puisque ce problème est maintenant clos, y a-t-il encore une nouvelle proposition pour les pièces qui manquent encore ? Comme un moyen de gérer le type de retour en fonction des arguments ?

Puisque ce problème est maintenant clos, y a-t-il encore une nouvelle proposition pour les pièces qui manquent encore ?

aucun à ma connaissance.

Comme un moyen de gérer le type de retour en fonction des arguments ?

ne pense pas que nous ayons un problème de suivi des types d'appels.

Existe-t-il un support préliminaire pour l'idée d'ajouter simplement une application de fonction au niveau du type ? Je pourrais rédiger une proposition pour cela. Syntactiquement, je pense que c'est le chemin le plus simple.

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 }

Cas marginaux :

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?

Existe-t-il un support préliminaire pour l'idée d'ajouter simplement une application de fonction au niveau du type ? Je pourrais rédiger une proposition pour cela. Syntactiquement, je pense que c'est le chemin le plus simple.

Pas pour le moment. Nous ne voulons vraiment pas mettre la résolution de surcharge dans un espace de type d'ordre supérieur ; le processus est plutôt complexe et comprend plusieurs passes pour déduire les paramètres de type et les arguments de type contextuel, etc. le temps étant.

@mhegazy a-t-il changé la position de l'équipe à ce sujet, compte tenu des travaux récents dans # 24897 ??

Il semble y avoir pas mal de problèmes concernant les solutions qui pourraient être réduites à un type $Call , et un type $Call ouvrirait la porte à un moyen relativement simple d'émuler des types de type supérieur ; voir https://gist.github.com/hallettj/0fde5bd4c3ce5a5f6d50db6236aaa39e par exemple (remplacez l'utilisation de $PropertyType et $ObjMap par $Call ). EDIT : Exemple supplémentaire : https://github.com/facebook/flow/issues/30#issuecomment -346674472

Une telle fonctionnalité serait sans doute conforme aux antécédents de TypeScript en matière de recherche d'une solution commune raisonnable à de nombreux problèmes, non ?

Les types conditionnels rendent désormais cela largement hors de propos puisque vous pouvez écrire ReturnTypeOf<T> avec d'autres alias spécifiques si vous souhaitez valider un ensemble particulier d'arguments. Ils ne peuvent pas faire de résolution de surcharge, mais nous ne pensons pas que cette fonctionnalité vaut la complexité juste pour ce cas d'utilisation.

@RyanCavanaugh @mhegazy

Je suis d'accord qu'il est possible de faire des choses en utilisant des types conditionnels. Je pense que cela n'apporterait pas beaucoup de complexité supplémentaire dans le compilateur si nous réécrivions User.avatar en User extends { avatar: infer T } ? T : never ? Ainsi, par exemple, nous pourrions écrire

export type Avatar = User extends { avatar: infer T } ? T : never;

comme

export type Avatar = User.avatar;

pour améliorer la lisibilité.

Exemple complet

Supposons que nous chargions et transformions certaines données, et que nous nous retrouvions avec une fonction findUser comme celle-ci

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'] }
        ]
      }
    ]
  };
}

Grâce à l'inférence à partir des types mappés, nous pouvons extraire le type de la fonction comme ceci :

export type User = ReturnType<typeof findUser>;
export type Avatar = User extends { avatar: infer T } ? T : never;

Suggestion : cela devrait correspondre au même résultat

export type Avatar = User.avatar;

De plus, nous pourrions même affirmer que User.avatar ne doit pas être de type never .

Plus d'exemples

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[];

Lors de la cartographie d'une propriété imbriquée en une seule fois, ce qui se passe n'est pas très clair

export type Tag2 = User extends { repos: { pull_requests: { tags: (infer T)[] }[] }[] } ? T : never;

Cela clarifierait beaucoup

export type Tag = User.repos[].pull_requests[].tags[];

Valise d'angle

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 On dirait que tu veux juste

export type Avatar = User["avatar"];

qui fonctionne aujourd'hui

@lukaselmer On dirait que tu veux juste

export type Avatar = User["avatar"];

qui fonctionne aujourd'hui

C'est exactement ce que je cherchais. Je le cherchais dans la documentation , mais je ne le trouvais pas. Merci!

Cela fait-il partie du manuel ou existe-t-il une documentation officielle sur la façon dont cela fonctionne ? Je me suis assez familiarisé avec la façon de l'utiliser, mais quand j'essaie de diriger les gens vers la documentation, tout ce que je peux trouver, c'est le type de gardes, ce qui est vraiment complètement différent

Donc, j'ai remarqué que cette proposition a rebondi à partir de 2015 et l'un des objectifs initiaux était d'obtenir en quelque sorte le type d'une seule propriété d'une interface.

interface a {
 foo: bar;
 /* more types */
}

const example = (fooNeeded: [magic] a.foo ) => {};

ai-je raison de supposer que ce n'est toujours pas possible 5 ans plus tard ?

@MFry Je pense que vous recherchez cette syntaxe : a['foo']

Savons-nous s'il existe déjà une solution à cela ?

J'essaie d'obtenir quelque chose comme ça :

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

Salut @maraisr Je ne suis pas sûr à 100% de ce que vous essayez de réaliser. Dans votre exemple, something prend deux types mais ne les utilise pas, et hello est la valeur de retour de quelque chose qui sera toujours void ; Ainsi, doThing ne voit jamais le type string à aucun moment.

Peut-être que quelque chose comme ci-dessous est ce que vous voulez?

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 ouais - désolé pour ça. Merci pour votre réponse rapide!! :100: @acutmore

Le void était juste pour indiquer que le type de retour de cette fonction n'est pas pertinent. Ces 2 types sont transmis à d'autres types génériques, qui sont finalement utilisés dans les arguments.

quelque chose comme:

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. 

Ma fonction doThing ne se soucie pas vraiment de ce qu'est le premier générique ( A ), mais elle se soucie de ce qu'est le second ( B ).

Voir que something dans mon propre cas d'utilisation fait un effet secondaire qui est lu par le doThing .

Donc, je ne peux pas simplement obtenir le ReturnType d'une fonction - j'ai besoin d'une manière ou d'une autre d'aspirer le générique d'une fonction créée.


Si vous pensez que cette requête dépasse le cadre de ce problème, je poursuivrai mon voyage sur StackOverflow !

@maraisr merci pour l'info supplémentaire.

Si vous voulez doThing pour pouvoir obtenir le type original B à partir de something , alors il doit être passé à hello d'une manière ou d'une autre. TypeScript ne regarde que hello et sans aide, il ne saura pas qu'il s'agit du type de retour de something .

Voici une façon de procéder :

/** 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 Pourquoi est-ce fermé ? Les types conditionnels ne résolvent pas cela et bien d'autres cas d'utilisation, et si cela est fusionné, cela rendrait tant de choses possibles.

Je travaille sur une fonction qui peut transformer n'importe quel appel de méthode en une version "sans point" (exemple : [].map(() => n > 5) se transforme en map(() => n > 5)([]) et la seule chose qui manque est que les types conditionnels et infer ne peut pas détecter les génériques, donc dans les fonctions génériques, certains types sortiront comme unknown .

Si je pouvais "appeler" les fonctions pour obtenir le type ( typeof myFunc(() => Either<string,number>) ), il serait possible d'avoir cette fonctionnalité (ce qui est actuellement impossible), et de rendre beaucoup d'autres choses beaucoup plus faciles à faire (HKT, etc.. .)

La complexité est-elle très élevée pour pouvoir $Call une fonction (comme dans un flux) ? J'ai l'impression que le tapuscrit le fait déjà automatiquement.

@nythrox , nous ne pensons pas que la confusion syntaxique que cela pourrait entraîner soit compensée par les cas où vous en avez besoin pour accéder à un type. Le cas spécifique de la résolution d'une expression d'appel est suivi ailleurs ; la proposition dans le PO de "autoriser n'importe quelle expression" n'est pas quelque chose que nous pensons être un bon choix pour la langue.

@RyanCavanaugh oh d'accord, je comprends. Merci pour la réponse, savez-vous quels sont les problèmes de suivi de la résolution d'un appel de fonction ?

J'ai cherché un peu et je n'ai pas trouvé de problème pour un type d'utilitaire d'appel de fonction; la seule référence à celle que j'ai trouvée était dans # 20352, qui vient de renvoyer à ce problème.

Le cas spécifique de la résolution d'une expression d'appel est suivi ailleurs

@RyanCavanaugh Mind lien vers ailleurs? 🙂

@tjjfvi # 37181 concerne plus spécifiquement la résolution d'une fonction en fonction de ses entrées. Peut-être ce que vous cherchez.

@acutmore Cela va un peu dans le sens de ce que je cherchais, même si je parlais spécifiquement d'un utilitaire flow-esque $Call , ou d'une autre syntaxe pour pouvoir l'implémenter. L'approche suggérée ici est étrange, mais merci pour le lien.

Cette page vous a été utile?
0 / 5 - 0 notes