Skip to content

Commit

Permalink
Allow access to 'this' in expressions / support introspections (#9123)
Browse files Browse the repository at this point in the history
* Allows access to `this` in expressions in certain contexts (currently only executable methods), fails compilation if used in an invalid context
* Include expression processing in introspections for properties and methods.
  • Loading branch information
graemerocher authored Apr 17, 2023
1 parent 9820df3 commit f8bd12d
Show file tree
Hide file tree
Showing 47 changed files with 572 additions and 313 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public InterceptorChain(Interceptor<B, R>[] interceptors,
this.executionHandle = method;
AnnotationMetadata metadata = executionHandle.getAnnotationMetadata();
if (originalParameters.length > 0 && metadata instanceof EvaluatedAnnotationMetadata eam) {
this.annotationMetadata = eam.withArguments(originalParameters);
this.annotationMetadata = eam.withArguments(target, originalParameters);
} else {
this.annotationMetadata = metadata;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public void process(BeanDefinition<?> beanDefinition, ExecutableMethod<?, ?> met
Object bean = beanContext.getBean(beanType, declaredQualifier);
AnnotationValue<Scheduled> finalAnnotationValue = scheduledAnnotation;
if (finalAnnotationValue instanceof EvaluatedAnnotationValue<Scheduled> evaluated) {
finalAnnotationValue = evaluated.withArguments(boundExecutable.getBoundArguments());
finalAnnotationValue = evaluated.withArguments(bean, boundExecutable.getBoundArguments());
}
boolean shouldRun = finalAnnotationValue.booleanValue(MEMBER_CONDITION).orElse(true);
if (shouldRun) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.Toggleable;
import io.micronaut.core.value.OptionalValues;
import io.micronaut.expressions.context.ExpressionWithContext;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.ExecutableMethod;
import io.micronaut.inject.ProxyBeanDefinition;
Expand Down Expand Up @@ -1302,11 +1301,6 @@ public AnnotationMetadata getAnnotationMetadata() {
return proxyBeanDefinitionWriter.getAnnotationMetadata();
}

@Override
public Set<ExpressionWithContext> getEvaluatedExpressions() {
return proxyBeanDefinitionWriter.getEvaluatedExpressions();
}

@Override
public void visitConfigBuilderField(ClassElement type, String field, AnnotationMetadata annotationMetadata, ConfigurationMetadataBuilder metadataBuilder, boolean isInterface) {
proxyBeanDefinitionWriter.visitConfigBuilderField(type, field, annotationMetadata, metadataBuilder, isInterface);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.ConstructorElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.ParameterElement;
import io.micronaut.inject.ast.PropertyElement;
Expand Down Expand Up @@ -47,19 +48,34 @@ public class DefaultExpressionCompilationContext implements ExtensibleExpression
private final Collection<ClassElement> classElements;
private final MethodElement methodElement;

private final ClassElement thisType;

DefaultExpressionCompilationContext(ClassElement... classElements) {
this(null, classElements);
this(null, null, classElements);
}

private DefaultExpressionCompilationContext(MethodElement methodElement,
private DefaultExpressionCompilationContext(ClassElement thisType,
MethodElement methodElement,
ClassElement... classElements) {
this.thisType = thisType;
this.methodElement = methodElement;
this.classElements = Arrays.asList(classElements);
}

@Override
public ExtensibleExpressionCompilationContext withThis(ClassElement classElement) {
return new DefaultExpressionCompilationContext(
classElement,
methodElement,
classElements.toArray(ClassElement[]::new)
);
}

@Override
public DefaultExpressionCompilationContext extendWith(MethodElement methodElement) {
ClassElement resolvedThis = methodElement.isStatic() || methodElement instanceof ConstructorElement ? null : methodElement.getOwningType();
return new DefaultExpressionCompilationContext(
resolvedThis,
methodElement,
classElements.toArray(ClassElement[]::new)
);
Expand All @@ -68,11 +84,17 @@ public DefaultExpressionCompilationContext extendWith(MethodElement methodElemen
@Override
public DefaultExpressionCompilationContext extendWith(ClassElement classElement) {
return new DefaultExpressionCompilationContext(
this.thisType,
this.methodElement,
ArrayUtils.concat(classElements.toArray(ClassElement[]::new), classElement)
);
}

@Override
public ClassElement findThis() {
return thisType;
}

@Override
public List<MethodElement> findMethods(String name) {
return classElements.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@ private DefaultExpressionCompilationContext recreateContext() {
@NonNull
public ExpressionCompilationContext buildContextForMethod(@NonNull EvaluatedExpressionReference expression,
@NonNull MethodElement methodElement) {
return buildForExpression(expression)
return buildForExpression(expression, null)
.extendWith(methodElement);
}

@Override
@NonNull
public ExpressionCompilationContext buildContext(EvaluatedExpressionReference expression) {
return buildForExpression(expression);
public ExpressionCompilationContext buildContext(EvaluatedExpressionReference expression, ClassElement thisElement) {
return buildForExpression(expression, thisElement);
}

@Override
Expand All @@ -75,7 +75,7 @@ public ExpressionCompilationContextFactory registerContextClass(ClassElement con
return this;
}

private ExtensibleExpressionCompilationContext buildForExpression(EvaluatedExpressionReference expression) {
private ExtensibleExpressionCompilationContext buildForExpression(EvaluatedExpressionReference expression, ClassElement thisElement) {
String annotationName = expression.annotationName();
String memberName = expression.annotationMember();

Expand All @@ -87,6 +87,9 @@ private ExtensibleExpressionCompilationContext buildForExpression(EvaluatedExpre
evaluationContext = addAnnotationMemberEvaluationContext(evaluationContext, annotation, memberName);
}

if (thisElement != null) {
return evaluationContext.withThis(thisElement);
}
return evaluationContext;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.ParameterElement;
import io.micronaut.inject.ast.PropertyElement;
Expand All @@ -33,6 +35,12 @@
@Internal
public interface ExpressionCompilationContext {

/**
* @return Find the type that represents this.
*/
@Nullable
ClassElement findThis();

/**
* Search methods in compilation context by name.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import io.micronaut.core.annotation.Experimental;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.expressions.EvaluatedExpressionReference;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.MethodElement;
Expand All @@ -42,11 +43,12 @@ ExpressionCompilationContext buildContextForMethod(@NonNull EvaluatedExpressionR
/**
* Builds expression evaluation context for expression reference.
*
* @param expression expression reference
* @param expression expression reference
* @param thisElement
* @return evaluation context for method
*/
@NonNull
ExpressionCompilationContext buildContext(EvaluatedExpressionReference expression);
ExpressionCompilationContext buildContext(EvaluatedExpressionReference expression, @Nullable ClassElement thisElement);

/**
* Adds evaluated expression context class element to context loader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
*/
@Internal
public interface ExtensibleExpressionCompilationContext extends ExpressionCompilationContext {

/**
* @param classElement The type that represents this.
* @return extended context
*/
ExtensibleExpressionCompilationContext withThis(@NonNull ClassElement classElement);

/**
* Extends compilation context with method element. Compilation context can only include
* one method at the same time, so this method will return the context which will
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.micronaut.expressions.parser.ast.access.EnvironmentAccess;
import io.micronaut.expressions.parser.ast.access.SubscriptOperator;
import io.micronaut.expressions.parser.ast.access.PropertyAccess;
import io.micronaut.expressions.parser.ast.access.ThisAccess;
import io.micronaut.expressions.parser.ast.conditional.ElvisOperator;
import io.micronaut.expressions.parser.ast.conditional.TernaryExpression;
import io.micronaut.expressions.parser.ast.literal.BoolLiteral;
Expand Down Expand Up @@ -62,48 +63,7 @@
import java.util.ArrayList;
import java.util.List;

import static io.micronaut.expressions.parser.token.TokenType.AND;
import static io.micronaut.expressions.parser.token.TokenType.BEAN_CONTEXT;
import static io.micronaut.expressions.parser.token.TokenType.BOOL;
import static io.micronaut.expressions.parser.token.TokenType.COLON;
import static io.micronaut.expressions.parser.token.TokenType.COMMA;
import static io.micronaut.expressions.parser.token.TokenType.DECREMENT;
import static io.micronaut.expressions.parser.token.TokenType.DIV;
import static io.micronaut.expressions.parser.token.TokenType.DOT;
import static io.micronaut.expressions.parser.token.TokenType.DOUBLE;
import static io.micronaut.expressions.parser.token.TokenType.ELVIS;
import static io.micronaut.expressions.parser.token.TokenType.EMPTY;
import static io.micronaut.expressions.parser.token.TokenType.ENVIRONMENT;
import static io.micronaut.expressions.parser.token.TokenType.EQ;
import static io.micronaut.expressions.parser.token.TokenType.EXPRESSION_CONTEXT_REF;
import static io.micronaut.expressions.parser.token.TokenType.FLOAT;
import static io.micronaut.expressions.parser.token.TokenType.GT;
import static io.micronaut.expressions.parser.token.TokenType.GTE;
import static io.micronaut.expressions.parser.token.TokenType.IDENTIFIER;
import static io.micronaut.expressions.parser.token.TokenType.R_SQUARE;
import static io.micronaut.expressions.parser.token.TokenType.TYPE_IDENTIFIER;
import static io.micronaut.expressions.parser.token.TokenType.INCREMENT;
import static io.micronaut.expressions.parser.token.TokenType.INSTANCEOF;
import static io.micronaut.expressions.parser.token.TokenType.INT;
import static io.micronaut.expressions.parser.token.TokenType.LONG;
import static io.micronaut.expressions.parser.token.TokenType.LT;
import static io.micronaut.expressions.parser.token.TokenType.LTE;
import static io.micronaut.expressions.parser.token.TokenType.L_PAREN;
import static io.micronaut.expressions.parser.token.TokenType.L_SQUARE;
import static io.micronaut.expressions.parser.token.TokenType.MATCHES;
import static io.micronaut.expressions.parser.token.TokenType.MINUS;
import static io.micronaut.expressions.parser.token.TokenType.MOD;
import static io.micronaut.expressions.parser.token.TokenType.MUL;
import static io.micronaut.expressions.parser.token.TokenType.NE;
import static io.micronaut.expressions.parser.token.TokenType.NOT;
import static io.micronaut.expressions.parser.token.TokenType.NULL;
import static io.micronaut.expressions.parser.token.TokenType.OR;
import static io.micronaut.expressions.parser.token.TokenType.PLUS;
import static io.micronaut.expressions.parser.token.TokenType.POW;
import static io.micronaut.expressions.parser.token.TokenType.QMARK;
import static io.micronaut.expressions.parser.token.TokenType.R_PAREN;
import static io.micronaut.expressions.parser.token.TokenType.SAFE_NAV;
import static io.micronaut.expressions.parser.token.TokenType.STRING;
import static io.micronaut.expressions.parser.token.TokenType.*;

/**
* Parser for building AST for single evaluated expression.
Expand Down Expand Up @@ -374,13 +334,19 @@ private ExpressionNode primaryExpression() {
case IDENTIFIER -> evaluationContextAccess(false);
case BEAN_CONTEXT -> beanContextAccess();
case ENVIRONMENT -> environmentAccess();
case THIS -> thisAccess();
case TYPE_IDENTIFIER -> typeIdentifier(true);
case L_PAREN -> parenthesizedExpression();
case STRING, INT, LONG, DOUBLE, FLOAT, BOOL, NULL -> literal();
default -> throw new ExpressionParsingException("Unexpected token: " + lookahead.value());
};
}

private ExpressionNode thisAccess() {
eat(THIS);
return new ThisAccess();
}

// EvaluationContextAccess
// : '#' Identifier
// | '#' Identifier MethodArguments
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright 2017-2023 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.expressions.parser.ast.access;

import io.micronaut.core.annotation.Internal;
import io.micronaut.expressions.parser.ast.ExpressionNode;
import io.micronaut.expressions.parser.compilation.ExpressionVisitorContext;
import io.micronaut.expressions.parser.exception.ExpressionCompilationException;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.processing.JavaModelUtils;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;

import static io.micronaut.expressions.parser.ast.util.TypeDescriptors.EVALUATION_CONTEXT_TYPE;

/**
* Enables access to 'this' in non-static contexts.
*/
@Internal
public final class ThisAccess extends ExpressionNode {
@Override
protected void generateBytecode(ExpressionVisitorContext ctx) {
GeneratorAdapter mv = ctx.methodVisitor();
mv.loadArg(0);
mv.invokeInterface(EVALUATION_CONTEXT_TYPE, new Method("getThis", Type.getType(Object.class), new Type[0]));
mv.checkCast(resolveType(ctx));
}

@Override
protected ClassElement doResolveClassElement(ExpressionVisitorContext ctx) {
ClassElement thisType = ctx.compilationContext().findThis();
if (thisType == null) {
throw new ExpressionCompilationException(
"Cannot reference 'this' from the current context.");

}
return thisType;
}

@Override
protected Type doResolveType(ExpressionVisitorContext ctx) {
return JavaModelUtils.getTypeReference(doResolveClassElement(ctx));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public enum TokenType {
IDENTIFIER,
BEAN_CONTEXT,
ENVIRONMENT,
THIS,
TYPE_IDENTIFIER,
EXPRESSION_CONTEXT_REF,
DOT,
Expand Down
Loading

0 comments on commit f8bd12d

Please sign in to comment.