Skip to content

Commit

Permalink
Merge pull request #43825 from ballerina-platform/str-template-cost-expr
Browse files Browse the repository at this point in the history
Add support for string template expression as constant expression
  • Loading branch information
chiranSachintha authored Feb 19, 2025
2 parents 3c2a68f + 73868ec commit 2eee70a
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangRecordLiteral;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangSimpleVarRef;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangStringTemplateLiteral;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangUnaryExpr;
import org.wso2.ballerinalang.compiler.util.CompilerContext;
import org.wso2.ballerinalang.compiler.util.Names;
Expand Down Expand Up @@ -172,10 +173,16 @@ public void visit(BLangListConstructorExpr.BLangListConstructorSpreadOpExpr spre
analyzeExpr(spreadOpExpr.expr);
}

@Override
public void visit(BLangStringTemplateLiteral stringTemplateLiteral) {
stringTemplateLiteral.exprs.forEach(this::analyzeExpr);
}

void analyzeExpr(BLangExpression expr) {
switch (expr.getKind()) {
case LITERAL:
case NUMERIC_LITERAL:
case STRING_TEMPLATE_LITERAL:
case RECORD_LITERAL_EXPR:
case LIST_CONSTRUCTOR_EXPR:
case LIST_CONSTRUCTOR_SPREAD_OP:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangRecordLiteral;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangSimpleVarRef;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangStringTemplateLiteral;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangUnaryExpr;
import org.wso2.ballerinalang.compiler.util.BArrayState;
import org.wso2.ballerinalang.compiler.util.CompilerContext;
Expand Down Expand Up @@ -193,6 +194,7 @@ public BType checkConstExpr(BLangExpression expr, SymbolEnv env, BType expType,
switch (expr.getKind()) {
case LITERAL:
case NUMERIC_LITERAL:
case STRING_TEMPLATE_LITERAL:
case RECORD_LITERAL_EXPR:
case LIST_CONSTRUCTOR_EXPR:
case SIMPLE_VARIABLE_REF:
Expand Down Expand Up @@ -511,6 +513,34 @@ public void visit(BLangUnaryExpr unaryExpr, AnalyzerData data) {
data.resultType = finiteType;
}

@Override
public void visit(BLangStringTemplateLiteral stringTemplateLiteral, AnalyzerData data) {
StringBuilder resultString = new StringBuilder();
for (BLangExpression expr : stringTemplateLiteral.exprs) {
BType exprType = checkConstExpr(expr, data);
if (exprType == symTable.semanticError) {
data.resultType = symTable.semanticError;
return;
}
if (!types.isNonNilSimpleBasicTypeOrString(exprType)) {
dlog.error(expr.pos, DiagnosticErrorCode.INCOMPATIBLE_TYPES, symTable.interpolationAllowedType,
exprType);
data.resultType = symTable.semanticError;
return;
}
BLangLiteral exprLiteral = (BLangLiteral) ((BFiniteType) exprType).getValueSpace().iterator().next();
resultString.append(getValue(exprLiteral));
}

Location pos = stringTemplateLiteral.pos;
BType finiteType = getFiniteType(resultString.toString(), data.constantSymbol, pos, symTable.stringType);
if (data.compoundExprCount == 0 && types.typeIncompatible(pos, finiteType, data.expType)) {
data.resultType = symTable.semanticError;
return;
}
data.resultType = finiteType;
}

private BRecordType createNewRecordType(BRecordTypeSymbol symbol, LinkedHashMap<String, BField> inferredFields,
AnalyzerData data) {
BRecordType recordType = new BRecordType(symbol);
Expand Down Expand Up @@ -1577,7 +1607,8 @@ private Object calculateSubtract(Object lhs, Object rhs, BType type, AnalyzerDat
case TypeTags.DECIMAL:
BigDecimal lhsDecimal = new BigDecimal(String.valueOf(lhs), MathContext.DECIMAL128);
BigDecimal rhsDecimal = new BigDecimal(String.valueOf(rhs), MathContext.DECIMAL128);
BigDecimal resultDecimal = lhsDecimal.subtract(rhsDecimal, MathContext.DECIMAL128);
BigDecimal resultDecimal = lhsDecimal.compareTo(rhsDecimal) == 0 ? BigDecimal.ZERO :
lhsDecimal.subtract(rhsDecimal, MathContext.DECIMAL128);
resultDecimal = types.getValidDecimalNumber(data.pos, resultDecimal);
return resultDecimal != null ? resultDecimal.toPlainString() : null;
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangRecordLiteral;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangSimpleVarRef;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangStringTemplateLiteral;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangUnaryExpr;
import org.wso2.ballerinalang.compiler.tree.types.BLangRecordTypeNode;
import org.wso2.ballerinalang.compiler.util.CompilerContext;
Expand Down Expand Up @@ -298,6 +299,15 @@ public void visit(BLangBinaryExpr binaryExpr) {
this.result = calculateConstValue(lhs, rhs, binaryExpr.opKind);
}

public void visit(BLangStringTemplateLiteral stringTemplateLiteral) {
StringBuilder resultString = new StringBuilder();
stringTemplateLiteral.exprs.forEach(expr -> {
expr.accept(this);
resultString.append(this.result);
});
this.result = new BLangConstantValue(resultString.toString(), symTable.stringType);
}

@Override
public void visit(BLangGroupExpr groupExpr) {
this.result = constructBLangConstantValue(groupExpr.expression);
Expand Down Expand Up @@ -610,6 +620,7 @@ private BLangConstantValue constructBLangConstantValue(BLangExpression node) {
case BINARY_EXPR:
case GROUP_EXPR:
case UNARY_EXPR:
case STRING_TEMPLATE_LITERAL:
BLangConstantValue prevResult = this.result;
Location prevPos = this.currentPos;
this.currentPos = node.pos;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
import org.wso2.ballerinalang.compiler.tree.expressions.BLangRecordVarRef.BLangRecordVarRefKeyValue;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangRegExpTemplateLiteral;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangSimpleVarRef;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangStringTemplateLiteral;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangTupleVarRef;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangTypeConversionExpr;
import org.wso2.ballerinalang.compiler.tree.expressions.BLangTypeInit;
Expand Down Expand Up @@ -4451,6 +4452,9 @@ private void checkAnnotConstantExpression(BLangExpression expression) {
case LIST_CONSTRUCTOR_EXPR:
((BLangListConstructorExpr) expression).exprs.forEach(this::checkAnnotConstantExpression);
break;
case STRING_TEMPLATE_LITERAL:
((BLangStringTemplateLiteral) expression).exprs.forEach(this::checkAnnotConstantExpression);
break;
default:
dlog.error(expression.pos, DiagnosticErrorCode.EXPRESSION_IS_NOT_A_CONSTANT_EXPRESSION);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8229,10 +8229,7 @@ private void checkStringTemplateExprs(List<? extends BLangExpression> exprs, Ana
}

if (!types.isNonNilSimpleBasicTypeOrString(type)) {
dlog.error(expr.pos, DiagnosticErrorCode.INCOMPATIBLE_TYPES,
BUnionType.create(null, symTable.intType, symTable.floatType,
symTable.decimalType, symTable.stringType,
symTable.booleanType), type);
dlog.error(expr.pos, DiagnosticErrorCode.INCOMPATIBLE_TYPES, symTable.interpolationAllowedType, type);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ public class SymbolTable {
public final BType readonlyType = new BReadonlyType(TypeTags.READONLY, null);
public final BType pathParamAllowedType = BUnionType.create(null,
intType, stringType, floatType, booleanType, decimalType);
public final BType interpolationAllowedType = BUnionType.create(null, intType, floatType, decimalType,
stringType, booleanType);
public final BIntersectionType anyAndReadonly;
public BUnionType anyAndReadonlyOrError;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org).
*
* WSO2 LLC. licenses this file to you 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 org.ballerinalang.test.types.constant;

import org.ballerinalang.test.BAssertUtil;
import org.ballerinalang.test.BCompileUtil;
import org.ballerinalang.test.BRunUtil;
import org.ballerinalang.test.CompileResult;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

/**
* @since 2201.12.0
*
* This class contains a set of test cases to test the string template expression as a constant expression.
*/
public class StringTemplateConstantTest {

private CompileResult compileResult;

@BeforeClass
public void setup() {
compileResult = BCompileUtil.compile("test-src/types/constant/string_template_constant.bal");
}

@Test
public void testStringTemplateConstantExpr() {
BRunUtil.invoke(compileResult, "testStringTemplateConstantExpr");
}

@Test
public void testStringTemplateConstantExprNegative() {
CompileResult negativeResult = BCompileUtil.compile(
"test-src/types/constant/string_template_constant_negative.bal");
int index = 0;
BAssertUtil.validateError(negativeResult, index++, "incompatible types: expected '(int|float|" +
"decimal|string|boolean)', found '(record {| 4 a; |} & readonly)'", 17, 27);
BAssertUtil.validateError(negativeResult, index++, "expression is not a constant expression",
20, 30);
BAssertUtil.validateError(negativeResult, index++, "expression is not a constant expression",
21, 30);
BAssertUtil.validateError(negativeResult, index++, "expression is not a constant expression",
29, 21);

Assert.assertEquals(negativeResult.getErrorCount(), index);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
//
// WSO2 LLC. licenses this file to you 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.
const v1 = string `hello ${"world"}`;
const v2 = string `hello${" world"}`;

const boolean bool = true;
const v3 = string `This is ${bool}`;
const v4 = string `This is ${!bool}`;

const int intVal = 444;
string v5 = string `${intVal} is greater than ${100}.`;

const float floatVal = 5.0090;
string v6 = string `this is a float value ${floatVal}. ${v2}. Have a nice day.`;

const decimal b = 5.7888;
const decimal c = 5.7888;
const v7 = string `hello ${b + c}. This is ${b - c}.`;

public function testStringTemplateConstantExpr() {
assertEquality("hello world", v1);
assertEquality("hello world", v2);
assertEquality("This is true", v3);
assertEquality("This is false", v4);
assertEquality("444 is greater than 100.", v5);
assertEquality("this is a float value 5.009. hello world. Have a nice day.", v6);
assertEquality("hello 11.5776. This is 0.", v7);

// define a new non constant variables for the above v1 to v7 locally
string v11 = string `hello ${"world"}`;
string v12 = string `hello${" world"}`;
string v13 = string `This is ${bool}`;
string v14 = string `This is ${!bool}`;
string v15 = string `${intVal} is greater than ${100}.`;
string v16 = string `this is a float value ${floatVal}. ${v12}. Have a nice day.`;
string v17 = string `hello ${b + c}. This is ${b - c}.`;

// assert the equality of the above local variables with the constant variables
assertEquality(v1, v11);
assertEquality(v2, v12);
assertEquality(v3, v13);
assertEquality(v4, v14);
assertEquality(v5, v15);
assertEquality(v6, v16);
assertEquality(v7, v17); // Todo: This needs to be fixed
}

function assertEquality(anydata expected, anydata actual) {
if expected == actual {
return;
}

panic error(string `expected '${expected.toBalString()}', found '${actual.toBalString()}'`);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
//
// WSO2 LLC. licenses this file to you 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.
const v1 = {a:4};
const v2 = string `hello${v1}`;

boolean bool = true;
const v3 = string `This is ${bool}`;
const v4 = string `This is ${foo()}`;

function foo() returns boolean {
return false;
}

final string baz = "FOO";
string gim = "BAR";
const A = string `${baz}:${gim}`;

0 comments on commit 2eee70a

Please sign in to comment.