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

A Type Error occurs with using a generic that extends a branded intrinsic type #55040

Closed
RebeccaStevens opened this issue Jul 17, 2023 · 6 comments
Labels
Not a Defect This behavior is one of several equally-correct options

Comments

@RebeccaStevens
Copy link

RebeccaStevens commented Jul 17, 2023

Bug Report

πŸ”Ž Search Terms

  • generics
  • branded
  • intrinsic type

πŸ•— Version & Regression Information

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

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

/////////////////////////////////////////////////////////////////////////////////////////////
// Types

type BrandedNumber = number & {
  __branded: true
}

type OperationIO<T extends number> = T extends BrandedNumber
  ? T
  : number;

/////////////////////////////////////////////////////////////////////////////////////////////
// Function

function sub<T extends number>(
  a: OperationIO<T>,
): (b: OperationIO<T>) => OperationIO<T> {
  return (b) => (b - a) as any;
}


/////////////////////////////////////////////////////////////////////////////////////////////
// Tests

// Make sure non-branded numbers work.
const testNumbers = sub(2)(3);

// Make sure branded numbers work.
function testWithoutGenerics(a: BrandedNumber, b: BrandedNumber) {
  return sub(a)(b);
}

// Subtypes of branded numbers should work too.
function testWithGenerics<T extends BrandedNumber>(a: T, b: T) {
  return sub(a)(b); // <-- Why is this not allowed?
}

// Has correct typing dispite above error.
const testResultWithGenerics = testWithGenerics(
  2 as BrandedNumber & { __foo: 1},
  3 as BrandedNumber & { __foo: 1}
);

πŸ™ Actual behavior

TypeScript reports a type error on line 35.

πŸ™‚ Expected behavior

No error should be reported because as far I can tell, this line should be valid. Additionally, TypeScript can successfully deduce the type on line 39.

@fatcerberus
Copy link

What is an β€œinstinct type”?

@jcalz
Copy link
Contributor

jcalz commented Jul 17, 2023

From context I'd say it means "primitive type"? πŸ€·β€β™‚οΈ

The issue here is probably just that generic conditional types don't consult the generic's constraints and therefore remain unresolved, as in #31096

@RebeccaStevens
Copy link
Author

RebeccaStevens commented Jul 17, 2023

Yeah, it does seem to be that issue that's occurring. Any suggestions on how I can work around it? The only solution I've come up with is to use overloads of the sub function.

Also, "instinct" was a mistake by me, I meant "intrinsic".

@RebeccaStevens RebeccaStevens changed the title A Type Error occurs with using a generic that extends a branded instinct type A Type Error occurs with using a generic that extends a branded intrinsic type Jul 17, 2023
@jcalz
Copy link
Contributor

jcalz commented Jul 17, 2023

The conventional term is "primitive", though. In TypeScript, "intrinsic" refers to compiler-provided types that users cannot implement themselves (see #40580), and while one could make a case that all primitive types are intrinsic, not all intrinsic types are primitive.

@RebeccaStevens
Copy link
Author

Ahh, good to know. I've kind of just been using the two interchangeably.

@RyanCavanaugh
Copy link
Member

For this substitution to be safe, the conditional type would have to be nondistributive. If you change this to

type OperationIO<T extends number> = [T] extends [BrandedNumber]
  ? T
  : number;

then it indeed works as expected

@RyanCavanaugh RyanCavanaugh added the Not a Defect This behavior is one of several equally-correct options label Jul 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Not a Defect This behavior is one of several equally-correct options
Projects
None yet
Development

No branches or pull requests

4 participants