diff --git a/eclipse-sarl/plugins/io.sarl.lang/src/io/sarl/lang/jvmmodel/SARLJvmModelInferrer.java b/eclipse-sarl/plugins/io.sarl.lang/src/io/sarl/lang/jvmmodel/SARLJvmModelInferrer.java index ec30591252..10cd582e9e 100644 --- a/eclipse-sarl/plugins/io.sarl.lang/src/io/sarl/lang/jvmmodel/SARLJvmModelInferrer.java +++ b/eclipse-sarl/plugins/io.sarl.lang/src/io/sarl/lang/jvmmodel/SARLJvmModelInferrer.java @@ -97,8 +97,17 @@ import org.eclipse.xtext.serializer.sequencer.IContextFinder; import org.eclipse.xtext.xbase.XBlockExpression; import org.eclipse.xtext.xbase.XBooleanLiteral; +import org.eclipse.xtext.xbase.XCastedExpression; import org.eclipse.xtext.xbase.XExpression; +import org.eclipse.xtext.xbase.XInstanceOfExpression; +import org.eclipse.xtext.xbase.XNullLiteral; +import org.eclipse.xtext.xbase.XNumberLiteral; +import org.eclipse.xtext.xbase.XReturnExpression; +import org.eclipse.xtext.xbase.XStringLiteral; +import org.eclipse.xtext.xbase.XTypeLiteral; import org.eclipse.xtext.xbase.compiler.GeneratorConfig; +import org.eclipse.xtext.xbase.compiler.ImportManager; +import org.eclipse.xtext.xbase.compiler.output.FakeTreeAppendable; import org.eclipse.xtext.xbase.compiler.output.ITreeAppendable; import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor; import org.eclipse.xtext.xbase.jvmmodel.IJvmModelAssociator; @@ -1313,6 +1322,12 @@ protected void transform(final XtendFunction source, final JvmGenericType contai operation.getAnnotations().add(annotationClassRef(FiredEvent.class, firedEvents)); } + // Add @Inline annotation + if (this.expressionHelper.isInlinableOperation(operation, expression) + && this.annotationFinder.findAnnotation(operation, Inline.class) == null) { + appendInlineAnnotation(operation, expression); + } + // 1. Ensure that the Java annotations related to the default value are really present. // They may be not present if the generated action is a specific version of an inherited // action with default values for parameters. @@ -2373,6 +2388,90 @@ protected void appendInlineAnnotation(JvmOperation operation, String inlineExpre operation.getAnnotations().add(annotationReference); } + /** Append the inline annotation to the given operation. + * + * @param operation the operation to annotate. + * @param expression the expression of the operation. + * @see SARLExpressionHelper#isInlinableOperation(JvmOperation, XExpression) + */ + protected void appendInlineAnnotation(JvmOperation operation, XExpression expression) { + XExpression content = expression; + while (content instanceof XBlockExpression) { + final XBlockExpression blockExpr = (XBlockExpression) content; + if (blockExpr.getExpressions().size() == 1) { + content = blockExpr.getExpressions().get(0); + } else { + content = null; + } + } + final ImportManager imports = new ImportManager(); + final ITreeAppendable result = new FakeTreeAppendable(imports, "", " "); //$NON-NLS-1$//$NON-NLS-2$ + if (appendInlineAnnotation(operation, content, result)) { + final List importedTypes = imports.getImports(); + final JvmTypeReference[] importArray = new JvmTypeReference[importedTypes.size()]; + for (int i = 0; i < importArray.length; ++i) { + importArray[i] = this.typeReferences.getTypeForName(importedTypes.get(i), expression); + } + appendInlineAnnotation(operation, result.toString(), importArray); + } + } + + /** Append the inline annotation to the given operation. + * + * @param operation the operation to annotate. + * @param expression the expression of the operation. + * @param output the inline code. + */ + @SuppressWarnings("checkstyle:npathcomplexity") + private boolean appendInlineAnnotation(JvmOperation operation, XExpression expression, ITreeAppendable output) { + if (expression instanceof XBooleanLiteral) { + final XBooleanLiteral expr = (XBooleanLiteral) expression; + output.append(Boolean.toString(expr.isIsTrue())); + return true; + } + if (expression instanceof XNullLiteral) { + output.append("null"); //$NON-NLS-1$ + return true; + } + if (expression instanceof XNumberLiteral) { + final XNumberLiteral expr = (XNumberLiteral) expression; + output.append(expr.getValue()); + return true; + } + if (expression instanceof XStringLiteral) { + final XStringLiteral expr = (XStringLiteral) expression; + output.append("\"" //$NON-NLS-1$ + + org.eclipse.xtext.util.Strings.convertToJavaString(expr.getValue()) + + "\""); //$NON-NLS-1$ + return true; + } + if (expression instanceof XTypeLiteral) { + final XTypeLiteral expr = (XTypeLiteral) expression; + output.append(expr.getType()); + output.append(".class"); //$NON-NLS-1$ + return true; + } + if (expression instanceof XReturnExpression) { + return appendInlineAnnotation(operation, ((XReturnExpression) expression).getExpression(), output); + } + if (expression instanceof XCastedExpression) { + final XCastedExpression expr = (XCastedExpression) expression; + output.append("("); //$NON-NLS-1$ + output.append(expr.getType().getType()); + output.append(")"); //$NON-NLS-1$ + appendInlineAnnotation(operation, expr.getTarget(), output); + return true; + } + if (expression instanceof XInstanceOfExpression) { + final XInstanceOfExpression expr = (XInstanceOfExpression) expression; + appendInlineAnnotation(operation, expr.getExpression(), output); + output.append(" instanceof "); //$NON-NLS-1$ + output.append(expr.getType().getType()); + return true; + } + return false; + } + /** Create an annotation with classes as values. * * @param type - the type of the annotation. diff --git a/eclipse-sarl/plugins/io.sarl.lang/src/io/sarl/lang/typesystem/SARLExpressionHelper.java b/eclipse-sarl/plugins/io.sarl.lang/src/io/sarl/lang/typesystem/SARLExpressionHelper.java index b13c9c7dba..d710f7687c 100644 --- a/eclipse-sarl/plugins/io.sarl.lang/src/io/sarl/lang/typesystem/SARLExpressionHelper.java +++ b/eclipse-sarl/plugins/io.sarl.lang/src/io/sarl/lang/typesystem/SARLExpressionHelper.java @@ -34,10 +34,17 @@ import org.eclipse.xtext.common.types.JvmTypeReference; import org.eclipse.xtext.xbase.XAbstractFeatureCall; import org.eclipse.xtext.xbase.XBlockExpression; +import org.eclipse.xtext.xbase.XBooleanLiteral; import org.eclipse.xtext.xbase.XCastedExpression; import org.eclipse.xtext.xbase.XExpression; import org.eclipse.xtext.xbase.XInstanceOfExpression; +import org.eclipse.xtext.xbase.XNullLiteral; +import org.eclipse.xtext.xbase.XNumberLiteral; +import org.eclipse.xtext.xbase.XReturnExpression; +import org.eclipse.xtext.xbase.XStringLiteral; import org.eclipse.xtext.xbase.XSynchronizedExpression; +import org.eclipse.xtext.xbase.XTypeLiteral; +import org.eclipse.xtext.xbase.lib.Inline; import org.eclipse.xtext.xbase.lib.Pure; import org.eclipse.xtext.xbase.typesystem.util.CommonTypeComputationServices; @@ -157,4 +164,58 @@ public boolean isPurableOperation(JvmOperation operation, XExpression body) { return (name != null && this.pattern.matcher(name).find()) || !hasSideEffects(body); } + /** Replies if the given expression could be inline. + * + * @param expr - the body of the operation. + * @return true if one of the components of the given expression could be inline; + * otherwise false. + * @see Inline + */ + @SuppressWarnings("checkstyle:npathcomplexity") + public boolean isInlinableOperation(XExpression expr) { + if (expr != null) { + XExpression content = expr; + while (content instanceof XBlockExpression) { + final XBlockExpression blockExpr = (XBlockExpression) content; + if (blockExpr.getExpressions().size() == 1) { + content = blockExpr.getExpressions().get(0); + } else { + content = null; + } + } + if (content instanceof XBooleanLiteral + || content instanceof XNullLiteral + || content instanceof XNumberLiteral + || content instanceof XStringLiteral + || content instanceof XTypeLiteral) { + return true; + } + if (content instanceof XReturnExpression) { + return isInlinableOperation(((XReturnExpression) content).getExpression()); + } + if (content instanceof XCastedExpression) { + return isInlinableOperation(((XCastedExpression) content).getTarget()); + } + if (content instanceof XInstanceOfExpression) { + return isInlinableOperation(((XInstanceOfExpression) content).getExpression()); + } + } + return false; + } + + /** Replies if the given body could be inline. + * + * @param operation - the operation to test. + * @param body - the body of the operation. + * @return true if one of the components of the given expression could be inline; + * otherwise false. + * @see Inline + */ + public boolean isInlinableOperation(JvmOperation operation, XExpression body) { + if (operation != null && !operation.isAbstract()) { + return isInlinableOperation(body); + } + return false; + } + } diff --git a/tests/io.sarl.lang.tests/src/io/sarl/lang/tests/compilation/general/InlineFunctionTest.java b/tests/io.sarl.lang.tests/src/io/sarl/lang/tests/compilation/general/InlineFunctionTest.java new file mode 100644 index 0000000000..b6d5c77fca --- /dev/null +++ b/tests/io.sarl.lang.tests/src/io/sarl/lang/tests/compilation/general/InlineFunctionTest.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2014-2016 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.compilation.general; + +import static org.junit.Assert.assertEquals; + +import com.google.inject.Inject; +import org.eclipse.xtext.util.IAcceptor; +import org.eclipse.xtext.xbase.compiler.CompilationTestHelper; +import org.eclipse.xtext.xbase.compiler.CompilationTestHelper.Result; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +import io.sarl.lang.SARLVersion; +import io.sarl.tests.api.AbstractSarlTest; + + +/** + * @author $Author: sgalland$ + * @version $Name$ $Revision$ $Date$ + * @mavengroupid $GroupId$ + * @mavenartifactid $ArtifactId$ + */ +@SuppressWarnings("all") +public class InlineFunctionTest extends AbstractSarlTest { + + @Inject + private CompilationTestHelper compiler; + + @Test + public void booleanLiteral_true() throws Exception { + String source = multilineString( + "class C1 {", + " def fct { return true }", + "}", + ""); + final String expectedC1 = multilineString( + "import io.sarl.lang.annotation.SarlSpecification;", + "import org.eclipse.xtext.xbase.lib.Inline;", + "", + "@SarlSpecification(\"" + SARLVersion.SPECIFICATION_RELEASE_VERSION_STRING + "\")", + "@SuppressWarnings(\"all\")", + "public class C1 {", + " @Inline(value = \"true\")", + " public boolean fct() {", + " return true;", + " }", + "}", + "" + ); + this.compiler.compile(source, (r) -> { + assertEquals(expectedC1, r.getGeneratedCode("C1")); + }); + } + + @Test + public void booleanLiteral_false() throws Exception { + String source = multilineString( + "class C1 {", + " def fct { return false }", + "}", + ""); + final String expectedC1 = multilineString( + "import io.sarl.lang.annotation.SarlSpecification;", + "import org.eclipse.xtext.xbase.lib.Inline;", + "", + "@SarlSpecification(\"" + SARLVersion.SPECIFICATION_RELEASE_VERSION_STRING + "\")", + "@SuppressWarnings(\"all\")", + "public class C1 {", + " @Inline(value = \"false\")", + " public boolean fct() {", + " return false;", + " }", + "}", + "" + ); + this.compiler.compile(source, (r) -> { + assertEquals(expectedC1, r.getGeneratedCode("C1")); + }); + } + + @Test + public void nullLiteral() throws Exception { + String source = multilineString( + "class C1 {", + " def fct { return null }", + "}", + ""); + final String expectedC1 = multilineString( + "import io.sarl.lang.annotation.SarlSpecification;", + "import org.eclipse.xtext.xbase.lib.Inline;", + "", + "@SarlSpecification(\"" + SARLVersion.SPECIFICATION_RELEASE_VERSION_STRING + "\")", + "@SuppressWarnings(\"all\")", + "public class C1 {", + " @Inline(value = \"null\")", + " public Object fct() {", + " return null;", + " }", + "}", + "" + ); + this.compiler.compile(source, (r) -> { + assertEquals(expectedC1, r.getGeneratedCode("C1")); + }); + } + + @Test + public void numberLiteral() throws Exception { + String source = multilineString( + "class C1 {", + " def fct { return 123.456 }", + "}", + ""); + final String expectedC1 = multilineString( + "import io.sarl.lang.annotation.SarlSpecification;", + "import org.eclipse.xtext.xbase.lib.Inline;", + "", + "@SarlSpecification(\"" + SARLVersion.SPECIFICATION_RELEASE_VERSION_STRING + "\")", + "@SuppressWarnings(\"all\")", + "public class C1 {", + " @Inline(value = \"123.456\")", + " public double fct() {", + " return 123.456;", + " }", + "}", + "" + ); + this.compiler.compile(source, (r) -> { + assertEquals(expectedC1, r.getGeneratedCode("C1")); + }); + } + + @Test + public void stringLiteral() throws Exception { + String source = multilineString( + "class C1 {", + " def fct { return \"abc\" }", + "}", + ""); + final String expectedC1 = multilineString( + "import io.sarl.lang.annotation.SarlSpecification;", + "import org.eclipse.xtext.xbase.lib.Inline;", + "", + "@SarlSpecification(\"" + SARLVersion.SPECIFICATION_RELEASE_VERSION_STRING + "\")", + "@SuppressWarnings(\"all\")", + "public class C1 {", + " @Inline(value = \"\\\"abc\\\"\")", + " public String fct() {", + " return \"abc\";", + " }", + "}", + "" + ); + this.compiler.compile(source, (r) -> { + assertEquals(expectedC1, r.getGeneratedCode("C1")); + }); + } + + @Test + public void typeLiteral_typeof() throws Exception { + String source = multilineString( + "class C1 {", + " def fct { return typeof(Integer) }", + "}", + ""); + final String expectedC1 = multilineString( + "import io.sarl.lang.annotation.SarlSpecification;", + "import org.eclipse.xtext.xbase.lib.Inline;", + "", + "@SarlSpecification(\"" + SARLVersion.SPECIFICATION_RELEASE_VERSION_STRING + "\")", + "@SuppressWarnings(\"all\")", + "public class C1 {", + " @Inline(value = \"Integer.class\")", + " public Class fct() {", + " return Integer.class;", + " }", + "}", + "" + ); + this.compiler.compile(source, (r) -> { + assertEquals(expectedC1, r.getGeneratedCode("C1")); + }); + } + + @Test + public void castOperator() throws Exception { + String source = multilineString( + "class C1 {", + " def fct { return null as Integer }", + "}", + ""); + final String expectedC1 = multilineString( + "import io.sarl.lang.annotation.SarlSpecification;", + "import org.eclipse.xtext.xbase.lib.Inline;", + "", + "@SarlSpecification(\"" + SARLVersion.SPECIFICATION_RELEASE_VERSION_STRING + "\")", + "@SuppressWarnings(\"all\")", + "public class C1 {", + " @Inline(value = \"(Integer)null\")", + " public Integer fct() {", + " return ((Integer) null);", + " }", + "}", + "" + ); + this.compiler.compile(source, (r) -> { + assertEquals(expectedC1, r.getGeneratedCode("C1")); + }); + } + + @Test + public void instanceofOperator() throws Exception { + String source = multilineString( + "class C1 {", + " def fct { return null instanceof Integer }", + "}", + ""); + final String expectedC1 = multilineString( + "import io.sarl.lang.annotation.SarlSpecification;", + "import org.eclipse.xtext.xbase.lib.Inline;", + "", + "@SarlSpecification(\"" + SARLVersion.SPECIFICATION_RELEASE_VERSION_STRING + "\")", + "@SuppressWarnings(\"all\")", + "public class C1 {", + " @Inline(value = \"null instanceof Integer\")", + " public boolean fct() {", + " return (null instanceof Integer);", + " }", + "}", + "" + ); + this.compiler.compile(source, (r) -> { + assertEquals(expectedC1, r.getGeneratedCode("C1")); + }); + } + +} \ No newline at end of file