Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use TypeDef.erasure to prevent recursion types errors #11657

Merged
merged 2 commits into from
Mar 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -668,24 +668,26 @@ public void visitAroundMethod(TypedElement beanType,

if (!proxiedMethodsRefSet.contains(methodKey)) {

String interceptedProxyClassName = null;
String interceptedProxyBridgeMethodName = null;
ClassTypeDef interceptedProxyDef = null;
MethodDef interceptedProxyBridgeMethod = null;

if (!isProxyTarget) {
// if the target is not being proxied then we need to generate a bridge method and executable method that knows about it

if (!methodElement.isAbstract() || methodElement.isDefault()) {
interceptedProxyClassName = proxyFullName;
interceptedProxyBridgeMethodName = "$$access$$" + methodName;
interceptedProxyDef = ClassTypeDef.of(proxyFullName);
interceptedProxyBridgeMethod = MethodDef.builder("$$access$$" + methodName)
.addModifiers(Modifier.PUBLIC)
.addParameters(argumentTypeList.stream().map(p -> ParameterDef.of(p.getName(), TypeDef.erasure(p.getType()))).toList())
.returns(TypeDef.erasure(returnType))
.build((aThis, methodParameters) -> aThis.superRef((ClassTypeDef) TypeDef.erasure(methodElement.getOwningType()))
.invoke(methodElement, methodParameters)
.returning()
);

// now build a bridge to invoke the original method
proxyBuilder.addMethod(
MethodDef.builder(interceptedProxyBridgeMethodName)
.addModifiers(Modifier.PUBLIC)
.addParameters(argumentTypeList.stream().map(p -> ParameterDef.of(p.getName(), TypeDef.erasure(p.getType()))).toList())
.returns(TypeDef.erasure(returnType))
.build((aThis, methodParameters) -> aThis.superRef((ClassTypeDef) TypeDef.erasure(methodElement.getOwningType()))
.invoke(methodElement, methodParameters).returning())
interceptedProxyBridgeMethod
);
}
}
Expand All @@ -694,8 +696,8 @@ public void visitAroundMethod(TypedElement beanType,
int methodIndex = beanDefinitionWriter.visitExecutableMethod(
beanType,
methodElement,
interceptedProxyClassName,
interceptedProxyBridgeMethodName
interceptedProxyDef,
interceptedProxyBridgeMethod
);
int index = proxyMethodCount++;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2620,14 +2620,14 @@ public int visitExecutableMethod(TypedElement declaringBean,
* @param declaringType The declaring type of the method. Either a Class or a string representing the
* name of the type
* @param methodElement The method element
* @param interceptedProxyClassName The intercepted proxy class name
* @param interceptedProxyBridgeMethodName The intercepted proxy bridge method name
* @param interceptedProxyType The intercepted proxy type
* @param interceptedProxyBridgeMethod The intercepted proxy bridge method name
* @return The index of a new method.
*/
public int visitExecutableMethod(TypedElement declaringType,
MethodElement methodElement,
String interceptedProxyClassName,
String interceptedProxyBridgeMethodName) {
ClassTypeDef interceptedProxyType,
MethodDef interceptedProxyBridgeMethod) {

if (executableMethodsDefinitionWriter == null) {
executableMethodsDefinitionWriter = new ExecutableMethodsDefinitionWriter(
Expand All @@ -2639,7 +2639,7 @@ public int visitExecutableMethod(TypedElement declaringType,
visitorContext
);
}
return executableMethodsDefinitionWriter.visitExecutableMethod(declaringType, methodElement, interceptedProxyClassName, interceptedProxyBridgeMethodName);
return executableMethodsDefinitionWriter.visitExecutableMethod(declaringType, methodElement, interceptedProxyType, interceptedProxyBridgeMethod);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,21 +160,21 @@ private DispatchTarget findDispatchTarget(TypedElement declaringType, MethodElem
*
* @param declaringType The declaring type
* @param methodElement The method element
* @param interceptedProxyClassName The interceptedProxyClassName
* @param interceptedProxyBridgeMethodName The interceptedProxyBridgeMethodName
* @param interceptedProxyType The interceptedProxyType
* @param interceptedProxyBridgeMethod The interceptedProxyBridgeMethod
* @return the target index
*/
public int addInterceptedMethod(TypedElement declaringType,
MethodElement methodElement,
String interceptedProxyClassName,
String interceptedProxyBridgeMethodName) {
ClassTypeDef interceptedProxyType,
MethodDef interceptedProxyBridgeMethod) {
hasInterceptedMethod = true;
return addDispatchTarget(new InterceptableMethodDispatchTarget(
findDispatchTarget(declaringType, methodElement, false),
declaringType,
methodElement,
interceptedProxyClassName,
interceptedProxyBridgeMethodName)
interceptedProxyType,
interceptedProxyBridgeMethod)
);
}

Expand Down Expand Up @@ -770,20 +770,20 @@ public ExpressionDef dispatchOneExpression(ExpressionDef target, ExpressionDef v
public static final class InterceptableMethodDispatchTarget extends AbstractDispatchTarget {
private final TypedElement declaringType;
private final DispatchTarget dispatchTarget;
private final String interceptedProxyClassName;
private final String interceptedProxyBridgeMethodName;
private final ClassTypeDef interceptedProxyType;
private final MethodDef interceptedProxyBridgeMethod;
private final MethodElement methodElement;

private InterceptableMethodDispatchTarget(DispatchTarget dispatchTarget,
TypedElement declaringType,
MethodElement methodElement,
String interceptedProxyClassName,
String interceptedProxyBridgeMethodName) {
ClassTypeDef interceptedProxyType,
MethodDef interceptedProxyBridgeMethod) {
this.declaringType = declaringType;
this.methodElement = methodElement;
this.dispatchTarget = dispatchTarget;
this.interceptedProxyClassName = interceptedProxyClassName;
this.interceptedProxyBridgeMethodName = interceptedProxyBridgeMethodName;
this.interceptedProxyType = interceptedProxyType;
this.interceptedProxyBridgeMethod = interceptedProxyBridgeMethod;
}

@Override
Expand Down Expand Up @@ -811,23 +811,21 @@ public StatementDef dispatch(ExpressionDef target, ExpressionDef valuesArray) {
VariableDef.Field interceptableField = new VariableDef.This()
.field(FIELD_INTERCEPTABLE, TypeDef.of(boolean.class));

ClassTypeDef proxyType = ClassTypeDef.of(interceptedProxyClassName);

return interceptableField.isTrue().and(target.instanceOf(proxyType))
return interceptableField.isTrue().and(target.instanceOf(interceptedProxyType))
.doIfElse(
invokeProxyBridge(proxyType, target, valuesArray),
invokeProxyBridge(interceptedProxyType, target, valuesArray),
dispatchTarget.dispatch(target, valuesArray)
);
}

private StatementDef invokeProxyBridge(ClassTypeDef proxyType, ExpressionDef target, ExpressionDef valuesArray) {
boolean suspend = methodElement.isSuspend();
ExpressionDef.InvokeInstanceMethod invoke = target.cast(proxyType).invoke(
interceptedProxyBridgeMethodName,
Arrays.stream(methodElement.getSuspendParameters()).map(p -> TypeDef.of(p.getType())).toList(),
suspend ? TypeDef.OBJECT : TypeDef.of(methodElement.getReturnType()),
IntStream.range(0, methodElement.getSuspendParameters().length).mapToObj(valuesArray::arrayElement).toList()
);
ExpressionDef.InvokeInstanceMethod invoke = target.cast(proxyType)
.invoke(
interceptedProxyBridgeMethod,
IntStream.range(0, methodElement.getSuspendParameters().length).mapToObj(valuesArray::arrayElement).toList()
);
if (dispatchTarget.getMethodElement().getReturnType().isVoid() && !suspend) {
return StatementDef.multi(
invoke,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,14 @@ public boolean isSuspend(int index) {
* @param declaringType The declaring type of the method. Either a Class or a string representing the
* name of the type
* @param methodElement The method element
* @param interceptedProxyClassName The intercepted proxy class name
* @param interceptedProxyBridgeMethodName The intercepted proxy bridge method name
* @param interceptedProxyType The intercepted proxy type
* @param interceptedProxyBridgeMethod The intercepted proxy bridge method
* @return The method index
*/
public int visitExecutableMethod(TypedElement declaringType,
MethodElement methodElement,
String interceptedProxyClassName,
String interceptedProxyBridgeMethodName) {
ClassTypeDef interceptedProxyType,
MethodDef interceptedProxyBridgeMethod) {
evaluatedExpressionProcessor.processEvaluatedExpressions(methodElement);

String methodKey = methodElement.getName() +
Expand All @@ -219,10 +219,10 @@ public int visitExecutableMethod(TypedElement declaringType,
return index;
}
addedMethods.add(methodKey);
if (interceptedProxyClassName == null) {
if (interceptedProxyType == null) {
return methodDispatchWriter.addMethod(declaringType, methodElement);
} else {
return methodDispatchWriter.addInterceptedMethod(declaringType, methodElement, interceptedProxyClassName, interceptedProxyBridgeMethodName);
return methodDispatchWriter.addInterceptedMethod(declaringType, methodElement, interceptedProxyType, interceptedProxyBridgeMethod);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ private static ExpressionDef invokeKotlinDefaultMethod(ClassElement declaringTyp
newValues.addAll(List.of(masks)); // Bit mask of defaults
newValues.add(ExpressionDef.nullValue()); // Last parameter is just a marker and is always null

MethodDef defaultKotlinMethod = MethodGenUtils.asDefaultKotlinMethod(TypeDef.of(declaringType), methodElement, numberOfMasks);
MethodDef defaultKotlinMethod = MethodGenUtils.asDefaultKotlinMethod(TypeDef.erasure(declaringType), methodElement, numberOfMasks);

return ClassTypeDef.of(declaringType).invokeStatic(defaultKotlinMethod, newValues);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,114 @@ class TestInterceptor implements Interceptor {
interceptor.invoked
}

void 'test apply interceptor using interface return ENUM'() {
given:
ApplicationContext context = buildContext('''
package test;

import java.lang.annotation.*;
import io.micronaut.aop.*;
import jakarta.inject.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

interface IMyBean {
@TestAnn
<E extends Enum<E>> E test();
}

@Singleton
class MyBean implements IMyBean {

@Override
public <E extends Enum<E>> E test() {
return null;
}

}

@Inherited
@Retention(RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@InterceptorBinding
@interface TestAnn {
}

@InterceptorBean(TestAnn.class)
class TestInterceptor implements Interceptor {
boolean invoked = false;
@Override
public Object intercept(InvocationContext context) {
invoked = true;
return context.proceed();
}
}

''')
def instance = getBean(context, 'test.MyBean')
def interceptor = getBean(context, 'test.TestInterceptor')

when:
instance.test() == null

then:"the interceptor was invoked"
instance instanceof Intercepted
interceptor.invoked
}

void 'test apply interceptor using interface parameter ENUM'() {
given:
ApplicationContext context = buildContext('''
package test;

import java.lang.annotation.*;
import io.micronaut.aop.*;
import jakarta.inject.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

interface IMyBean {
@TestAnn
<E extends Enum<E>> String test(E param);
}

@Singleton
class MyBean implements IMyBean {

@Override
public <E extends Enum<E>> String test(E param) {
return "sss";
}

}

@Inherited
@Retention(RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@InterceptorBinding
@interface TestAnn {
}

@InterceptorBean(TestAnn.class)
class TestInterceptor implements Interceptor {
boolean invoked = false;
@Override
public Object intercept(InvocationContext context) {
invoked = true;
return context.proceed();
}
}

''')
def instance = getBean(context, 'test.MyBean')
def interceptor = getBean(context, 'test.TestInterceptor')

when:
instance.test(null) == "sss"

then:"the interceptor was invoked"
instance instanceof Intercepted
interceptor.invoked
}

void 'test stereotype method level interceptor matching'() {
given:
ApplicationContext context = buildContext('''
Expand Down
Loading