From 1fda9088ab7984c5a6c143431e2b9cf4d63bceea Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Mon, 7 Oct 2024 13:20:17 +0200 Subject: [PATCH] ArC: add validation for sealed types --- .../java/io/quarkus/arc/processor/Beans.java | 22 +- .../io/quarkus/arc/processor/BuiltinBean.java | 199 +++++++++--------- .../InterceptedRecordProducerTest.java | 75 +++++++ .../arc/test/sealed/DependentSealedTest.java | 28 +++ .../sealed/InterceptedSealedProducerTest.java | 78 +++++++ .../test/sealed/InterceptedSealedTest.java | 68 ++++++ .../NormalScopedSealedProducerTest.java | 46 ++++ .../test/sealed/NormalScopedSealedTest.java | 36 ++++ .../sealed/SingletonSealedProducerTest.java | 38 ++++ 9 files changed, 483 insertions(+), 107 deletions(-) create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/records/InterceptedRecordProducerTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/DependentSealedTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/InterceptedSealedProducerTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/InterceptedSealedTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/NormalScopedSealedProducerTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/NormalScopedSealedTest.java create mode 100644 independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/SingletonSealedProducerTest.java diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java index da540feb91778..22b11952cbf7e 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java @@ -39,6 +39,7 @@ import org.objectweb.asm.Opcodes; import io.quarkus.arc.processor.BeanDeployment.SkippedClass; +import io.quarkus.arc.processor.BuiltinBean.ValidatorContext; import io.quarkus.arc.processor.InjectionPointInfo.TypeAndQualifiers; import io.quarkus.arc.processor.Types.TypeClosure; import io.quarkus.gizmo.ClassTransformer; @@ -485,7 +486,7 @@ static void resolveInjectionPoint(BeanDeployment deployment, InjectionTargetInfo } BuiltinBean builtinBean = BuiltinBean.resolve(injectionPoint); if (builtinBean != null) { - builtinBean.validate(target, injectionPoint, errors::add); + builtinBean.getValidator().validate(new ValidatorContext(deployment, target, injectionPoint, errors::add)); // Skip built-in beans return; } @@ -827,7 +828,7 @@ static void validateBean(BeanInfo bean, List errors, Consumer errors, Consumer errors, Consumer errors, Consumer errors) { - validator.validate(injectionTarget, injectionPoint, errors); - } - DotName[] getRawTypeDotNames() { return rawTypeDotNames; } @@ -117,6 +113,10 @@ Generator getGenerator() { return generator; } + Validator getValidator() { + return validator; + } + public static boolean resolvesTo(InjectionPointInfo injectionPoint) { return resolve(injectionPoint) != null; } @@ -130,34 +130,17 @@ public static BuiltinBean resolve(InjectionPointInfo injectionPoint) { return null; } - public static class GeneratorContext { - - final ClassOutput classOutput; - final BeanDeployment beanDeployment; - final InjectionPointInfo injectionPoint; - final ClassCreator clazzCreator; - final MethodCreator constructor; - final String providerName; - final AnnotationLiteralProcessor annotationLiterals; - final InjectionTargetInfo targetInfo; - final ReflectionRegistration reflectionRegistration; - final Predicate injectionPointAnnotationsPredicate; - - public GeneratorContext(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, - ClassCreator clazzCreator, MethodCreator constructor, String providerName, - AnnotationLiteralProcessor annotationLiterals, InjectionTargetInfo targetInfo, - ReflectionRegistration reflectionRegistration, Predicate injectionPointAnnotationsPredicate) { - this.classOutput = classOutput; - this.beanDeployment = beanDeployment; - this.injectionPoint = injectionPoint; - this.clazzCreator = clazzCreator; - this.constructor = constructor; - this.providerName = providerName; - this.annotationLiterals = annotationLiterals; - this.targetInfo = targetInfo; - this.reflectionRegistration = reflectionRegistration; - this.injectionPointAnnotationsPredicate = injectionPointAnnotationsPredicate; - } + public record GeneratorContext( + ClassOutput classOutput, + BeanDeployment beanDeployment, + InjectionPointInfo injectionPoint, + ClassCreator clazzCreator, + MethodCreator constructor, + String providerName, + AnnotationLiteralProcessor annotationLiterals, + InjectionTargetInfo targetInfo, + ReflectionRegistration reflectionRegistration, + Predicate injectionPointAnnotationsPredicate) { } @FunctionalInterface @@ -170,13 +153,20 @@ interface Generator { } + public record ValidatorContext( + BeanDeployment beanDeployment, + InjectionTargetInfo injectionTarget, + InjectionPointInfo injectionPoint, + Consumer errors) { + } + @FunctionalInterface interface Validator { - Validator NOOP = (it, ip, e) -> { + Validator NOOP = ctx -> { }; - void validate(InjectionTargetInfo injectionTarget, InjectionPointInfo injectionPoint, Consumer errors); + void validate(ValidatorContext context); } @@ -449,117 +439,118 @@ private static ResultHandle loadInvokerTargetBean(InvokerInfo invoker, BytecodeC bytecode.load(invoker.targetBean.getIdentifier())); } - private static void validateInstance(InjectionTargetInfo injectionTarget, InjectionPointInfo injectionPoint, - Consumer errors) { - if (injectionPoint.getType().kind() != Kind.PARAMETERIZED_TYPE) { - errors.accept( - new DefinitionException("An injection point of raw type jakarta.enterprise.inject.Instance is defined: " - + injectionPoint.getTargetInfo())); - } else if (injectionPoint.getRequiredType().kind() == Kind.WILDCARD_TYPE) { - errors.accept( - new DefinitionException("Wildcard is not a legal type argument for jakarta.enterprise.inject.Instance: " + - injectionPoint.getTargetInfo())); - } else if (injectionPoint.getRequiredType().kind() == Kind.TYPE_VARIABLE) { - errors.accept(new DefinitionException( - "Type variable is not a legal type argument for jakarta.enterprise.inject.Instance: " + - injectionPoint.getTargetInfo())); + private static void validateInstance(ValidatorContext ctx) { + if (ctx.injectionPoint.getType().kind() != Kind.PARAMETERIZED_TYPE) { + ctx.errors.accept(new DefinitionException( + "An injection point of raw type jakarta.enterprise.inject.Instance is defined: " + + ctx.injectionPoint.getTargetInfo())); + } else if (ctx.injectionPoint.getRequiredType().kind() == Kind.WILDCARD_TYPE) { + ctx.errors.accept(new DefinitionException( + "Wildcard is not a legal type argument for jakarta.enterprise.inject.Instance: " + + ctx.injectionPoint.getTargetInfo())); + } else if (ctx.injectionPoint.getRequiredType().kind() == Kind.TYPE_VARIABLE) { + ctx.errors.accept(new DefinitionException( + "Type variable is not a legal type argument for jakarta.enterprise.inject.Instance: " + + ctx.injectionPoint.getTargetInfo())); } } - private static void validateList(InjectionTargetInfo injectionTarget, InjectionPointInfo injectionPoint, - Consumer errors) { - if (injectionPoint.getType().kind() != Kind.PARAMETERIZED_TYPE) { - errors.accept( - new DefinitionException("An injection point of raw type is defined: " + injectionPoint.getTargetInfo())); + private static void validateList(ValidatorContext ctx) { + if (ctx.injectionPoint.getType().kind() != Kind.PARAMETERIZED_TYPE) { + ctx.errors.accept(new DefinitionException( + "An injection point of raw type is defined: " + ctx.injectionPoint.getTargetInfo())); } else { // Note that at this point we can be sure that the required type is List<> - Type typeParam = injectionPoint.getType().asParameterizedType().arguments().get(0); + Type typeParam = ctx.injectionPoint.getType().asParameterizedType().arguments().get(0); if (typeParam.kind() == Type.Kind.WILDCARD_TYPE) { - if (injectionPoint.isSynthetic()) { - errors.accept( - new DefinitionException( - "Wildcard is not a legal type argument for a synthetic @All List injection point used in: " - + injectionTarget.toString())); + if (ctx.injectionPoint.isSynthetic()) { + ctx.errors.accept(new DefinitionException( + "Wildcard is not a legal type argument for a synthetic @All List injection point used in: " + + ctx.injectionTarget.toString())); return; } ClassInfo declaringClass; - if (injectionPoint.isField()) { - declaringClass = injectionPoint.getAnnotationTarget().asField().declaringClass(); + if (ctx.injectionPoint.isField()) { + declaringClass = ctx.injectionPoint.getAnnotationTarget().asField().declaringClass(); } else { - declaringClass = injectionPoint.getAnnotationTarget().asMethodParameter().method().declaringClass(); + declaringClass = ctx.injectionPoint.getAnnotationTarget().asMethodParameter().method().declaringClass(); } if (isKotlinClass(declaringClass)) { - errors.accept( - new DefinitionException( - "kotlin.collections.List cannot be used together with the @All qualifier, please use MutableList or java.util.List instead: " - + injectionPoint.getTargetInfo())); + ctx.errors.accept(new DefinitionException( + "kotlin.collections.List cannot be used together with the @All qualifier, please use MutableList or java.util.List instead: " + + ctx.injectionPoint.getTargetInfo())); } else { - errors.accept( - new DefinitionException( - "Wildcard is not a legal type argument for: " + injectionPoint.getTargetInfo())); + ctx.errors.accept(new DefinitionException( + "Wildcard is not a legal type argument for: " + ctx.injectionPoint.getTargetInfo())); } } else if (typeParam.kind() == Type.Kind.TYPE_VARIABLE) { - errors.accept(new DefinitionException( - "Type variable is not a legal type argument for: " + injectionPoint.getTargetInfo())); + ctx.errors.accept(new DefinitionException( + "Type variable is not a legal type argument for: " + ctx.injectionPoint.getTargetInfo())); } } } - private static void validateInjectionPoint(InjectionTargetInfo injectionTarget, InjectionPointInfo injectionPoint, - Consumer errors) { - if (injectionTarget.kind() != TargetKind.BEAN || !BuiltinScope.DEPENDENT.is(injectionTarget.asBean().getScope())) { - String msg = injectionPoint.getTargetInfo(); + private static void validateInjectionPoint(ValidatorContext ctx) { + if (ctx.injectionTarget.kind() != TargetKind.BEAN + || !BuiltinScope.DEPENDENT.is(ctx.injectionTarget.asBean().getScope())) { + String msg = ctx.injectionPoint.getTargetInfo(); if (msg.isBlank()) { - msg = injectionTarget.toString(); + msg = ctx.injectionTarget.toString(); } - errors.accept(new DefinitionException("Only @Dependent beans can access metadata about an injection point: " - + msg)); + ctx.errors.accept(new DefinitionException( + "Only @Dependent beans can access metadata about an injection point: " + msg)); } } - private static void validateBean(InjectionTargetInfo injectionTarget, InjectionPointInfo injectionPoint, - Consumer errors) { - if (injectionTarget.kind() != InjectionTargetInfo.TargetKind.BEAN) { - errors.accept(new DefinitionException("Only beans can access bean metadata")); + private static void validateBean(ValidatorContext ctx) { + if (ctx.injectionTarget.kind() != InjectionTargetInfo.TargetKind.BEAN) { + ctx.errors.accept(new DefinitionException("Only beans can access bean metadata")); } } - private static void validateInterceptedBean(InjectionTargetInfo injectionTarget, InjectionPointInfo injectionPoint, - Consumer errors) { - if (injectionTarget.kind() != InjectionTargetInfo.TargetKind.BEAN || !injectionTarget.asBean().isInterceptor()) { - errors.accept(new DefinitionException("Only interceptors can access intercepted bean metadata")); + private static void validateInterceptedBean(ValidatorContext ctx) { + if (ctx.injectionTarget.kind() != InjectionTargetInfo.TargetKind.BEAN + || !ctx.injectionTarget.asBean().isInterceptor()) { + ctx.errors.accept(new DefinitionException("Only interceptors can access intercepted bean metadata")); } } - private static void validateEventMetadata(InjectionTargetInfo injectionTarget, InjectionPointInfo injectionPoint, - Consumer errors) { - if (injectionTarget.kind() != TargetKind.OBSERVER) { - errors.accept(new DefinitionException("EventMetadata can be only injected into an observer method: " - + injectionPoint.getTargetInfo())); + private static void validateEventMetadata(ValidatorContext ctx) { + if (ctx.injectionTarget.kind() != TargetKind.OBSERVER) { + ctx.errors.accept(new DefinitionException( + "EventMetadata can be only injected into an observer method: " + ctx.injectionPoint.getTargetInfo())); } } - private static void validateInterceptionProxy(InjectionTargetInfo injectionTarget, - InjectionPointInfo injectionPoint, Consumer errors) { - if (injectionTarget.kind() != TargetKind.BEAN - || (!injectionTarget.asBean().isProducerMethod() && !injectionTarget.asBean().isSynthetic()) - || injectionTarget.asBean().getInterceptionProxy() == null) { - errors.accept(new DefinitionException( + private static void validateInterceptionProxy(ValidatorContext ctx) { + if (ctx.injectionTarget.kind() != TargetKind.BEAN + || (!ctx.injectionTarget.asBean().isProducerMethod() && !ctx.injectionTarget.asBean().isSynthetic()) + || ctx.injectionTarget.asBean().getInterceptionProxy() == null) { + ctx.errors.accept(new DefinitionException( "InterceptionProxy can only be injected into a producer method or a synthetic bean")); } - if (injectionPoint.getType().kind() != Kind.PARAMETERIZED_TYPE) { - errors.accept(new DefinitionException("InterceptionProxy must be a parameterized type")); + if (ctx.injectionPoint.getType().kind() != Kind.PARAMETERIZED_TYPE) { + ctx.errors.accept(new DefinitionException("InterceptionProxy must be a parameterized type")); } - Type interceptionProxyType = injectionPoint.getType().asParameterizedType().arguments().get(0); + Type interceptionProxyType = ctx.injectionPoint.getType().asParameterizedType().arguments().get(0); if (interceptionProxyType.kind() != Kind.CLASS && interceptionProxyType.kind() != Kind.PARAMETERIZED_TYPE) { - errors.accept(new DefinitionException( + ctx.errors.accept(new DefinitionException( "Type argument of InterceptionProxy may only be a class or parameterized type")); } - if (!injectionTarget.asBean().getProviderType().equals(interceptionProxyType)) { - String msg = injectionTarget.asBean().isProducerMethod() + if (!ctx.injectionTarget.asBean().getProviderType().equals(interceptionProxyType)) { + String msg = ctx.injectionTarget.asBean().isProducerMethod() ? "Type argument of InterceptionProxy must be equal to the return type of the producer method" : "Type argument of InterceptionProxy must be equal to the bean provider type"; - errors.accept(new DefinitionException(msg)); + ctx.errors.accept(new DefinitionException(msg)); + } + ClassInfo clazz = getClassByName(ctx.beanDeployment.getBeanArchiveIndex(), interceptionProxyType.name()); + if (clazz != null) { + if (clazz.isRecord()) { + ctx.errors.accept(new DefinitionException("Cannot build InterceptionProxy for a record")); + } + if (clazz.isSealed()) { + ctx.errors.accept(new DefinitionException("Cannot build InterceptionProxy for a sealed type")); + } } } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/records/InterceptedRecordProducerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/records/InterceptedRecordProducerTest.java new file mode 100644 index 0000000000000..6ef9283391cc3 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/records/InterceptedRecordProducerTest.java @@ -0,0 +1,75 @@ +package io.quarkus.arc.test.records; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.InterceptionProxy; +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptedRecordProducerTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Producer.class, MyInterceptorBinding.class, MyInterceptor.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Cannot build InterceptionProxy for a record")); + } + + @Dependent + static class Producer { + @Produces + @Dependent + DependentRecord produce(InterceptionProxy proxy) { + return proxy.create(new DependentRecord()); + } + } + + record DependentRecord() { + @MyInterceptorBinding + String hello() { + return "hello"; + } + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/DependentSealedTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/DependentSealedTest.java new file mode 100644 index 0000000000000..5031767caf3c2 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/DependentSealedTest.java @@ -0,0 +1,28 @@ +package io.quarkus.arc.test.sealed; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import jakarta.enterprise.context.Dependent; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class DependentSealedTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(MyDependent.class); + + @Test + public void test() { + assertNotNull(Arc.container().select(MyDependent.class).get()); + } + + @Dependent + static sealed class MyDependent permits MyDependentSubclass { + } + + static final class MyDependentSubclass extends MyDependent { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/InterceptedSealedProducerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/InterceptedSealedProducerTest.java new file mode 100644 index 0000000000000..84e392ba8c520 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/InterceptedSealedProducerTest.java @@ -0,0 +1,78 @@ +package io.quarkus.arc.test.sealed; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.InterceptionProxy; +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptedSealedProducerTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Producer.class, MyInterceptorBinding.class, MyInterceptor.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DefinitionException.class, error); + assertTrue(error.getMessage().contains("Cannot build InterceptionProxy for a sealed type")); + } + + @Dependent + static class Producer { + @Produces + @Dependent + DependentSealed produce(InterceptionProxy proxy) { + return proxy.create(new DependentSealed()); + } + } + + static sealed class DependentSealed permits DependentSealedSubclass { + @MyInterceptorBinding + String hello() { + return "hello"; + } + } + + static final class DependentSealedSubclass extends DependentSealed { + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/InterceptedSealedTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/InterceptedSealedTest.java new file mode 100644 index 0000000000000..d332b2cbbcbab --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/InterceptedSealedTest.java @@ -0,0 +1,68 @@ +package io.quarkus.arc.test.sealed; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.spi.DeploymentException; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InterceptorBinding; +import jakarta.interceptor.InvocationContext; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class InterceptedSealedTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(DependentSealed.class, MyInterceptorBinding.class, MyInterceptor.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DeploymentException.class, error); + assertTrue(error.getMessage().contains("must not be sealed")); + } + + @Dependent + static sealed class DependentSealed permits DependentSealedSubclass { + @MyInterceptorBinding + String hello() { + return "hello"; + } + } + + static final class DependentSealedSubclass extends DependentSealed { + } + + @Target({ ElementType.TYPE, ElementType.METHOD }) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @InterceptorBinding + @interface MyInterceptorBinding { + } + + @MyInterceptorBinding + @Interceptor + @Priority(1) + static class MyInterceptor { + @AroundInvoke + Object intercept(InvocationContext ctx) throws Exception { + return "intercepted: " + ctx.proceed(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/NormalScopedSealedProducerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/NormalScopedSealedProducerTest.java new file mode 100644 index 0000000000000..df5f240dea31e --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/NormalScopedSealedProducerTest.java @@ -0,0 +1,46 @@ +package io.quarkus.arc.test.sealed; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.DeploymentException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class NormalScopedSealedProducerTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(Producer.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DeploymentException.class, error); + assertTrue(error.getMessage().contains("must not have a return type that is sealed")); + } + + @Dependent + static class Producer { + @Produces + @ApplicationScoped + MySealed produce() { + return new MySealed(); + } + } + + static sealed class MySealed permits MySealedSubclass { + } + + static final class MySealedSubclass extends MySealed { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/NormalScopedSealedTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/NormalScopedSealedTest.java new file mode 100644 index 0000000000000..b9c78158ff323 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/NormalScopedSealedTest.java @@ -0,0 +1,36 @@ +package io.quarkus.arc.test.sealed; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.spi.DeploymentException; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.ArcTestContainer; + +public class NormalScopedSealedTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(NormalScopedSealed.class) + .shouldFail() + .build(); + + @Test + public void trigger() { + Throwable error = container.getFailure(); + assertNotNull(error); + assertInstanceOf(DeploymentException.class, error); + assertTrue(error.getMessage().contains("must not be sealed")); + } + + @ApplicationScoped + static sealed class NormalScopedSealed permits NormalScopedSealedSubclass { + } + + static final class NormalScopedSealedSubclass extends NormalScopedSealed { + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/SingletonSealedProducerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/SingletonSealedProducerTest.java new file mode 100644 index 0000000000000..8498dacb6b888 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/sealed/SingletonSealedProducerTest.java @@ -0,0 +1,38 @@ +package io.quarkus.arc.test.sealed; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; + +public class SingletonSealedProducerTest { + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Producer.class); + + @Test + public void test() { + assertNotNull(Arc.container().select(MySealed.class).get()); + } + + @Dependent + static class Producer { + @Produces + @Singleton + MySealed produce() { + return new MySealed(); + } + } + + static sealed class MySealed permits MySealedSubclass { + } + + static final class MySealedSubclass extends MySealed { + } +}