Skip to content
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

No error is thrown when two types differ by one or more properties #56740

Closed
nicomontanari opened this issue Dec 11, 2023 · 8 comments
Closed
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@nicomontanari
Copy link

nicomontanari commented Dec 11, 2023

πŸ”Ž Search Terms

error,thrown,types differ,one or more properties

πŸ•— Version & Regression Information

  • I was able to test this on version 5.3.3

⏯ Playground Link

https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgKoGdrIN4ChnLAAmAXMiAK4C2ARtPshFXMADZnphSgDmDADnHToA7gHsopZJ24g+AX1y5QkWIhQBhVhDggIRDFjwFiZSrXoEmLdtK69ci3AjEhOyCpihlDUZAF4cBlNkAEYAGgZrNjIAcljIgkFhcUk42MclFzcwZARtXX1fMi0dPQMvAI8vJSA

πŸ’» Code

interface User {
  id: number
  email: string
  password: string
}

interface CleanedUser {
  id: number
  email: string
}

const user: User = {
  id: 1,
  email: '',
  password: ''
}

const cleanedUser: CleanedUser = user

πŸ™ Actual behavior

No error received when i assign User to CleanedUser

πŸ™‚ Expected behavior

I would like to see an error that tells me that the CleanUser and User type are not equals because "password" or any additional properties are not allowed to the CleanedUser type.

Additional information about the issue

Is there a way to configure that behavior inside the tsconfig file?

@MartinJohns
Copy link
Contributor

This is working as intended, see also: https://github.com/microsoft/TypeScript/wiki/FAQ#what-is-structural-typing

You would need #12936 for this.

@fatcerberus
Copy link

To exclude specific properties you can do this:

interface CleanedUser {
  id: number
  email: string
  password?: never
}

@fatcerberus
Copy link

@MartinJohns Re: Confused emoji - optional properties typed as never is a common pattern to prevent specific properties from existing on a type. The property can either be omitted (because it's optional) or exist with type never, but because there are no values of type never, the effect is to prevent the object from having that property at all. It's often used to express unions like { a: string, b?: never } | {a?: never, b: string }

@MartinJohns
Copy link
Contributor

Confused is the wrong emoji, but I didn't want to give the thumbs down smiley. I really don't like this hacky workaround.

Most of you wrote is correct, except the "the effect is to prevent the object from having that property at all". A property typed never does not necessarily mean it doesn't exist, it could also be a property that throws upon accessing. The type Record<string, never> was once described by Ryan as "an object that throws upon accessing any property".

This code works perfectly fine and logs true:

interface CleanedUser {
  id: number
  email: string
  password?: never
}

const f: CleanedUser = {
  id: 1,
  email: 'foo',
  get password(): never { throw new Error() }
}
console.log('password' in f)

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Dec 11, 2023
@fatcerberus
Copy link

That's fair; I'd argue that still matches intent in this case since the effect is to prevent reading a password off of a "cleaned" user, but I can see that being problematic in other scenarios. The alternate version is password?: undefined but that breaks exactOptionalPropertyTypes (if it's used) and also doesn't fix the in issue you highlighted.

Ultimately I agree that #12936 would be the non-hacky fix, but realistically it'll probably never happen.

@jcalz
Copy link
Contributor

jcalz commented Dec 12, 2023

@MartinJohns

hacky workaround.
it could also be a property that throws upon accessing.

Note that any property could throw upon accessing, yet generally one interprets {x: string} as an object with a string-valued x property without the caveat that maybe the x property will throw when accessed instead. An object of type {x?: never} is, in descending order of reasonableness:

  • an object without an x property
  • an object with an x property of type undefined (depending on compiler settings)
  • an object that throws when you access x
  • an object that throws when you access it in any way

I'd generally stop at the top bullet when discussing this unless unless such secondary and tertiary effects become important. I wouldn't call this a "hack", unless I wanted to reinterpret anything that neglects "this might throw" as a hack as well.

@fatcerberus
Copy link

@jcalz Bullet point 3 becomes somewhat more reasonable when you consider that the compiler won't stop you from accessing x, even with EOPT enabled. So from a purely type-theoretical POV, it really does mean "property that either doesn't exist or throws upon access". In practice though, since you have to write a getter to actually get there, the first and second interpretations are still favored.

@typescript-bot
Copy link
Collaborator

This issue has been marked as "Working as Intended" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Dec 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

6 participants