Skip to content

Commit

Permalink
Adds support for KSP (#8462)
Browse files Browse the repository at this point in the history
Adds a new `micronaut-inject-kotlin` module that implements initial support for Kotlin Symbol Processing (KSP).

Co-authored-by: jameskleeh <[email protected]>
  • Loading branch information
graemerocher and jameskleeh authored Jan 23, 2023
1 parent 3cac50a commit d500b2f
Show file tree
Hide file tree
Showing 740 changed files with 41,159 additions and 400 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1104,9 +1104,17 @@ private AnnotationMetadata buildInternalMulti(
Optional<String> value = annotationMetadata.stringValue(DefaultScope.class);
value.ifPresent(name -> annotationMetadata.addDeclaredAnnotation(name, Collections.emptyMap()));
}
if (annotationMetadata instanceof MutableAnnotationMetadata mutableAnnotationMetadata) {
postProcess(mutableAnnotationMetadata, element);
}
return annotationMetadata;
}

protected void postProcess(MutableAnnotationMetadata mutableAnnotationMetadata,
T element) {
//no-op
}

private void includeAnnotations(DefaultAnnotationMetadata annotationMetadata,
T element,
boolean originatingElementIsSameParent,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2017-2022 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.inject.annotation.internal;

import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.inject.annotation.NamedAnnotationTransformer;
import io.micronaut.inject.visitor.VisitorContext;

import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.List;

/**
* Allows treating the Kotlin deprecated annotation as the Java one.
*
* @since 4.0.0
*/
public final class KotlinDeprecatedTransformer implements NamedAnnotationTransformer {
@Override
public String getName() {
return "kotlin.Deprecated";
}

@Override
public List<AnnotationValue<?>> transform(AnnotationValue<Annotation> annotation, VisitorContext visitorContext) {
return Collections.singletonList(
AnnotationValue.builder(Deprecated.class).build()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@ public interface ElementFactory<E, C extends E, M extends E, F extends E> {
* @param resolvedGenerics The resolved generics
* @return The class element
* @since 4.0.0
* @deprecated no longer used
*/
@NonNull
@Deprecated
ClassElement newClassElement(@NonNull C type,
@NonNull ElementAnnotationMetadataFactory annotationMetadataFactory,
@NonNull Map<String, ClassElement> resolvedGenerics);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,16 @@ default AccessKind getWriteAccessKind() {
return AccessKind.METHOD;
}

/**
* Does a this property override the given property. Supported only with languages that have native properties.
* @param overridden The overridden method.
* @return True this property overrides the given property.
* @since 4.0.0
*/
default boolean overrides(PropertyElement overridden) {
return false;
}

/**
* The access type for bean properties.
* @since 4.0.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,20 @@
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.clhm.ConcurrentLinkedHashMap;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.ConstructorElement;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.ast.ElementModifier;
import io.micronaut.inject.ast.ElementQuery;
import io.micronaut.inject.ast.FieldElement;
import io.micronaut.inject.ast.MemberElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.PropertyElement;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
Expand All @@ -51,7 +53,7 @@
@Internal
public abstract class EnclosedElementsQuery<C, N> {

private final Map<N, io.micronaut.inject.ast.Element> elementsCache = new HashMap<>();
private final Map<N, Element> elementsCache = new ConcurrentLinkedHashMap.Builder<N, Element>().maximumWeightedCapacity(200).build();

/**
* Return the elements that match the given query.
Expand Down Expand Up @@ -172,6 +174,8 @@ private boolean reduceElements(io.micronaut.inject.ast.Element newElement,
if (!result.isIncludeOverriddenMethods()) {
if (newElement instanceof MethodElement && existingElement instanceof MethodElement) {
return ((MethodElement) newElement).overrides((MethodElement) existingElement);
} else if (newElement instanceof PropertyElement newPropertyElement && existingElement instanceof PropertyElement existingPropertyElement) {
return newPropertyElement.overrides(existingPropertyElement);
}
}
return false;
Expand All @@ -188,7 +192,13 @@ private Collection<io.micronaut.inject.ast.Element> getAllElements(C classNode,
Set<io.micronaut.inject.ast.Element> addedFromClassElements = new LinkedHashSet<>();
classElements:
for (N element : classElements) {
io.micronaut.inject.ast.Element newElement = elementsCache.computeIfAbsent(element, this::toAstElement);
N cacheKey = getCacheKey(element);
io.micronaut.inject.ast.Element newElement = elementsCache.computeIfAbsent(cacheKey, e -> this.toAstElement(e, result.getElementType()));
if (!result.getElementType().isInstance(newElement)) {
// dirty cache
elementsCache.remove(cacheKey);
newElement = elementsCache.computeIfAbsent(cacheKey, e -> this.toAstElement(e, result.getElementType()));
}
for (Iterator<io.micronaut.inject.ast.Element> iterator = elements.iterator(); iterator.hasNext(); ) {
io.micronaut.inject.ast.Element existingElement = iterator.next();
if (newElement.equals(existingElement)) {
Expand All @@ -208,6 +218,15 @@ private Collection<io.micronaut.inject.ast.Element> getAllElements(C classNode,
return elements;
}

/**
* get the cache key.
* @param element The element
* @return The cache key
*/
protected N getCacheKey(N element) {
return element;
}

private void collectHierarchy(C classNode,
boolean onlyDeclared,
List<List<N>> hierarchy,
Expand Down Expand Up @@ -279,9 +298,10 @@ protected Set<N> getExcludedNativeElements(@NonNull ElementQuery.Result<?> resul
* Converts the native element to the AST element.
*
* @param enclosedElement The native element.
* @param elementType The result type
* @return The AST element
*/
@NonNull
protected abstract io.micronaut.inject.ast.Element toAstElement(N enclosedElement);
protected abstract io.micronaut.inject.ast.Element toAstElement(N enclosedElement, Class<?> elementType);

}
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ private void writeInstantiateMethod(ClassWriter classWriter, MethodElement const

private void invokeBeanConstructor(GeneratorAdapter writer, MethodElement constructor, BiConsumer<GeneratorAdapter, MethodElement> argumentsPusher) {
boolean isConstructor = constructor instanceof ConstructorElement;
boolean isCompanion = constructor != defaultConstructor && constructor.getDeclaringType().getSimpleName().endsWith("$Companion");
boolean isCompanion = constructor.getDeclaringType().getSimpleName().endsWith("$Companion");

List<ParameterElement> constructorArguments = Arrays.asList(constructor.getParameters());
Collection<Type> argumentTypes = constructorArguments.stream().map(pe ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.micronaut.aop.internal.intercepted.InterceptedMethodUtil;
import io.micronaut.aop.writer.AopProxyWriter;
import io.micronaut.context.RequiresCondition;
import io.micronaut.context.annotation.Executable;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationUtil;
Expand Down Expand Up @@ -90,7 +91,7 @@ protected void visitAnnotationMetadata(BeanDefinitionVisitor writer, AnnotationM
annotation.stringValue(RequiresCondition.MEMBER_BEAN_PROPERTY)
.ifPresent(beanProperty -> {
annotation.stringValue(RequiresCondition.MEMBER_BEAN)
.map(className -> visitorContext.getClassElement(className, visitorContext.getElementAnnotationMetadataFactory().readOnly()).get())
.flatMap(className -> visitorContext.getClassElement(className, visitorContext.getElementAnnotationMetadataFactory().readOnly()))
.ifPresent(classElement -> {
String requiredValue = annotation.stringValue().orElse(null);
String notEqualsValue = annotation.stringValue(RequiresCondition.MEMBER_NOT_EQUALS).orElse(null);
Expand Down Expand Up @@ -121,6 +122,12 @@ protected boolean visitIntrospectedMethod(BeanDefinitionVisitor visitor, ClassEl
|| InterceptedMethodUtil.hasDeclaredAroundAdvice(methodElement.getAnnotationMetadata())) {
addToIntroduction(aopProxyWriter, typeElement, methodElement, false);
return true;
} else if (!methodElement.isAbstract() && methodElement.hasDeclaredStereotype(Executable.class)) {
aopProxyWriter.visitExecutableMethod(
typeElement,
methodElement,
visitorContext
);
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.micronaut.inject.processing;

import io.micronaut.aop.Interceptor;
import io.micronaut.aop.internal.intercepted.InterceptedMethodUtil;
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.DefaultScope;
Expand Down Expand Up @@ -70,6 +71,9 @@ public static BeanDefinitionCreator produce(ClassElement classElement, VisitorCo
if (classElement.hasStereotype("groovy.lang.Singleton")) {
throw new ProcessingException(classElement, "Class annotated with groovy.lang.Singleton instead of jakarta.inject.Singleton. Import jakarta.inject.Singleton to use Micronaut Dependency Injection.");
}
if (classElement.isEnum()) {
throw new ProcessingException(classElement, "Enum types cannot be defined as beans");
}
return new DeclaredBeanElementCreator(classElement, visitorContext, false);
}
return Collections::emptyList;
Expand Down Expand Up @@ -110,7 +114,7 @@ private static boolean containsInjectPoint(AnnotationMetadata annotationMetadata
}

private static boolean isAopProxyType(ClassElement classElement) {
return !classElement.isAssignable("io.micronaut.aop.Interceptor") && InterceptedMethodUtil.hasAroundStereotype(classElement.getAnnotationMetadata());
return !classElement.isAssignable(Interceptor.class) && InterceptedMethodUtil.hasAroundStereotype(classElement.getAnnotationMetadata());
}

public static boolean isDeclaredBeanInMetadata(AnnotationMetadata concreteClassMetadata) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ private void build(BeanDefinitionVisitor visitor) {
if (processAsProperties()) {
memberQuery = memberQuery.excludePropertyElements();
for (PropertyElement propertyElement : classElement.getBeanProperties()) {
propertyElement.getField().ifPresent(processedFields::add);
visitPropertyInternal(visitor, propertyElement);
}
} else {
Expand All @@ -176,7 +177,7 @@ private void build(BeanDefinitionVisitor visitor) {
visitFieldInternal(visitor, fieldElement);
} else if (memberElement instanceof MethodElement methodElement) {
visitMethodInternal(visitor, methodElement);
} else {
} else if (!(memberElement instanceof PropertyElement)) {
throw new IllegalStateException("Unknown element");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,20 @@ private void buildProducedBeanDefinition(BeanDefinitionWriter producedBeanDefini
producedType.annotate(ConfigurationReader.class, builder -> builder.member(ConfigurationReader.PREFIX, ConfigurationUtils.getRequiredTypePath(producedType)));
}

if (producingElement instanceof MethodElement) {
producedBeanDefinitionWriter.visitBeanFactoryMethod(classElement, (MethodElement) producingElement);
if (producingElement instanceof PropertyElement propertyElement) {
MethodElement readMethod = propertyElement.getReadMethod().orElse(null);
if (readMethod != null) {
producedBeanDefinitionWriter.visitBeanFactoryMethod(classElement, readMethod);
} else {
FieldElement fieldElement = propertyElement.getField().orElse(null);
if (fieldElement != null && fieldElement.isAccessible()) {
producedBeanDefinitionWriter.visitBeanFactoryField(classElement, fieldElement);
} else {
throw new ProcessingException(producingElement, "A property element that defines the @Bean annotation must have an accessible getter or field");
}
}
} else if (producingElement instanceof MethodElement methodElement) {
producedBeanDefinitionWriter.visitBeanFactoryMethod(classElement, methodElement);
} else {
producedBeanDefinitionWriter.visitBeanFactoryField(classElement, (FieldElement) producingElement);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.ElementQuery;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.PropertyElement;
import io.micronaut.inject.visitor.VisitorContext;
import io.micronaut.inject.writer.BeanDefinitionVisitor;

Expand Down Expand Up @@ -70,7 +71,22 @@ public void buildInternal() {
for (MethodElement methodElement : methods) {
visitIntrospectedMethod(aopProxyWriter, classElement, methodElement);
}
List<PropertyElement> beanProperties = classElement.getSyntheticBeanProperties();
for (PropertyElement beanProperty : beanProperties) {
handlePropertyMethod(aopProxyWriter, methods, beanProperty.getReadMethod().orElse(null));
handlePropertyMethod(aopProxyWriter, methods, beanProperty.getWriteMethod().orElse(null));
}
beanDefinitionWriters.add(aopProxyWriter);
}

private void handlePropertyMethod(BeanDefinitionVisitor aopProxyWriter, List<MethodElement> methods, MethodElement method) {
if (method != null && method.isAbstract() && !methods.contains(method)) {
visitIntrospectedMethod(
aopProxyWriter,
this.classElement,
method
);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,15 @@
public final class ProcessingException extends RuntimeException {

private final transient Element originatingElement;
private final String message;

public ProcessingException(Element element, String message) {
super(message);
this.originatingElement = element;
this.message = message;
}

public ProcessingException(Element originatingElement, String message, Throwable cause) {
super(message, cause);
this.originatingElement = originatingElement;
}

@Nullable
Expand All @@ -42,8 +46,4 @@ public Object getOriginatingElement() {
return null;
}

@Override
public String getMessage() {
return message;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.util.Toggleable;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.ast.*;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.ast.FieldElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.ParameterElement;
import io.micronaut.inject.ast.TypedElement;
import io.micronaut.inject.configuration.ConfigurationMetadataBuilder;
import io.micronaut.inject.visitor.VisitorContext;
import org.objectweb.asm.Type;
Expand Down Expand Up @@ -136,7 +141,7 @@ void visitDefaultConstructor(

/**
* Alter the super class of this bean definition. The passed class should be a subclass of
* {@link io.micronaut.context.AbstractBeanDefinition}.
* {@link io.micronaut.context.AbstractInitializableBeanDefinition}.
*
* @param name The super type
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,21 @@ public BeanDefinitionWriter(Element beanProducingElement,
}
final ClassElement declaringType = factoryMethodElement.getOwningType();
this.beanDefinitionName = declaringType.getPackageName() + "." + prefixClassName(declaringType.getSimpleName()) + "$" + upperCaseMethodName + uniqueIdentifier + CLASS_SUFFIX;
} else if (beanProducingElement instanceof PropertyElement factoryPropertyElement) {
autoApplyNamedToBeanProducingElement(beanProducingElement);
final ClassElement producedElement = factoryPropertyElement.getGenericType();
this.beanTypeElement = producedElement;
this.packageName = producedElement.getPackageName();
this.isInterface = producedElement.isInterface();
this.isAbstract = beanProducingElement.isAbstract();
this.beanFullClassName = producedElement.getName();
this.beanSimpleClassName = producedElement.getSimpleName();
String upperCaseMethodName = NameUtils.capitalize(factoryPropertyElement.getName());
if (uniqueIdentifier == null) {
throw new IllegalArgumentException("Factory methods require passing a unique identifier");
}
final ClassElement declaringType = factoryPropertyElement.getOwningType();
this.beanDefinitionName = declaringType.getPackageName() + "." + prefixClassName(declaringType.getSimpleName()) + "$" + upperCaseMethodName + uniqueIdentifier + CLASS_SUFFIX;
} else if (beanProducingElement instanceof FieldElement factoryMethodElement) {
autoApplyNamedToBeanProducingElement(beanProducingElement);
final ClassElement producedElement = factoryMethodElement.getGenericField();
Expand All @@ -663,11 +678,10 @@ public BeanDefinitionWriter(Element beanProducingElement,
throw new IllegalArgumentException("Beans produced by addAssociatedBean(..) require passing a unique identifier");
}
final Element originatingElement = beanElementBuilder.getOriginatingElement();
if (originatingElement instanceof ClassElement) {
ClassElement originatingClass = (ClassElement) originatingElement;
if (originatingElement instanceof ClassElement originatingClass) {
this.beanDefinitionName = getAssociatedBeanName(uniqueIdentifier, originatingClass);
} else if (originatingElement instanceof MethodElement) {
ClassElement originatingClass = ((MethodElement) originatingElement).getDeclaringType();
} else if (originatingElement instanceof MethodElement methodElement) {
ClassElement originatingClass = methodElement.getDeclaringType();
this.beanDefinitionName = getAssociatedBeanName(uniqueIdentifier, originatingClass);
} else {
throw new IllegalArgumentException("Unsupported originating element");
Expand Down
Loading

0 comments on commit d500b2f

Please sign in to comment.