invalid_reflect: a @[reflect] that cannot hold

The refinement checker reflects every function it can: pure, total, terminating and first-order. It doesn’t need to be asked. @[reflect] does not turn reflection on. It asserts that the function is reflected, so a later edit that breaks that (a println, a panic, a call of something that isn’t reflected) is an error here, not a quiet loss of every proof that used it.

@[reflect(fuel: N)] also sets how far the checker follows chains of this function’s calls when a proof needs more than one step (the default is 2). N is between 1 and 8.

This error is reported when:

  • the function is not reflected, and the message names the first reason;
  • the fuel is outside 1 to 8 (@[opaque], not fuel 0, is how a function is kept out of the logic);
  • the attribute has an argument other than fuel: N.

A program that triggers it

mod Main do
  needs IO.Console

  @[reflect]
  fn noisy(n : Int) : Int do
    println("computing")
    n + 1
  end

  fn main(_cap : Cap(IO.Console)) : Int do
    noisy(1)
  end
end

noisy prints, so it has an effect, and a function with an effect cannot be part of the logic.

The fix

Remove the effect, or remove @[reflect] if the function is not meant to be used in proofs:

mod Main do
  @[reflect]
  fn inc(n : Int) : Int do
    n + 1
  end

  fn main() : Int do
    inc(1)
  end
end

Why the rule exists

Reflection is automatic, so a function can stop being reflected without anyone noticing, and every proof that used its definition then turns into a skip. @[reflect] lets the author say “this must stay in the logic”, and the compiler holds the code to it.

See also: the language reference, the lemmas guide, invalid_measure, reflect_conflict.