Skip to content

Array.forEach args type error #59791

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
chenyulun opened this issue Aug 29, 2024 · 10 comments
Closed

Array.forEach args type error #59791

chenyulun opened this issue Aug 29, 2024 · 10 comments

Comments

@chenyulun
Copy link

🔎 Search Terms

export interface TypeA {
  name: string
}

export interface TypeB extends TypeA {
  unit: string
}

const xxx: { a: TypeB[], b: TypeA[] } = {
  a: [],
  b: [],
};
(['a', 'b'] as const).forEach((key) => {
  const yyy = xxx[key]

  yyy.forEach((item) => { // (parameter) item: TypeA

    if('unit' in item) {
      console.log(item.unit) // (property) unit: unknown
    }
  })
})

🕗 Version & Regression Information

  • This changed between versions ______ and _______
  • This changed in commit or PR _______
  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about _________
  • I was unable to test this on prior versions because _______
mkdir test-ts5.5
cd $_
pnpm init
pnpm add typescript

package.json

{
  "name": "test-ts5.5",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "typescript": "^5.5.4"
  }
}

⏯ Playground Link

No response

💻 Code

export interface TypeA {
  name: string
}

// export interface TypeB extends TypeA {
 export interface TypeB {
  name: string
  unit: string
}

const xxx: { a: TypeB[], b: TypeA[] } = {
  a: [],
  b: [],
};
(['a', 'b'] as const).forEach((key) => {
  const yyy = xxx[key]

  yyy.forEach((item) => {
    if('unit' in item) {
      console.log(item.unit)
    }
  })
})

🙁 Actual behavior

item type Only TypeA

🙂 Expected behavior

item type sould be TypeA | TypeB

Additional information about the issue

I have not idea

@MartinJohns
Copy link
Contributor

Duplicate of #55201.

@chenyulun
Copy link
Author

chenyulun commented Aug 29, 2024

@RyanCavanaugh

but item.unit type be unknown,

Expected behavior

item.unit type be string,

@MartinJohns
Copy link
Contributor

Objects are not sealed. It's possible that your TypeA values have a unit property that is not a string, that's why checking for a property using the in operator results in unknown.

@chenyulun
Copy link
Author

Objects are not sealed. It's possible that your TypeA values have a unit property that is not a string, that's why checking for a property using the in operator results in unknown.

So there is a problem with item's type recognition
But it was marked as non-defective in another issue

@chenyulun
Copy link
Author

How should I write best practices for the following code

export interface TypeA {
  name: string;
}

export interface TypeB {
  name: string;
  unit: string;
}
let unit: string; 
const testObject: { a: TypeB[], b: TypeA[] } = {
  a: [],
  b: [],
};
(['a', 'b'] as const).forEach((key) => {
  const ObjectValue = testObject[key]

  ObjectValue.forEach((item) => {
    if('unit' in item) {
      unit = item.unit // error ts(2322)
    }
  })
})

@chenyulun
Copy link
Author

chenyulun commented Aug 29, 2024

export interface TypeA {
  name: string;
  // age: number;
}

export interface TypeB {
  name: string;
  unit: string;
}
type TypeTest = TypeA[] | TypeB[]
// type TypeTest = string[] | boolean[];




type Item = TypeTest extends Array<infer R> ? R : never // as  type Item = TypeA
// because this?
type IsBExtendsA = TypeB extends TypeA ? 'true' : 'false' // 'true'

@MartinJohns
Copy link
Contributor

So there is a problem with item's type recognition

No, it's working as intended. There is no defect.

@chenyulun
Copy link
Author

export interface TypeA {
  name: string;
  // age: number;
}

export interface TypeB {
  name: string;
  unit: string;
}
type TypeTest = TypeA[] | TypeB[]
// type TypeTest = string[] | boolean[];




type Item = TypeTest extends Array<infer R> ? R : never // as  type Item = TypeA
// because this?
type IsBExtendsA = TypeB extends TypeA ? 'true' : 'false' // 'true'

Although the logic is fine, the extends judgment in real use scenarios doesn't make sense, and this may involve too much underlying design.

@chenyulun
Copy link
Author

So there is a problem with item's type recognition

No, it's working as intended. There is no defect.

But he realistically lost the type of unit as string,Is there a better way to practice this situation?

@MartinJohns
Copy link
Contributor

As mentioned in the other issue, it's due to subtype reduction. Look it up, there are many issues about it. It's working as intended.

You can either explicitly check if unit is a string by doing 'unit' in item && typeof item.unit === 'string' or just use a type assertion item.unit as string. But again, objects are not sealed. Values of TypeA are permitted to have a unit property at runtime, which may not be typed string. This adjustment of your code demonstrates it:

interface TypeA {
  name: string;
}

interface TypeB {
  name: string;
  unit: string;
}
let unit: string; 
const testObject: { a: TypeB[], b: TypeA[] } = {
  a: [],
  b: [],
};


// The value is compatible with TypeA, so it can be pushed to the TypeA array.
const value = { name: 'example', unit: 12345 };
testObject.b.push(value);


(['a', 'b'] as const).forEach((key) => {
  const ObjectValue = testObject[key]

  ObjectValue.forEach((item) => {
    if('unit' in item) {
      console.log(item.unit)
      unit = item.unit // error ts(2322)
    }
  })
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants