Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

review: Fix template parameter constraint #1535

Merged
merged 2 commits into from
Sep 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 23 additions & 21 deletions src/main/java/spoon/template/Substitution.java
Original file line number Diff line number Diff line change
Expand Up @@ -671,29 +671,31 @@ private static <T> void checkTemplateContracts(CtClass<T> c) {
Parameter templateParamAnnotation = f.getAnnotation(Parameter.class);
if (templateParamAnnotation != null && !templateParamAnnotation.value().equals("")) {
String proxyName = templateParamAnnotation.value();
// contract: if value, then the field type must be String
if (!f.getType().equals(c.getFactory().Type().STRING)) {
throw new TemplateException("proxy template parameter must be typed as String " + f.getType().getQualifiedName());
}

// contract: the name of the template parameter must correspond to the name of the field
// as found, by Pavel, this is not good contract because it prevents easy refactoring of templates
// we remove it but keep th commented code in case somebody would come up with this bad idae
// if (!f.getSimpleName().equals("_" + f.getAnnotation(Parameter.class).value())) {
// throw new TemplateException("the field name of a proxy template parameter must be called _" + f.getSimpleName());
// }

// contract: if a proxy parameter is declared and named "x" (@Parameter("x")), then a type member named "x" must exist.
boolean found = false;
for (CtTypeMember member: c.getTypeMembers()) {
if (member.getSimpleName().equals(proxyName)) {
found = true;
// contract: if value, then the field type must be String or CtTypeReference
String fieldTypeQName = f.getType().getQualifiedName();
if (fieldTypeQName.equals(String.class.getName())) {
// contract: the name of the template parameter must correspond to the name of the field
// as found, by Pavel, this is not good contract because it prevents easy refactoring of templates
// we remove it but keep th commented code in case somebody would come up with this bad idae
// if (!f.getSimpleName().equals("_" + f.getAnnotation(Parameter.class).value())) {
// throw new TemplateException("the field name of a proxy template parameter must be called _" + f.getSimpleName());
// }

// contract: if a proxy parameter is declared and named "x" (@Parameter("x")), then a type member named "x" must exist.
boolean found = false;
for (CtTypeMember member: c.getTypeMembers()) {
if (member.getSimpleName().equals(proxyName)) {
found = true;
}
}
if (!found) {
throw new TemplateException("if a proxy parameter is declared and named \"" + proxyName + "\", then a type member named \"\" + proxyName + \"\" must exist.");
}
} else if (fieldTypeQName.equals(CtTypeReference.class.getName())) {
//OK it is CtTypeReference
} else {
throw new TemplateException("proxy template parameter must be typed as String or CtTypeReference, but it is " + fieldTypeQName);
}
if (!found) {
throw new TemplateException("if a proxy parameter is declared and named \"" + proxyName + "\", then a type member named \"\" + proxyName + \"\" must exist.");
}

}
}
}
Expand Down
28 changes: 28 additions & 0 deletions src/test/java/spoon/test/template/TemplateTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import spoon.reflect.declaration.CtTypeMember;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtFieldReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.ModelConsistencyChecker;
import spoon.reflect.visitor.filter.NamedElementFilter;
import spoon.support.compiler.FileSystemFile;
Expand All @@ -44,6 +45,7 @@
import spoon.test.template.testclasses.SubStringTemplate;
import spoon.test.template.testclasses.SubstituteLiteralTemplate;
import spoon.test.template.testclasses.SubstituteRootTemplate;
import spoon.test.template.testclasses.TypeReferenceClassAccessTemplate;
import spoon.test.template.testclasses.bounds.CheckBound;
import spoon.test.template.testclasses.bounds.CheckBoundMatcher;
import spoon.test.template.testclasses.bounds.CheckBoundTemplate;
Expand All @@ -62,6 +64,7 @@
import spoon.test.template.testclasses.types.AClassModel;
import spoon.test.template.testclasses.types.AnEnumModel;
import spoon.test.template.testclasses.types.AnIfaceModel;
import spoon.testing.utils.ModelUtils;

import java.io.File;
import java.io.Serializable;
Expand Down Expand Up @@ -1014,4 +1017,29 @@ public void testAnotherFieldAccessNameSubstitution() throws Exception {
assertEquals("java.lang.System.out.println(((x) + (m_x)))", result.getAnonymousExecutables().get(0).getBody().getStatement(0).toString());
}
}

@Test
public void substituteTypeAccessReference() throws Exception {
//contract: the substitution of CtTypeAccess expression ignores actual type arguments if it have to
Launcher spoon = new Launcher();
spoon.addTemplateResource(new FileSystemFile("./src/test/java/spoon/test/template/testclasses/TypeReferenceClassAccessTemplate.java"));
String outputDir = "./target/spooned/test/template/testclasses";
spoon.setSourceOutputDirectory(outputDir);

spoon.buildModel();
Factory factory = spoon.getFactory();

//contract: String value is substituted in substring of literal, named element and reference
CtTypeReference<?> typeRef = factory.Type().createReference(TypeReferenceClassAccessTemplate.Example.class);
typeRef.addActualTypeArgument(factory.Type().DATE);

final CtClass<?> result = (CtClass<?>) new TypeReferenceClassAccessTemplate(typeRef).apply(factory.Class().create("spoon.test.template.TypeReferenceClassAccess"));
spoon.prettyprint();
ModelUtils.canBeBuilt(outputDir, 8);
CtMethod<?> method = result.getMethodsByName("someMethod").get(0);
assertEquals("spoon.test.template.TypeReferenceClassAccess.Example<java.util.Date>", method.getType().toString());
assertEquals("spoon.test.template.TypeReferenceClassAccess.Example<java.util.Date>", method.getParameters().get(0).getType().toString());
assertEquals("o = spoon.test.template.TypeReferenceClassAccess.Example.out", method.getBody().getStatement(0).toString());
assertEquals("spoon.test.template.TypeReferenceClassAccess.Example<java.util.Date> ret = new spoon.test.template.TypeReferenceClassAccess.Example<java.util.Date>()", method.getBody().getStatement(1).toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package spoon.test.template.testclasses;

import spoon.reflect.reference.CtTypeReference;
import spoon.template.ExtensionTemplate;
import spoon.template.Local;
import spoon.template.Parameter;

public class TypeReferenceClassAccessTemplate extends ExtensionTemplate {
Object o;

$Type$ someMethod($Type$ param) {
o = $Type$.out;
$Type$ ret = new $Type$();
return ret;
}

@Local
public TypeReferenceClassAccessTemplate(CtTypeReference<?> typeRef) {
this.typeRef = typeRef;
}

@Parameter("$Type$")
CtTypeReference<?> typeRef;

@Local
static class $Type$ {
static final String out = "";
static long currentTimeMillis(){
return 0;
}
}

public static class Example<T> {
static final String out = "";
static long currentTimeMillis(){
return 0;
}
}
}