microsoft / microsoft/TypeScript

Code fails on union when it works fine with both members

Ouverte
#52,402 2 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

Bug Domain: check: Control Flow Help Wanted
Langage dominant
Go
Étoiles
111k
Forks
14.4k
Merge moyen
1 j 19 h
PR mergées (30 j)
117

Description

Bug Report

🔎 Search Terms

Union all members

🕗 Version & Regression Information
  • This changed between versions 4.5.5 and 4.6.4 with the error appearing for the parameters with preapplied narrowing, and again between 4.7.4 and 4.8.4 in the example with type guards. Prior to version 4, the complex type derivation fails.
  • I reviewed the FAQ for entries about union types
  • Nightly version at time of test: v5.0.0-dev.20230123
⏯ Playground Link

Playground link with relevant code

💻 Code
import BN from "bn.js";
interface BoatDataAPI { //Externally defined API, can't be changed
    requiresLicense: (id: string) => boolean;
    maxGroundSpeed: (id: string) => BN;
    description: (id: string) => string;
    displacement: (id: string) => number;
}
//The example below requires somewhat complex type generation.
//The next few lines aim to provide just the complexity needed for the demo,
//without showing all its necessary origins.
//In the inspiring example, type derivation is way more complex,
//with other modifications along the derivation process.
type TupleToObject<T extends [string, any]> = { [key in T[0]]: Extract<T, [key, any]>[1] };
type Attributes = TupleToObject<{
    [F in keyof BoatDataAPI]: [attributeName: F, attributeType: ReturnType<BoatDataAPI[F]>]
}[keyof BoatDataAPI]>;
type AttributeType<A extends keyof BoatDataAPI> = Attributes[A]; //can also be directly used in the example
//A simpler derivation of the type, such as either of the following,
//works for this toy example but not the inspiring example.
type AttributesSimpler = {requiresLicense: boolean; maxGroundSpeed: BN; description: string; displacement: number;}
type AttributeTypeSimpler<A extends keyof BoatDataAPI> = ReturnType<BoatDataAPI[A]>;
//This demo is about the unexpected behavior when each element of a union type works but the union fails.
export const demoFn = function<
    FN extends keyof Attributes
> (
    attributeName: FN, //to clarify that this is known/specified
    attributeValue: AttributeType<FN>,
    //These simulate variables subjected to type guards within the function:
    withNumber : (AttributeType<FN> & number),
    withBN : (AttributeType<FN> & BN),
    withEither : (AttributeType<FN> & number) | (AttributeType<FN> & BN)
) {
    attributeValue.toString(); //works fine, even without type guards
    withNumber.toString(); //works fine
    withBN.toString(); //works fine
    withEither.toString(); //should equal one of the two above & work fine, but doesn't
    let myString : string;
    //Concise code which is not allowed in TypeScript:
    if((typeof attributeValue === 'number') || (BN.isBN(attributeValue))) {
        myString = attributeValue.toString(); //demo with using OR in type guard shows that concise code fails
    }
    //Where that JavaScript code exists for conversion, it must be rewritten as:
    if(typeof attributeValue === 'number') {
        myString = attributeValue.toString(); //works fine, same as withNumber
    } else if(BN.isBN(attributeValue)) {
        myString = attributeValue.toString(); //works fine, same as withBN
    }
}
🙁 Actual behavior

The TypeScript compiler reports an error when the efficiently-written type guard is used and requires that the code be broken out into two conditions. It is not easy to figure out that this is the solution, and the error does not help, nor is this solution on the short list of things to try because it goes against good programming practices. (This solution also does not scale up well when the union has more than two members and a type guard function is being used to narrow the type to one of the members.)

🙂 Expected behavior

Even when the types involved are derived through a complex process, TypeScript still supports efficiently written code, and none of the code above throws a TypeScript error.

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez par la reproduction liée dans Playground et par l’exemple générique AttributeType, en comparant les diagnostics de withEither et du garde de type combiné avec ceux des conditions séparées. Vérifiez le comportement signalé dans les versions de TypeScript mentionnées, puis suivez le rétrécissement des unions et la gestion des gardes de type par le compilateur. C’est terminé lorsque le cas d’union concis passe la vérification de types sans régression dans les cas existants avec des branches séparées.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
typescript
Domaine
compilers
Type d'issue
Bug
Difficulté
5/5
Temps estimé
Plus d'une semaine
Activité
À l'abandon
Clarté
Plutôt claire
Accessibilité débutants
25/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.