8359335: Template-Framework Library: Primitive Types subtyping

Reviewed-by: chagedorn, epeter
This commit is contained in:
Manuel Hässig 2026-03-18 12:33:41 +00:00
parent e99ed13691
commit 262b31be3d
6 changed files with 116 additions and 18 deletions

View File

@ -52,6 +52,7 @@ import static compiler.lib.template_framework.Template.let;
import static compiler.lib.template_framework.Template.$;
import compiler.lib.template_framework.library.CodeGenerationDataNameType;
import compiler.lib.template_framework.library.Expression;
import compiler.lib.template_framework.library.Expression.Nesting;
import compiler.lib.template_framework.library.Operations;
import compiler.lib.template_framework.library.PrimitiveType;
import compiler.lib.template_framework.library.TestFrameworkClass;
@ -335,7 +336,7 @@ public class ExpressionFuzzer {
for (int i = 0; i < 10; i++) {
// The depth determines roughly how many operations are going to be used in the expression.
int depth = RANDOM.nextInt(1, 20);
Expression expression = Expression.nestRandomly(type, Operations.PRIMITIVE_OPERATIONS, depth);
Expression expression = Expression.nestRandomly(type, Operations.PRIMITIVE_OPERATIONS, depth, Nesting.EXACT);
tests.add(testTemplate.asToken(expression));
}
}
@ -350,7 +351,7 @@ public class ExpressionFuzzer {
for (int i = 0; i < 2; i++) {
// The depth determines roughly how many operations are going to be used in the expression.
int depth = RANDOM.nextInt(1, 20);
Expression expression = Expression.nestRandomly(type, Operations.SCALAR_NUMERIC_OPERATIONS, depth);
Expression expression = Expression.nestRandomly(type, Operations.SCALAR_NUMERIC_OPERATIONS, depth, Nesting.EXACT);
tests.add(testTemplate.asToken(expression));
}
}

View File

@ -382,6 +382,23 @@ public class Expression {
return sb.toString();
}
/**
* {@link Nesting} defines the different ways of selecting {@link Expression}s
* to nest based on their types.
*/
public enum Nesting {
/**
* Only nest {@Expression}s where the argument and return types match exactly
* based on the implementation of {@link CodeGenerateionDataNameType#isSubtypeOf}.
*/
EXACT,
/**
* Only nest {@Expression}s where the return type is a subtype of the argument
* type based on the implemetation of {@link CodeGenerateionDataNameType#isSubtypeOf}.
*/
SUBTYPE
}
/**
* Create a nested {@link Expression} with a specified {@code returnType} from a
* set of {@code expressions}.
@ -391,11 +408,13 @@ public class Expression {
* the nested {@link Expression}.
* @param maxNumberOfUsedExpressions the maximal number of {@link Expression}s from the
* {@code expressions} are nested.
* @param nesting control the {@link Nesting} of the sampled {@link Expression}s.
* @return a new randomly nested {@link Expression}.
*/
public static Expression nestRandomly(CodeGenerationDataNameType returnType,
List<Expression> expressions,
int maxNumberOfUsedExpressions) {
int maxNumberOfUsedExpressions,
Nesting nesting) {
List<Expression> filtered = expressions.stream().filter(e -> e.returnType.isSubtypeOf(returnType)).toList();
if (filtered.isEmpty()) {
@ -406,7 +425,7 @@ public class Expression {
Expression expression = filtered.get(r);
for (int i = 1; i < maxNumberOfUsedExpressions; i++) {
expression = expression.nestRandomly(expressions);
expression = expression.nestRandomly(expressions, nesting);
}
return expression;
}
@ -416,12 +435,16 @@ public class Expression {
* {@code this} {@link Expression}, ensuring compatibility of argument and return type.
*
* @param nestingExpressions list of expressions we sample from for the inner {@link Expression}.
* @param nesting control the {@link Nesting} of the sampled {@link Expression}s.
* @return a new nested {@link Expression}.
*/
public Expression nestRandomly(List<Expression> nestingExpressions) {
public Expression nestRandomly(List<Expression> nestingExpressions, Nesting nesting) {
int argumentIndex = RANDOM.nextInt(this.argumentTypes.size());
CodeGenerationDataNameType argumentType = this.argumentTypes.get(argumentIndex);
List<Expression> filtered = nestingExpressions.stream().filter(e -> e.returnType.isSubtypeOf(argumentType)).toList();
List<Expression> filtered = nestingExpressions.stream()
.filter(e -> e.returnType.isSubtypeOf(argumentType) &&
(nesting == Nesting.EXACT ? argumentType.isSubtypeOf(e.returnType) : true))
.toList();
if (filtered.isEmpty()) {
// Found no expression that has a matching returnType.

View File

@ -129,8 +129,17 @@ public final class Operations {
ops.add(Expression.make(type, "(", type, " + ", type, ")"));
ops.add(Expression.make(type, "(", type, " - ", type, ")"));
ops.add(Expression.make(type, "(", type, " * ", type, ")"));
ops.add(Expression.make(type, "(", type, " / ", type, ")"));
ops.add(Expression.make(type, "(", type, " % ", type, ")"));
// Because of subtyping, we can sample an expression like `(float)((int)(3) / (int)(0))`. Floating point
// division and modulo do not throw an ArithmeticException on division by zero, integer division and modulo
// do. In the expression above, the division has an integer on both sides, so it is executed as an integer
// division and throws an ArithmeticException even though we would expect the float division not to do so.
// To prevent this issue, we provide two versions of floating point division operations: one that casts
// its operands and one that expects that an ArithmeticException might be thrown when we get unlucky when
// sampling subtypes.
ops.add(Expression.make(type, "((" + type.name() + ")(", type, ") / (" + type.name() +")(", type, "))"));
ops.add(Expression.make(type, "((" + type.name() + ")(", type, ") % (" + type.name() +")(", type, "))"));
ops.add(Expression.make(type, "(", type, " / ", type, ")", WITH_ARITHMETIC_EXCEPTION));
ops.add(Expression.make(type, "(", type, " % ", type, ")", WITH_ARITHMETIC_EXCEPTION));
// Relational / Comparison Operators
ops.add(Expression.make(BOOLEANS, "(", type, " == ", type, ")"));

View File

@ -71,7 +71,25 @@ public final class PrimitiveType implements CodeGenerationDataNameType {
@Override
public boolean isSubtypeOf(DataName.Type other) {
return (other instanceof PrimitiveType pt) && pt.kind == kind;
// Implement other >: this according to JLS §4.10.1.
if (other instanceof PrimitiveType superType) {
if (superType.kind == Kind.BOOLEAN || kind == Kind.BOOLEAN) {
// Boolean does not have a supertype and only itself as a subtype.
return superType.kind == this.kind;
}
if (superType.kind == Kind.CHAR || kind == Kind.CHAR) {
// Char does not have a subtype, but it is itself a subtype of any primitive type with
// a larger byte size. The following is correct for the subtype relation to floats,
// since chars are 16 bits wide and floats 32 bits or more.
return superType.kind == this.kind || (superType.byteSize() > this.byteSize() && this.kind != Kind.BYTE);
}
// Due to float >: long, all integers are subtypes of floating point types.
return (superType.isFloating() && !this.isFloating()) ||
// Generally, narrower types are subtypes of wider types.
(superType.isFloating() == this.isFloating() && superType.byteSize() >= this.byteSize());
}
return false;
}
@Override

View File

@ -48,6 +48,7 @@ import static compiler.lib.template_framework.Template.let;
import static compiler.lib.template_framework.Template.$;
import static compiler.lib.template_framework.Template.addDataName;
import static compiler.lib.template_framework.DataName.Mutability.MUTABLE;
import static compiler.lib.template_framework.DataName.Mutability.MUTABLE_OR_IMMUTABLE;
import compiler.lib.template_framework.library.Hooks;
import compiler.lib.template_framework.library.CodeGenerationDataNameType;
@ -129,7 +130,8 @@ public class TestPrimitiveTypes {
}
// Finally, test the type by creating some DataNames (variables), and sampling
// from them. There should be no cross-over between the types.
// from them. Sampling exactly should not lead to any conversion and sampling
// subtypes should only lead to widening conversions.
// IMPORTANT: since we are adding the DataName via an inserted Template, we
// must chose a "transparentScope", so that the DataName escapes. If we
// instead chose "scope", the test would fail, because it later
@ -150,6 +152,14 @@ public class TestPrimitiveTypes {
"""
));
var assignmentTemplate = Template.make("lhsType", (PrimitiveType lhsType) -> scope(
dataNames(MUTABLE).exactOf(lhsType).sampleAndLetAs("lhs"),
dataNames(MUTABLE_OR_IMMUTABLE).subtypeOf(lhsType).sampleAndLetAs("rhs"),
"""
#lhs = #rhs;
"""
));
var namesTemplate = Template.make(() -> scope(
"""
public static void test_names() {
@ -161,10 +171,16 @@ public class TestPrimitiveTypes {
).toList()
),
"""
// Now sample:
// Sample exactly:
""",
Collections.nCopies(10,
CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(sampleTemplate::asToken).toList()
),
"""
// Sample subtypes:
""",
Collections.nCopies(10,
CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(assignmentTemplate::asToken).toList()
)
)),
"""

View File

@ -41,6 +41,7 @@ import compiler.lib.template_framework.TemplateToken;
import static compiler.lib.template_framework.Template.scope;
import compiler.lib.template_framework.library.CodeGenerationDataNameType;
import compiler.lib.template_framework.library.Expression;
import compiler.lib.template_framework.library.Expression.Nesting;
/**
* This tests the use of the {@link Expression} from the template library. This is
@ -174,44 +175,74 @@ public class TestExpression {
Expression e4 = Expression.make(myTypeA1, "<", myTypeA, ">");
Expression e5 = Expression.make(myTypeA, "[", myTypeB, "]");
Expression e1e2 = e1.nestRandomly(List.of(e2));
Expression e1ex = e1.nestRandomly(List.of(e3, e2, e3));
Expression e1e4 = e1.nestRandomly(List.of(e3, e4, e3));
Expression e1ey = e1.nestRandomly(List.of(e3, e3));
Expression e1e2 = e1.nestRandomly(List.of(e2), Nesting.SUBTYPE);
Expression e1e2Exact = e1.nestRandomly(List.of(e2), Nesting.EXACT);
Expression e1ex = e1.nestRandomly(List.of(e3, e2, e3), Nesting.SUBTYPE);
Expression e1exExact = e1.nestRandomly(List.of(e3, e2, e3), Nesting.EXACT);
Expression e1e4 = e1.nestRandomly(List.of(e3, e4, e3), Nesting.SUBTYPE);
Expression e1e4Exact = e1.nestRandomly(List.of(e3, e4, e3), Nesting.EXACT);
Expression e1ey = e1.nestRandomly(List.of(e3, e3), Nesting.SUBTYPE);
Expression e1eyExact = e1.nestRandomly(List.of(e3, e3), Nesting.EXACT);
// 5-deep nesting of e1
Expression deep1 = Expression.nestRandomly(myTypeA, List.of(e1, e3), 5);
Expression deep1 = Expression.nestRandomly(myTypeA, List.of(e1, e3), 5, Nesting.SUBTYPE);
Expression deep1Exact = Expression.nestRandomly(myTypeA, List.of(e1, e3), 5, Nesting.EXACT);
// Alternating pattern
Expression deep2 = Expression.nestRandomly(myTypeA, List.of(e5, e3), 5);
Expression deep2 = Expression.nestRandomly(myTypeA, List.of(e5, e3), 5, Nesting.SUBTYPE);
Expression deep2Exact = Expression.nestRandomly(myTypeA, List.of(e5, e3), 5, Nesting.SUBTYPE);
var template = Template.make(() -> scope(
"xx", e1e2.toString(), "yy\n",
"xx", e1e2Exact.toString(), "yy\n",
"xx", e1ex.toString(), "yy\n",
"xx", e1exExact.toString(), "yy\n",
"xx", e1e4.toString(), "yy\n",
"xx", e1e4Exact.toString(), "yy\n",
"xx", e1ey.toString(), "yy\n",
"xx", e1eyExact.toString(), "yy\n",
"xx", deep1.toString(), "yy\n",
"xx", deep1Exact.toString(), "yy\n",
"xx", deep2.toString(), "yy\n",
"xx", deep2Exact.toString(), "yy\n",
"xx", e1e2.asToken(List.of("a")), "yy\n",
"xx", e1e2Exact.asToken(List.of("a")), "yy\n",
"xx", e1ex.asToken(List.of("a")), "yy\n",
"xx", e1exExact.asToken(List.of("a")), "yy\n",
"xx", e1e4.asToken(List.of("a")), "yy\n",
"xx", e1e4Exact.asToken(List.of("a")), "yy\n",
"xx", e1ey.asToken(List.of("a")), "yy\n",
"xx", e1eyExact.asToken(List.of("a")), "yy\n",
"xx", deep1.asToken(List.of("a")), "yy\n",
"xx", deep2.asToken(List.of("a")), "yy\n"
"xx", deep1Exact.asToken(List.of("a")), "yy\n",
"xx", deep2.asToken(List.of("a")), "yy\n",
"xx", deep2Exact.asToken(List.of("a")), "yy\n"
));
String expected =
"""
xxExpression["[(", MyTypeA, ")]"]yy
xxExpression["[(", MyTypeA, ")]"]yy
xxExpression["[(", MyTypeA, ")]"]yy
xxExpression["[(", MyTypeA, ")]"]yy
xxExpression["[<", MyTypeA, ">]"]yy
xxExpression["[", MyTypeA, "]"]yy
xxExpression["[", MyTypeA, "]"]yy
xxExpression["[", MyTypeA, "]"]yy
xxExpression["[[[[[", MyTypeA, "]]]]]"]yy
xxExpression["[[[[[", MyTypeA, "]]]]]"]yy
xxExpression["[{[{[", MyTypeB, "]}]}]"]yy
xxExpression["[{[{[", MyTypeB, "]}]}]"]yy
xx[(a)]yy
xx[(a)]yy
xx[(a)]yy
xx[(a)]yy
xx[<a>]yy
xx[a]yy
xx[a]yy
xx[a]yy
xx[[[[[a]]]]]yy
xx[[[[[a]]]]]yy
xx[{[{[a]}]}]yy
xx[{[{[a]}]}]yy
""";
String code = template.render();