New Issue: [Bug]: Speculative resolution ('canResolve') not respected by generic caching.

28354, "DanilaFe", "[Bug]: Speculative resolution ('canResolve') not respected by generic caching.", "2026-01-28T23:49:58Z"

Consider the following program:

record Outermost {}

proc generic(x) {
  use Reflection;
  return x.foo();
}

proc case1() {
  proc Outermost.foo() do return "Outer foo";
  return generic(new Outermost());
}

proc case2() {
  return generic(new Outermost());
}

proc main() {
  writeln(case1());
  writeln(case2());
}

Here, we have two functions, case1 and case that invoke generic in different contexts. In one of those contexts, the Outermost.foo procedure is define on Outermost, and found via POI when resolving the body of generic. In the other context, there is no defined foo, and as a result, generic cannot be resolved. We obtain an error:

poitest.chpl:3: In function 'generic':
poitest.chpl:5: error: unresolved call 'Outermost.foo()'
poitest.chpl:5: note: because no functions named foo found in scope
poitest.chpl:5: note: unresolved call had id 2032505
  poitest.chpl:14: called as generic(x: Outermost)
note: generic instantiations are underlined in the above callstack

This is good. However, let's now make use of reflection to see if foo is available in the scope of generic:

5c5
<   return x.foo();
---
>   return canResolveMethod(x, "foo");

This compiles, and somehow tells us that foo is available in both scopes. Surely that can't be right, since we just got an error about foo not being available!

> _referenceChapel poitest.chpl
> ./poitest
true
true

Moreover, if we remove the call to the first function (which does provide foo, and is completely unrelated to the second function),

8,11c8,11
< proc case1() {
<   proc Outermost.foo() do return "Outer foo";
<   return generic(new Outermost());
< }
---
> // proc case1() {
> //   proc Outermost.foo() do return "Outer foo";
> //   return generic(new Outermost());
> // }
18c18
<   writeln(case1());
---
>   // writeln(case1());

the second function's output changes:

> _referenceChapel poitest.chpl
> ./poitest
false

This is because generic caches are only updated when we "normally resolve a call", and not when we "see if a call is resolvable":

This is improper because (via "can resolve" queries and branching), even speculatively resolved calls can affect function behavior, as I have demonstrated here.