GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Type assignable to index access of constraint of type parameter is incorrectly assignable to the index access itself · Issue #46076 · microsoft/TypeScript

Type assignable to index access of constraint of type parameter is incorrectly assignable to the index access itself #46076

New issue
Copy link
New issue
Copy link
Closed
Closed
Type assignable to index access of constraint of type parameter is incorrectly assignable to the index access itself#46076
Copy link
Assignees
gabritto
Labels
Design LimitationConstraints of the existing architecture prevent this from being fixedConstraints of the existing architecture prevent this from being fixedDomain: Indexed Access TypesThe issue relates to accessing subtypes via index accessThe issue relates to accessing subtypes via index access
Milestone
TypeScript 4.6.1

Description

@paulnelson2
paulnelson2
opened on Sep 26, 2021
Issue body actions

Bug Report

🔎 Search Terms

generic subtype, generic constraint

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about generic subtypes

⏯ Playground Link

(strict settings)
Playground link with relevant code

💻 Code

interface ContainerBase {
  success: boolean;
  item: {
    itemId: string;
  };
}

interface MyContainer {
  success: boolean;
  item: {
    itemId: string;
    thing: {
      color: string;
      size: number;
    };
  }
}

function getItemAndCheck<ContainerT extends ContainerBase>(container: ContainerT): ContainerT["item"] {
  if (!container.success) {
    throw new Error(`container success fail`);
  }
  //return container.item; // good code
  return {itemId: "id"}; // bad code, but tsc raises no error.
  // The error could be (for example): '{itemId: "id"}' is assignable to the constraint of type 'ContainerT["item"]',
  // but 'ContainerT["item"]' could be instantiated with a different subtype of constraint '{itemId: "id"}'
  // (like existing errors about generic subtypes)
}

const item = getItemAndCheck(fetchMyContainer());
console.log(item.thing.color); // throws an error at runtime but tsc raised no error earlier
                               // the caller thinks 'item' is the property of the specific subtype MyContainer but inside
                               // getItemAndCheck() you can return any subtype not just that one.

function fetchMyContainer(): MyContainer {
  // example
  return {
    success: true,
    item: {
      thing: {
        color: "green",
        size: 42,
      },
      itemId: "id",
    }
  }
}

🙁 Actual behavior

No error but there should have been.

🙂 Expected behavior

An error on the return statement of getItemAndCheck().

Use case

A rest api which returns data with very similar top level structure, but with differing nested properties, and wanting to write generic handlers for all responses.

Workaround

One could make the properties of the generic themselves generic type parameters as well, but this becomes more difficult the more complex the types are or if you want to manipulate several different properties in a generic way instead of one.

Reacted by DetachHead and KotlinIsland

Activity

  1. Nathan Shively-Sanders (sandersn)
    changed the title [-]Inconsistency between function body and caller regarding subtypes of a property of a generic[/-] [+]Type assignable to constraint of type parameter is incorrectly assignable to the parameter as well[/+] on Oct 6, 2021
  2. Nathan Shively-Sanders (sandersn)
    changed the title [-]Type assignable to constraint of type parameter is incorrectly assignable to the parameter as well[/-] [+]Type assignable to index access of constraint of type parameter is incorrectly assignable to the index access itself[/+] on Oct 6, 2021
  3. Nathan Shively-Sanders (sandersn)
    added
    BugA bug in TypeScriptA bug in TypeScript
    on Oct 6, 2021
  4. Nathan Shively-Sanders (sandersn)
    added this to the TypeScript 4.6.0 milestone on Oct 6, 2021
  5. Nathan Shively-Sanders (sandersn)
    assigned
    Gabriela Araujo Britto (gabritto)
    on Oct 6, 2021
  6. Ryan Cavanaugh (RyanCavanaugh)
    modified the milestones: TypeScript 4.6.0, TypeScript 4.6.1 on Feb 3, 2022
  7. DetachHead commented on Apr 2, 2022

    @DetachHead
    DetachHead
    on Apr 2, 2022
    Contributor
    More actions

    here is a more minimal example:

    //no error
    const impostor = <T extends [string|number]>(): T[0] => 'i am string'
    
    const a: number = impostor<[number]>() // actually is string
    
    a.toExponential() //runtime error
    Reacted by KotlinIsland, Gabriela Araujo Britto and paulnelson2
  8. gabritto commented on Apr 6, 2022

    @gabritto
    Gabriela Araujo Britto (gabritto)
    on Apr 6, 2022
    Member
    More actions

    So, I think this is a known design limitation in the way we check assignability in our type system.
    To clarify: the reason there's no error on the original example is that when we check if the type of the return { itemId: "id" } is assignable to the annotated return type ContainerT["item"], we check if { itemId: "id" } is assignable to the constraint of ContainerT["item"], which is ContainerBase["item"] = { itemId: string }, so we conclude it is assignable.
    The general rule is this: "A type S is related to a type T[K] if S is related to C, where C is the base constraint of T[K] for writing" (implemented here: https://github.dev/microsoft/TypeScript/blob/3fd8a6e44341f14681aa9d303dc380020ccb2147/src/compiler/checker.ts#L19368).
    The rule is knowingly unsound, and it is unfortunate that we don't error on cases like the above, but the rule is useful in ways explained in this comment.

    As to workarounds, I think you're right: this assignability rule only applies for an indexed access T[K] if the constraints of T and K are not themselves generic, so to evade this rule, you'd have to make sure either the constraint of K is generic, or the constraint of T is generic. The result would probably be something less ergonomic than the original code 🙁.

    We have made this assignability rule more strict over time, like here and here, but for this specific case pointed out here, I don't see how we could make the rule not applicable, since the index type in the examples is concrete ("item" and 0 in the example programs, respectively), which is exactly the case pointed out that we want to support (equivalent to e.g. this["xxx"]).

  9. Gabriela Araujo Britto (gabritto)
    closed this as completedon Apr 6, 2022
  10. Gabriela Araujo Britto (gabritto)
    added
    Design LimitationConstraints of the existing architecture prevent this from being fixedConstraints of the existing architecture prevent this from being fixed
    Domain: Indexed Access TypesThe issue relates to accessing subtypes via index accessThe issue relates to accessing subtypes via index access
    and removed
    BugA bug in TypeScriptA bug in TypeScript
    on Apr 6, 2022
  11. craigphicks commented on Jan 9, 2024

    @craigphicks
    Craig P Hicks (craigphicks)
    on Jan 9, 2024
    More actions

    I don't think this is a limitation, but rather a correct feature.
    It is similar the way that JS getters are intended allow the user to massage values - although getters are not the issue here, the need is similar. Consider this:

    function getItemAndCheck2<ContainerT extends ContainerBase>(container: ContainerT): ContainerT["item"] {
      ...
      return { itemId: container.item.itemId.toLocaleUpperCase() }; // an error here would be user unfriendly
    }
    

    It is a good thing that return value is not constrained in the way the OP suggests.

  12. DetachHead commented on Jan 10, 2024

    @DetachHead
    DetachHead
    on Jan 10, 2024
    Contributor
    More actions

    Craig P Hicks (@craigphicks) i don't get what would make an error there any less user friendly than in the original getItemAndCheck function from the OP. do you mean if container.item.itemId.toLocaleUpperCase() was an error? because i don't see why it would be, since ContainerT["item"] will always have itemId

  13. craigphicks commented on Jan 10, 2024

    @craigphicks
    Craig P Hicks (craigphicks)
    on Jan 10, 2024
    More actions

    André Alves Boutros (@Detach) head - A more slim example

    function getItemId<ContainerT extends ContainerBase>(container: ContainerT): ContainerT["item"]["ItemId"] {
      return container.item.itemId.toLocaleUpperCase(); // an error here would be user unfriendly
    }
    

    I am saying functional access to members is a common way to allow transformations of the returned member. It's a common software pattern. A common software pattern shouldn't trigger an error.
    I understand your counterargument that string could have been used instead of ContainerT["item"]["ItemId"] , and agree to disagree whether such usage should error.

    If your example is rewritten as:

    function impostor1<T extends [string|number]>(...args:T){
        return 'i am string';
    } 
    const ng: number = impostor1(1);
    // const ng: number
    // Type 'string' is not assignable to type 'number'.(2322)
    

    an error is produced. I do agree that

    const impostor = <T extends [string|number]>(): T[0] => 'i am string'
    

    should produce an error for the same reason that imposter1 does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

  • @gabritto
    Gabriela Araujo Britto (gabritto)

Labels

Design LimitationConstraints of the existing architecture prevent this from being fixedConstraints of the existing architecture prevent this from being fixedDomain: Indexed Access TypesThe issue relates to accessing subtypes via index accessThe issue relates to accessing subtypes via index access

Type

No type

Projects

No projects

    Milestone

    • TypeScript 4.6.1

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402503 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.