Skip to content

Commit

Permalink
[lang] Add detection of @DaTa for type immutability.
Browse files Browse the repository at this point in the history
see #1013

Signed-off-by: Stéphane Galland <galland@arakhne.org>
  • Loading branch information
gallandarakhneorg committed Aug 18, 2020
1 parent 87e699a commit c950094
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 5 deletions.
Expand Up @@ -45,7 +45,13 @@
import java.util.Locale;
import java.util.UUID;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.xtend.lib.annotations.Data;
import org.eclipse.xtext.common.types.JvmAnnotationTarget;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.util.AnnotationLookup;
import org.eclipse.xtext.util.Pair;
import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReference;

/**
Expand All @@ -62,7 +68,12 @@
@Singleton
public class DefaultImmutableTypeValidator implements IImmutableTypeValidator {

private static final Class<?>[] IMMUTABLE_TYPES = {
/** List of the well-known immutable types from Java, that are also considered as
* immutable in SARL.
*
* @since 0.12
*/
public static final Class<?>[] IMMUTABLE_TYPES = {
String.class,
UUID.class,
URL.class,
Expand Down Expand Up @@ -99,18 +110,102 @@ public class DefaultImmutableTypeValidator implements IImmutableTypeValidator {
ZoneOffset.class,
};

/** List of the annotations that are known to mark the types as immutable.
*
* @since 0.12
*/
@SuppressWarnings("deprecation")
public static final Class<?>[] IMMUTABLE_TYPE_ANNOTATIONS = {
Data.class,
org.eclipse.xtend.lib.Data.class,
};

/** Finder of annotations.
*/
private AnnotationLookup annotationFinder;

/** Change the JVM annotation finder that is used by this immutable type validator.
*
* @param finder the new finder, must not be {@code null}.
*/
@Inject
public void setAnnotationLookup(AnnotationLookup finder) {
assert finder != null;
this.annotationFinder = finder;
}

@Override
public boolean isImmutable(LightweightTypeReference type) {
assert type != null;
final LightweightTypeReference ref = type.getPrimitiveIfWrapperType();
if (ref.isArray()) {
// Several special types are assumed to be always mutable
if (isAlwaysMutable(type)) {
return false;
}
if (ref.isPrimitive() || ref.isPrimitiveVoid()) {
// Even if the primitive wrappers are in the IMMUTABLE_TYPES list, this
// test enables to exit early from the function in case the type is a
// primitive type or one of the associated wrapper types
if (isAlwaysImmutable(type)) {
return true;
}
// Test if the type is annotated with one of the known annotations
if (hasImmutableAnnotation(type)) {
return true;
}
// Test the type itself
return isRegisteredImmutableType(type);
}

/** Replies if the given type is always assumed to be mutable.
*
* @param ref the type to test.
* @return {@code true} if the given type is always mutable; or {@code false} if
* it is not known yet that the type is mutable or not.
*/
protected boolean isAlwaysMutable(LightweightTypeReference ref) {
return ref.isArray() || ref.isAnonymous() || ref.isAny() || ref.isUnknown() || ref.isFunctionType();
}

/** Replies if the given type is always assumed to be immutable.
*
* @param ref the type to test.
* @return {@code true} if the given type is always immutable; or {@code false} if
* it is not known yet that the type is immutable or not.
*/
protected boolean isAlwaysImmutable(LightweightTypeReference ref) {
final LightweightTypeReference pref = ref.getPrimitiveIfWrapperType();
return pref.isPrimitive() || pref.isPrimitiveVoid();
}

/** Replies if the given type is marked with an annotation that makes it immutable.
*
* @param ref the type to test.
* @return {@code true} if the given type is annotated with an immutable type annotation; or {@code false} if
* it is not known yet that the type is immutable or not.
* @see #IMMUTABLE_TYPE_ANNOTATIONS
*/
protected boolean hasImmutableAnnotation(LightweightTypeReference ref) {
final JvmType backType = ref.getType();
if (backType instanceof JvmAnnotationTarget) {
final JvmAnnotationTarget target = (JvmAnnotationTarget) backType;
for (final Class<?> jvmType : IMMUTABLE_TYPE_ANNOTATIONS) {
if (this.annotationFinder.findAnnotation(target, jvmType.getCanonicalName()) != null) {
return true;
}
}
}
return false;
}

/** Replies if the given type is known as an immutable type.
*
* @param ref the type to test.
* @return {@code true} if the given type is an immutable type; or {@code false} if
* it is not known yet that the type is immutable or not.
* @see #IMMUTABLE_TYPES
*/
protected boolean isRegisteredImmutableType(LightweightTypeReference ref) {
for (final Class<?> jvmType : IMMUTABLE_TYPES) {
if (type.isSubtypeOf(jvmType)) {
if (ref.isSubtypeOf(jvmType)) {
return true;
}
}
Expand Down
@@ -0,0 +1,178 @@
/*
* $Id$
*
* SARL is an general-purpose agent programming language.
* More details on http://www.sarl.io
*
* Copyright (C) 2014-2020 the original authors or 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
*
* http://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.sarl.lang.tests.modules.typesystem;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import javax.inject.Inject;

import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.xtend.lib.annotations.Data;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.util.AnnotationLookup;
import org.eclipse.xtext.common.types.util.TypeReferences;
import org.eclipse.xtext.xbase.lib.Functions;
import org.eclipse.xtext.xbase.lib.Procedures;
import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReference;
import org.eclipse.xtext.xbase.typesystem.util.CommonTypeComputationServices;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

import io.sarl.lang.sarl.SarlScript;
import io.sarl.lang.typesystem.DefaultImmutableTypeValidator;
import io.sarl.lang.util.Utils;
import io.sarl.tests.api.AbstractSarlTest;
import io.sarl.tests.api.Nullable;

/** This class tests the {@link DefaultImmutableTypeValidator} for SARL.
*
* @author $Author: sgalland$
* @version $Name$ $Revision$ $Date$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
* @since 0.12
*/
@SuppressWarnings("all")
@DisplayName("DefaultImmutableTypeValidator")
@Tag("core")
@Tag("unit")
public class DefaultImmutableTypeValidatorTest extends AbstractSarlTest {

@Nullable
private DefaultImmutableTypeValidator validator;

@Nullable
private Notifier context;

@Inject
private AnnotationLookup annotationFinder;

@Inject
private CommonTypeComputationServices services;

@Inject
private TypeReferences typeReferences;

private boolean isImmutable(Class<?> type) throws Exception {
final JvmType jvmtype = this.typeReferences.findDeclaredType(type, this.context);
assertNotNull(jvmtype);
final LightweightTypeReference ref = Utils.toLightweightTypeReference(jvmtype, this.services);
assertNotNull(ref);
return this.validator.isImmutable(ref);
}

@BeforeEach
public void setUp() throws Exception {
final SarlScript script = getParseHelper().parse("agent XXX { }");
assertNotNull(script);
this.context = script;
this.validator = new DefaultImmutableTypeValidator();
this.validator.setAnnotationLookup(this.annotationFinder);
}

@ParameterizedTest
@MethodSource
@DisplayName("Declared immutable types")
public void isImmutable_declaredImmutableTypes(Class<?> type) throws Exception {
assertTrue(isImmutable(type));
}

private static List<Class<?>> isImmutable_declaredImmutableTypes() {
return Arrays.asList(DefaultImmutableTypeValidator.IMMUTABLE_TYPES);
}

@ParameterizedTest
@ValueSource(classes = {Object.class, BigInteger.class, AtomicInteger.class})
@DisplayName("Mutable types")
public void isImmutable_Object(Class<?> type) throws Exception {
assertFalse(isImmutable(type));
}

@ParameterizedTest
@ValueSource(classes = {byte.class, short.class, int.class, long.class, float.class, double.class, boolean.class, char.class})
@DisplayName("Primitive types")
public void isImmutable_primitive(Class<?> type) throws Exception {
assertTrue(isImmutable(type));
}

@ParameterizedTest
@ValueSource(classes = {void.class, Void.class})
@DisplayName("Void types")
public void isImmutable_void(Class<?> type) throws Exception {
assertTrue(isImmutable(type));
}

@ParameterizedTest
@MethodSource
@DisplayName("Procedures")
public void isImmutable_procedure(Class<?> type) throws Exception {
assertFalse(isImmutable(type));
}

private static List<Class<?>> isImmutable_procedure() {
return Arrays.asList(Procedures.class.getDeclaredClasses());
}

@ParameterizedTest
@MethodSource
@DisplayName("Functions")
public void isImmutable_function(Class<?> type) throws Exception {
assertFalse(isImmutable(type));
}

private static List<Class<?>> isImmutable_function() {
return Arrays.asList(Functions.class.getDeclaredClasses());
}

@Test
@DisplayName("Mutable user type")
public void isImmutable_MutableUserType() throws Exception {
assertFalse(isImmutable(MutableUserType.class));
}

@Test
@DisplayName("Immutable user type")
public void isImmutable_ImmutableUserType() throws Exception {
assertTrue(isImmutable(ImmutableUserType.class));
}

private class MutableUserType {
//
}

@Data
private class ImmutableUserType {
//
}

}

0 comments on commit c950094

Please sign in to comment.