Help understanding the behavior of RecursionManager.CalculationStack.maybeMemoize

Answered

Hi. The behavior of maybeMemoize has changed for me between 2019.1 and 2019.2 and I see that it has been rewritten.

The problem I am having is that it is now memoizing values in a situation where it didn't used to, and my custom language plugin is giving unexpected errors.

Here is an example:

class EnumLiteral {
enum GosuEnum {
VAL1, VAL2
}

function acceptEnum(p: GosuEnum) {}

function acceptEnumOverloadWithTypeParameter<T>(p: T) {}
function acceptEnumOverloadWithTypeParameter(p: GosuEnum) {}

function acceptMap(p: Map<GosuEnum, String>) {}

function test() {
acceptEnum(VAL2)
acceptEnumOverloadWithTypeParameter(VAL1) //## error now happening on this line
acceptMap({VAL1 -> "val"})
}
}

The method scorer evaluates the 'VAL1' argument to the first version of the overloaded method, and fails to resolve. This is expected, because it is dealing with a generic parameter.

What should happen next is that it re-evaluates the 'VAL1' argument to the second version of the overloaded method, as a GosuEnum. With this assumption it should resolve properly, and this is how it works in 2019.1. But I see that in 2019.2 the previous (bad) resolution of VAL1 gets memoized, and is being returned when we score the second version of the method.

This makes the whole system order-dependent; if I switch the declaration order of the two overloaded methods it resolves successfully.

I don't know if this is a problem that will affect other plugin developers. But I need to know how to get it to behave properly in this circumstance.

3 comments
Comment actions Permalink
Official comment

The javadoc for memoize has been updated, too. It says, in particular, "Pass {@code true} if your computation has no side effects and doesn't depend on method parameters". If your computation returns different results for different overloads, it looks like it depends on the methods parameters (or some thread-locals), so it shouldn't be memoized.

Comment actions Permalink

That would be easy to handle if I were just calling resolveWithCaching() directly. But I think it's invoked many levels down, starting from a call to PsiElement.getType().

I was wondering if I could affect the value of memoize that I pass in...I'll investigate that some more.

0
Comment actions Permalink

You can't affect the value of "memoize" deep down. But you can notify RecursionManager that you're relying on a specific thread-local value: wrap everything relying on it into doPreventingRecursion, and invoke prohibitResultCaching (or another doPreventingRecursion with the same key) when you're using it inside. Anything between those points in stack would only be memoized if the same thread-local value is present in the stack later.

0

Please sign in to leave a comment.