crash in lowering if integer literal is converted to a generic type

Author: zygoloidCreated Dec 14, 2025Updated Sep 2, 2026
Labelslong term issuetoolchain

Testcase:

fn F(N:! Core.IntLiteral()) -> Core.Int(N) {
  return 0;
}

fn G() {
  var n: i32 = F(32);
}

See discussion on discord.

When type-checking F, we discover that there is a blanket impl of ImplicitAs(Int(N)) for IntLiteral, but we don't commit to it because it isn't final (and due to the orphan rule, can't really be final). But when we form the specific for F with N = 32, we only re-evaluate the lookup for the impl witness, and don't subsequently re-evaluate the constant value of the call to that function, because it didn't have a symbolic constant value when type-checking F.

Put another way, we treat the F above just like this one:

fn F2(N:! Core.IntLiteral()) -> Core.Int(N) {
  let k: Core.IntLiteral() = // ...
  return k;
}

... which should be invalid, but it's currently accepted, and use of it currently crashes the toolchain for the same reason as the first example.

There are a couple of things that would help here:

  • We should probably treat impl forall [N:! IntLiteral] IntLiteral as ImplicitAs(Int(N)) as being effectively final for impl lookup purposes so that we commit to it when we see the conversion within F. This is correct because the only ? in the type structure is of type IntLiteral, and so cannot introduce any more associated libraries. And this would help because it'd allow us to validate the call when type-checking F, and know that we need to re-validate it when forming a specific.
  • Once we have support for forms, the IntLiteral as ImplicitAs(Int(N)) implementation should be conditional on the integer literal having a compile-time constant value (and should depend on what that compile-time constant value is). That would allow us to reject F2, because we can't find a suitable impl, but it may not be easy to accept F with this approach, because we would presumably not be able to validate that the constant value fits within Int(N) when checking the definition of F.

One other concern is: should F even be valid? What if instead of 0 we used a larger constant, that wouldn't fit within all possible values of N? I think the right tradeoff here may be to say that the check that the constant value fits within Int(N) is a template-phase check that doesn't affect impl selection (that is, the impl is always selected but may result in monomorphization errors), but maybe there's a better way to model this situation.

Source: carbon-language/carbon-lang