microsoft / microsoft/TypeScript

Infer from usage type logic

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

Personne n'a encore pris cette issue.

Experience Enhancement Help Wanted Suggestion
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
  • infer from usage
  • interface higher priority then type on type inference
🕗 Version & Regression Information

I think this has always been the behavior as far as I know. I didn't play around with this much yet, but the behavior exists in 4.0.5 and nightly and it seems odd for it to bounce around a lot.

⏯ Playground Link

playground link

💻 Code


// infers -> : { x: any; } = this makes sense
function inferWorksFine(obj) {
    return obj.x
}

// infers -> Z, this also makes sense
function inferFromFunctionCallTypedWithType(obj) {
    typedWithType(obj)
}
// infers -> Q, also makes sense
function inferFromFunctionCallTypedWithInterface(obj) {
    typedWithInterface(obj)
}

/*
 *  Here is when behavior gets odd
 * 
 */

// infers -> { toplevel?: any; z?: number }
//      I would prefer if toplevel resovled to a non ? type but thats sorta stylistic
//      z is not optional and making it results in a type error
function inferFromUsageAndFunctionCallTypedWithType(obj) {
    typedWithType(obj)
    return obj.toplevel
}
// infers -> Q 
//      This seems wrong to me, interface "types" have priority over 
//      anonymous types, this results in errors because the inferred property 
//      is an anonymous type
function inferFromUsageAndFunctionCallTypeWithInterface(obj) {
    typedWithInterface(obj)
    return obj.toplevel
}
// infers -> Q
//      From what I understand. The statement "type Z = {z: number}" 
//      is a type alias Z to an anonymous type {z: number} so the interface 
//      still has priority over it. 
function inferFromFunctionCallTypedWithTypeAndFunctionCallTypedWithInterface(obj) {
    typedWithType(obj)
    typedWithInterface(obj)
}

// There are more examples you could contrive that look like this/combinations of this
// but I think this is enough for the point




type Z = {z: number}
function typedWithType(z: Z) {

}

interface Q {
    q: number
}
function typedWithInterface(q: Q) {}
🙁 Explanation of Actual behavior

Note: If I reference line numbers or any function I'm referring to the main branch I pulled I think yesterday.

So I sort of already discussed the behavior in the code itself. So I think it makes sense to talk about the code that causes this behavior.
So this file path "TypeScript/src/services/codefixes/inferFromUsage.ts" is where the logic for inferFromUsage lives afaik.

Just to do this in order:

// infers -> { toplevel?: any; z?: number }
//      I would prefer if toplevel resovled to a non ? type but thats sorta stylistic
//      z is not optional and making it results in a type error
function inferFromUsageAndFunctionCallTypedWithType(obj) {
    typedWithType(obj)
    return obj.toplevel
}

The reason these are optional seem to stem from the function combineAnonymousTypes
Specifically

const members = mapEntries(props, (name, types) => {
            const isOptional = types.length < anons.length ? SymbolFlags.Optional : 0;
            const s = checker.createSymbol(SymbolFlags.Property | isOptional, name as __String);
            s.links.type = checker.getUnionType(types);
            return [name, s];
        });

I'm actually not sure what the isOptional is intended to do. From what I understand anons.length is the number of anonymous types that the function is passed while types refers to possible type resolutions of the property with name name. I'm not sure what comparing these means.

Priority of interfaces

Since in the code I mention why I think type aliasing doesn't change anything I'll just show one example.

function inferFromFunctionCallTypedWithTypeAndFunctionCallTypedWithInterface(obj) {
    typedWithType(obj)
    typedWithInterface(obj)
}

So here the type will be resolved to the interface type. In the function combineTypes there is a priority table defined as follows:

const priorities: Priority[] = [
            {
                high: t => t === checker.getStringType() || t === checker.getNumberType(),
                low: t => t === stringNumber
            },
            {
                high: t => !(t.flags & (TypeFlags.Any | TypeFlags.Void)),
                low: t => !!(t.flags & (TypeFlags.Any | TypeFlags.Void))
            },
            {
                high: t => !(t.flags & (TypeFlags.Nullable | TypeFlags.Any | TypeFlags.Void)) && !(getObjectFlags(t) & ObjectFlags.Anonymous),
                low: t => !!(getObjectFlags(t) & ObjectFlags.Anonymous)
            }];

The last entry

{
                high: t => !(t.flags & (TypeFlags.Nullable | TypeFlags.Any | TypeFlags.Void)) && !(getObjectFlags(t) & ObjectFlags.Anonymous),
                low: t => !!(getObjectFlags(t) & ObjectFlags.Anonymous)
            }

gives high priority to non-anonymous types which I think only means interfaces or a type which at some point was mixed with an interface? This would take further investigation. I'm not sure the purpose of this behavior or if its a bug.

🙂 Expected behavior

I want the behavior to be that the generated types don't cause errors when they don't have to, since currently generated types don't satisfy what they need to be.

Deleting these two lines "solves" the behavior, but there are definitely more things to look into. I'm not sure why they were written - like I don't have any intuition on what problem they solve - but I'm pretty sure they are there for important behavior.

Also since I'm listing things, I think this is the other "big one"

There are also more cases like

function f(obj) {
    g(obj)
    return obj.v > 1
}

not being able to resolve v to a number if "g" expects some type (even if the type is unrelated like {q}) and others.

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 TypeScript/src/services/codefixes/inferFromUsage.ts et comparez les cas Playground liés avec combineAnonymousTypes et combineTypes. Suivez la logique des propriétés optionnelles et la table des priorités, puis déterminez le comportement d'inférence attendu avant de modifier quoi que ce soit. La tâche est terminée lorsque les cas d'inférence à partir de l'utilisation signalés ne produisent plus d'erreurs de type inattendues.

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é
À clarifier
Accessibilité débutants
25/100

Recevez les nouvelles issues par e-mail

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