New Issue: Should 'forwarding foo()' calls be subject to call-site POI?

28358, "DanilaFe", "Should 'forwarding foo()' calls be subject to call-site POI?", "2026-01-29T22:15:08Z"

This is a weird one.

As I was digging through the production compiler's implementation of forwarding, I noticed that statements like record R { forwarding replaceme() } get converted roughly into procedures like this:

proc R.getForwardingBacker() {
  return replaceme();
}

An important point to note is that if R is generic (e.g., by having a type field), the function itself is generic as well.

The next detail is that, today, forwarded calls are resolved by inserting a call to getforwardingBacker() at the call site of the forwarded call. Thus, we get:

var r: R(...);
r.foo();
// becomes:
r.getForwardingBacker().foo();

We therefore have that the generic function getForwardingBacker() is resolved in whatever context that the call to foo() occurs! It is therefore, like all generic functions, subject to POI. In particular, we are able to replace replaceme with any function we want. Consider:

module Lib {
  record R {
    type idk;

    forwarding replaceme();
  }
}
module ContextA {
  use Lib;

  record Impl {
    proc foo() {
      writeln("ContextA.Impl.foo");
    }
  }

  proc R.replaceme() {
    return new Impl();
  }

  proc test() {
    var r: R(int);
    r.foo();
  }
}
module ContextB {
  use Lib;

  record Impl {
    proc foo() {
      writeln("ContextB.Impl.foo");
    }
  }

  proc R.replaceme() {
    return new Impl();
  }

  proc test() {
    var r: R(int);
    r.foo();
  }
}
module Main {
  use ContextA;
  use ContextB;

  proc main() {
    writeln("Testing ContextA:");
    ContextA.test();

    writeln("Testing ContextB:");
    ContextB.test();
  }
}

This program introduces on POI-scoped replaceme functions, and causes our forwarding mechanism to pick a different foo() call depending on where it's invoked. It prints:

Testing ContextA:
ContextA.Impl.foo
Testing ContextB:
ContextB.Impl.foo

This is a relatively niche issue because POI functions are always considered as a fallback to non-POI functions. Thus, if R defined its own replaceme (instead of leaving it precariously free), this behavior would not be possible. That said, it seems strange to me that this is allowed at all.

It seems like we should resolve the call to getForwardingBacker() in the scope that defines R, and emit an error when this attempted hijacking happens.