diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/BaseVisitor.java b/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/BaseVisitor.java index 0c6a541c9c0c..0b8a4d98ef44 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/BaseVisitor.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/BaseVisitor.java @@ -102,6 +102,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMarkdownReturnParameterDocumentation; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; @@ -1111,4 +1112,8 @@ public void visit(BLangGroupingKey groupingKeyClause) { @Override public void visit(BLangReCapturingGroups reCapturingGroups) { } + + @Override + public void visit(BLangNaturalExpression naturalExpression) { + } } diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/NodeFinder.java b/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/NodeFinder.java index e9d5e5346fe3..aeebba7d9017 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/NodeFinder.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/NodeFinder.java @@ -101,6 +101,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryExpr; @@ -1578,6 +1579,13 @@ public void visit(BLangCollectContextInvocation invocation) { lookupNode(invocation.invocation); } + @Override + public void visit(BLangNaturalExpression naturalExpression) { + lookupNodes(naturalExpression.arguments); + lookupNodes(naturalExpression.strings); + lookupNodes(naturalExpression.insertions); + } + private boolean setEnclosingNode(BLangNode node, Location pos) { if (PositionUtil.isRangeWithinNode(this.range, pos) && (this.enclosingNode == null diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/ReferenceFinder.java b/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/ReferenceFinder.java index c1d1b313f4d5..e5d090be8dd6 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/ReferenceFinder.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/ReferenceFinder.java @@ -109,6 +109,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryExpr; @@ -1480,6 +1481,13 @@ public void visit(BLangReFlagExpression reFlagExpression) { find(reFlagExpression.colon); } + @Override + public void visit(BLangNaturalExpression naturalExpression) { + find(naturalExpression.arguments); + find(naturalExpression.insertions); + find(naturalExpression.strings); + } + // Private methods private void findRefsInResourceAccessPathSegments(BLangInvocation.BLangResourceAccessInvocation invocation) { diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/SymbolFinder.java b/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/SymbolFinder.java index 948f17dc73d5..9754f245f8b9 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/SymbolFinder.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/compiler/api/impl/SymbolFinder.java @@ -118,6 +118,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryExpr; @@ -1795,6 +1796,13 @@ public void visit(BLangReFlagExpression reFlagExpression) { lookupNode(reFlagExpression.colon); } + @Override + public void visit(BLangNaturalExpression naturalExpression) { + lookupNodes(naturalExpression.arguments); + lookupNodes(naturalExpression.strings); + lookupNodes(naturalExpression.insertions); + } + private void lookupResourceAccessPathSegments(BLangInvocation.BLangResourceAccessInvocation resourceInvocation) { if (resourceInvocation.targetResourceFunc == null) { // Return if target-function is not set. diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/DocumentContext.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/DocumentContext.java index c219679275d7..8cd0098cd138 100644 --- a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/DocumentContext.java +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/DocumentContext.java @@ -23,6 +23,7 @@ import io.ballerina.compiler.syntax.tree.SeparatedNodeList; import io.ballerina.compiler.syntax.tree.SyntaxTree; import io.ballerina.projects.environment.ModuleLoadRequest; +import io.ballerina.projects.internal.NaturalProgrammingImportAnalyzer; import io.ballerina.projects.internal.TransactionImportValidator; import io.ballerina.tools.diagnostics.Diagnostic; import io.ballerina.tools.text.TextDocument; @@ -139,19 +140,45 @@ private Set getModuleLoadRequests(ModuleDescriptor currentMod moduleLoadRequestSet.add(getModuleLoadRequest(importDcl, scope)); } + addTransactionModuleImportIfRequired(currentModuleDesc, scope, modulePartNode, moduleLoadRequestSet); + addNaturalProgrammingModuleImportIfRequired(currentModuleDesc, scope, modulePartNode, moduleLoadRequestSet); + return moduleLoadRequestSet; + } + + private static void addTransactionModuleImportIfRequired(ModuleDescriptor currentModuleDesc, + PackageDependencyScope scope, + ModulePartNode modulePartNode, + Set moduleLoadRequestSet) { // TODO This is a temporary solution for SLP6 release // TODO Traverse the syntax tree to see whether to import the ballerinai/transaction package or not TransactionImportValidator trxImportValidator = new TransactionImportValidator(); + if (!trxImportValidator.shouldImportTransactionPackage(modulePartNode)) { + return; + } + addModuleLoadRequest(currentModuleDesc, scope, moduleLoadRequestSet, Names.BALLERINA_INTERNAL_ORG.value, + Names.TRANSACTION.value, DependencyResolutionType.PLATFORM_PROVIDED); + } - if (trxImportValidator.shouldImportTransactionPackage(modulePartNode) && - !currentModuleDesc.name().toString().equals(Names.TRANSACTION.value)) { - String moduleName = Names.TRANSACTION.value; - ModuleLoadRequest ballerinaiLoadReq = new ModuleLoadRequest( - PackageOrg.from(Names.BALLERINA_INTERNAL_ORG.value), - moduleName, scope, DependencyResolutionType.PLATFORM_PROVIDED); - moduleLoadRequestSet.add(ballerinaiLoadReq); + private static void addNaturalProgrammingModuleImportIfRequired(ModuleDescriptor currentModuleDesc, + PackageDependencyScope scope, + ModulePartNode modulePartNode, + Set moduleLoadRequestSet) { + NaturalProgrammingImportAnalyzer naturalProgrammingImportAnalyzer = new NaturalProgrammingImportAnalyzer(); + if (!naturalProgrammingImportAnalyzer.shouldImportNaturalProgrammingModule(modulePartNode)) { + return; + } + addModuleLoadRequest(currentModuleDesc, scope, moduleLoadRequestSet, Names.BALLERINA_ORG.value, + Names.NATURAL_PROGRAMMING.value, DependencyResolutionType.PLATFORM_PROVIDED); + } + + private static void addModuleLoadRequest(ModuleDescriptor currentModuleDesc, PackageDependencyScope scope, + Set moduleLoadRequestSet, String orgName, + String moduleName, DependencyResolutionType dependencyResolutionType) { + if (!currentModuleDesc.name().toString().equals(moduleName)) { + ModuleLoadRequest moduleLoadRequest = new ModuleLoadRequest( + PackageOrg.from(orgName), moduleName, scope, dependencyResolutionType); + moduleLoadRequestSet.add(moduleLoadRequest); } - return moduleLoadRequestSet; } private ModuleLoadRequest getModuleLoadRequest(ImportDeclarationNode importDcl, PackageDependencyScope scope) { diff --git a/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/NaturalProgrammingImportAnalyzer.java b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/NaturalProgrammingImportAnalyzer.java new file mode 100644 index 000000000000..7587be62992e --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/io/ballerina/projects/internal/NaturalProgrammingImportAnalyzer.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 Inc. 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 io.ballerina.projects.internal; + +import io.ballerina.compiler.syntax.tree.ExternalFunctionBodyNode; +import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.ModulePartNode; +import io.ballerina.compiler.syntax.tree.NaturalExpressionNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeVisitor; +import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; + +/** + * Check and add an import for the natural programming module if there is a natural expression. + * + * @since 2201.13.0 + */ +public class NaturalProgrammingImportAnalyzer extends NodeVisitor { + + private boolean shouldImportNaturalProgrammingModule; + private static final String CODE_ANNOTATION = "code"; + + public boolean shouldImportNaturalProgrammingModule(ModulePartNode modulePartNode) { + modulePartNode.accept(this); + return this.shouldImportNaturalProgrammingModule; + } + + @Override + public void visit(NaturalExpressionNode naturalExpressionNode) { + this.shouldImportNaturalProgrammingModule = true; + } + + @Override + public void visit(FunctionDefinitionNode functionDefinitionNode) { + if (this.shouldImportNaturalProgrammingModule) { + return; + } + + if (isExternalFunctionWithCodeAnnotation(functionDefinitionNode)) { + this.shouldImportNaturalProgrammingModule = true; + return; + } + + super.visit(functionDefinitionNode); + } + + private boolean isExternalFunctionWithCodeAnnotation(FunctionDefinitionNode functionDefinitionNode) { + if (!(functionDefinitionNode.functionBody() instanceof ExternalFunctionBodyNode externalFunctionBodyNode)) { + return false; + } + + return externalFunctionBodyNode.annotations().stream() + .anyMatch(annotation -> + annotation.annotReference() instanceof SimpleNameReferenceNode annotReference && + CODE_ANNOTATION.equals(annotReference.name().text())); + } + + @Override + protected void visitSyntaxNode(Node node) { + if (this.shouldImportNaturalProgrammingModule) { + return; + } + super.visitSyntaxNode(node); + } +} diff --git a/compiler/ballerina-lang/src/main/java/org/ballerinalang/model/TreeBuilder.java b/compiler/ballerina-lang/src/main/java/org/ballerinalang/model/TreeBuilder.java index 891927afecda..d96fd2256ec6 100644 --- a/compiler/ballerina-lang/src/main/java/org/ballerinalang/model/TreeBuilder.java +++ b/compiler/ballerina-lang/src/main/java/org/ballerinalang/model/TreeBuilder.java @@ -95,6 +95,7 @@ import org.ballerinalang.model.tree.expressions.MarkdownDocumentationReturnParameterAttributeNode; import org.ballerinalang.model.tree.expressions.MarkdownDocumentationTextAttributeNode; import org.ballerinalang.model.tree.expressions.NamedArgNode; +import org.ballerinalang.model.tree.expressions.NaturalExpressionNode; import org.ballerinalang.model.tree.expressions.QueryExpressionNode; import org.ballerinalang.model.tree.expressions.RawTemplateLiteralNode; import org.ballerinalang.model.tree.expressions.ReAssertionNode; @@ -275,6 +276,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; @@ -1191,4 +1193,8 @@ public static InferredTypedescDefaultNode createInferTypedescExpressionNode() { public static BLangInvocation.BLangResourceAccessInvocation createResourceAccessInvocation() { return new BLangInvocation.BLangResourceAccessInvocation(); } + + public static NaturalExpressionNode createNaturalExpressionNode() { + return new BLangNaturalExpression(); + } } diff --git a/compiler/ballerina-lang/src/main/java/org/ballerinalang/model/tree/NodeKind.java b/compiler/ballerina-lang/src/main/java/org/ballerinalang/model/tree/NodeKind.java index 1b6c3289d9fa..5ecf4dca4981 100644 --- a/compiler/ballerina-lang/src/main/java/org/ballerinalang/model/tree/NodeKind.java +++ b/compiler/ballerina-lang/src/main/java/org/ballerinalang/model/tree/NodeKind.java @@ -151,6 +151,7 @@ public enum NodeKind { REG_EXP_CAPTURING_GROUP, REG_EXP_FLAG_EXPR, REG_EXP_FLAGS_ON_OFF, + NATURAL_EXPR, /* Statements */ ABORT, diff --git a/compiler/ballerina-lang/src/main/java/org/ballerinalang/model/tree/expressions/NaturalExpressionNode.java b/compiler/ballerina-lang/src/main/java/org/ballerinalang/model/tree/expressions/NaturalExpressionNode.java new file mode 100644 index 000000000000..2db082bd64b3 --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/org/ballerinalang/model/tree/expressions/NaturalExpressionNode.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 Inc. 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.model.tree.expressions; + +import java.util.List; + +/** + * Represents a natural expression node. + * + * @since 2201.13.0 + */ +public interface NaturalExpressionNode extends ExpressionNode { + + List getInsertions(); + + void addInsertion(ExpressionNode expression); + + List getStrings(); + + void addString(ExpressionNode stringLiteral); +} diff --git a/compiler/ballerina-lang/src/main/java/org/ballerinalang/util/diagnostic/DiagnosticErrorCode.java b/compiler/ballerina-lang/src/main/java/org/ballerinalang/util/diagnostic/DiagnosticErrorCode.java index 6f1037b9bb94..3e7a63892676 100644 --- a/compiler/ballerina-lang/src/main/java/org/ballerinalang/util/diagnostic/DiagnosticErrorCode.java +++ b/compiler/ballerina-lang/src/main/java/org/ballerinalang/util/diagnostic/DiagnosticErrorCode.java @@ -817,7 +817,17 @@ public enum DiagnosticErrorCode implements DiagnosticCode { EXPRESSION_OF_FUTURE_TYPE_EXPECTED("BCE4057", "future.expression.expected"), INSTANTIATION_ERROR("BCE4058", "instantiation.error"), INVALID_BINDING_PATTERN_IN_ON_FAIL("BCE4059", "invalid.binding.pattern.in.on.fail"), - INVALID_USAGE_OF_CHECK_IN_PARAMETER_DEFAULT("BCE4060", "invalid.usage.of.check.in.parameter.default") + INVALID_USAGE_OF_CHECK_IN_PARAMETER_DEFAULT("BCE4060", "invalid.usage.of.check.in.parameter.default"), + + EXPECTED_TYPE_FOR_NATURAL_EXPR_MUST_CONTAIN_ERROR("BCE4070", "expected.type.for.natural.expr.must.contain.error"), + EXPECTED_TYPE_FOR_NATURAL_EXPR_MUST_CONTAIN_A_UNION_OF_NON_ERROR_AND_ERROR( + "BCE4071", "expected.type.for.natural.expr.must.contain.a.union.of.non.error.and.error"), + EXPECTED_TYPE_FOR_NATURAL_EXPR_MUST_BE_A_SUBTYPE_OF_ANYDATA_OR_ERROR( + "BCE4072", "expected.type.for.natural.expr.must.be.a.subtype.of.anydata.or.error"), + CONST_NATURAL_EXPR_CAN_HAVE_ONLY_CONST_EXPR_INSERTION( + "BCE4073", "const.natural.expr.can.have.only.const.expr.insertion"), + EXPECTED_TYPE_FOR_CONST_NATURAL_EXPR_MUST_BE_A_SUBTYPE_OF_ANYDATA( + "BCE4074", "expected.type.for.const.natural.expr.must.be.a.subtype.of.anydata") ; private final String diagnosticId; diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/ClosureGenerator.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/ClosureGenerator.java index 465dc91d503d..0e0227a649e7 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/ClosureGenerator.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/ClosureGenerator.java @@ -111,6 +111,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMarkdownReturnParameterDocumentation; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; @@ -1792,6 +1793,14 @@ public void visit(BLangMarkdownDocumentation bLangMarkdownDocumentation) { /* Ignore */ } + @Override + public void visit(BLangNaturalExpression naturalExpression) { + rewriteExprs(naturalExpression.arguments); + rewriteExprs(naturalExpression.strings); + rewriteExprs(naturalExpression.insertions); + result = naturalExpression; + } + // Rewrite methods @SuppressWarnings("unchecked") diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/ConstantPropagation.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/ConstantPropagation.java index 1cb329d9089e..8ab7f81ac3a0 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/ConstantPropagation.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/ConstantPropagation.java @@ -89,6 +89,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; @@ -1274,6 +1275,14 @@ public void visit(BLangReFlagExpression reFlagExpression) { result = reFlagExpression; } + @Override + public void visit(BLangNaturalExpression naturalExpression) { + rewrite(naturalExpression.arguments); + rewrite(naturalExpression.strings); + rewrite(naturalExpression.insertions); + result = naturalExpression; + } + @SuppressWarnings("unchecked") E rewrite(E node) { if (node == null) { diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/QueryDesugar.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/QueryDesugar.java index d52d0000dd98..abb5fa48d8c5 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/QueryDesugar.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/desugar/QueryDesugar.java @@ -114,6 +114,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryExpr; @@ -2801,6 +2802,14 @@ public void visit(BLangRegExpTemplateLiteral regExpTemplateLiteral) { result = regExpTemplateLiteral; } + @Override + public void visit(BLangNaturalExpression naturalExpression) { + rewrite(naturalExpression.arguments); + rewrite(naturalExpression.strings); + rewrite(naturalExpression.insertions); + result = naturalExpression; + } + private void acceptNode(BLangNode node) { if (node == null) { return; diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/parser/BLangNodeBuilder.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/parser/BLangNodeBuilder.java index 83d503eb31ab..56bc60ebbaba 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/parser/BLangNodeBuilder.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/parser/BLangNodeBuilder.java @@ -131,6 +131,7 @@ import io.ballerina.compiler.syntax.tree.NamedArgumentNode; import io.ballerina.compiler.syntax.tree.NamedWorkerDeclarationNode; import io.ballerina.compiler.syntax.tree.NamedWorkerDeclarator; +import io.ballerina.compiler.syntax.tree.NaturalExpressionNode; import io.ballerina.compiler.syntax.tree.NewExpressionNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeFactory; @@ -379,6 +380,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; @@ -2414,7 +2416,7 @@ public BLangTypeConversionExpr transform(TypeCastExpressionNode typeCastExpressi public BLangNode transform(Token token) { SyntaxKind kind = token.kind(); return switch (kind) { - case XML_TEXT_CONTENT, TEMPLATE_STRING, CLOSE_BRACE_TOKEN -> createSimpleLiteral(token); + case XML_TEXT_CONTENT, TEMPLATE_STRING, CLOSE_BRACE_TOKEN, PROMPT_CONTENT -> createSimpleLiteral(token); default -> { if (isTokenInRegExp(kind)) { yield createSimpleLiteral(token); @@ -3623,6 +3625,16 @@ public BLangNode transform(XMLTextNode xmlTextNode) { return createExpression(xmlTextNode.content()); } + @Override + public BLangNode transform(NaturalExpressionNode naturalExpressionNode) { + BLangNaturalExpression naturalExpr = (BLangNaturalExpression) TreeBuilder.createNaturalExpressionNode(); + naturalExpr.pos = getPosition(naturalExpressionNode); + naturalExpr.constExpr = naturalExpressionNode.constKeyword().isPresent(); + naturalExpr.arguments = getNaturalExpressionArguments(naturalExpressionNode); + populateTemplateContent(naturalExpressionNode.prompt(), naturalExpr.strings, naturalExpr.insertions); + return naturalExpr; + } + private BLangNode createXMLEmptyLiteral(Node expressionNode) { BLangXMLTextLiteral xmlTextLiteral = (BLangXMLTextLiteral) TreeBuilder.createXMLTextLiteralNode(); xmlTextLiteral.pos = getPosition(expressionNode); @@ -5523,36 +5535,39 @@ private BLangNode createStringTemplateLiteral(NodeList memberNodes, Locati private BLangRawTemplateLiteral createRawTemplateLiteral(NodeList members, Location location) { BLangRawTemplateLiteral literal = (BLangRawTemplateLiteral) TreeBuilder.createRawTemplateLiteralNode(); literal.pos = location; + populateTemplateContent(members, literal.strings, literal.insertions); + return literal; + } + private void populateTemplateContent(NodeList members, List strings, + List insertions) { boolean prevNodeWasInterpolation = false; Node firstMember = members.isEmpty() ? null : members.get(0); // will be empty for empty raw template if (firstMember != null && firstMember.kind() == SyntaxKind.INTERPOLATION) { - literal.strings.add(createStringLiteral("", getPosition(firstMember))); + strings.add(createStringLiteral("", getPosition(firstMember))); } for (Node member : members) { if (member.kind() == SyntaxKind.INTERPOLATION) { - literal.insertions.add((BLangExpression) member.apply(this)); + insertions.add((BLangExpression) member.apply(this)); if (prevNodeWasInterpolation) { - literal.strings.add(createStringLiteral("", getPosition(member))); + strings.add(createStringLiteral("", getPosition(member))); } prevNodeWasInterpolation = true; } else { - literal.strings.add((BLangLiteral) member.apply(this)); + strings.add((BLangLiteral) member.apply(this)); prevNodeWasInterpolation = false; } } if (prevNodeWasInterpolation) { - literal.strings.add(createStringLiteral("", getPosition(members.get(members.size() - 1)))); + strings.add(createStringLiteral("", getPosition(members.get(members.size() - 1)))); } - - return literal; } - + private BLangNode createRegExpTemplateLiteral(TemplateExpressionNode expressionNode) { BLangRegExpTemplateLiteral regExpTemplateLiteral = (BLangRegExpTemplateLiteral) TreeBuilder.createRegExpTemplateLiteralNode(); @@ -7075,4 +7090,13 @@ private List createBLangXMLStepExtends(NodeList nodes, } return extensions; } + + private List getNaturalExpressionArguments(NaturalExpressionNode naturalExpressionNode) { + Optional argsList = naturalExpressionNode.parenthesizedArgList(); + return argsList.map( + parenthesizedArgList -> parenthesizedArgList.arguments() + .stream() + .map(this::createExpression).toList()). + orElseGet(() -> new ArrayList<>(0)); + } } diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/parser/NodeCloner.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/parser/NodeCloner.java index cccd4bdf22b0..7fac62838b40 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/parser/NodeCloner.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/parser/NodeCloner.java @@ -120,6 +120,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; @@ -2536,4 +2537,14 @@ public void visit(BLangReFlagExpression source) { clone.flagsOnOff = clone(source.flagsOnOff); clone.colon = clone(source.colon); } + + @Override + public void visit(BLangNaturalExpression source) { + BLangNaturalExpression clone = new BLangNaturalExpression(); + source.cloneRef = clone; + clone.arguments = cloneList(source.arguments); + clone.strings = cloneList(source.strings); + clone.insertions = cloneList(source.insertions); + clone.constExpr = source.constExpr; + } } diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/CodeAnalyzer.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/CodeAnalyzer.java index 9c5229a36704..d5708745273c 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/CodeAnalyzer.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/CodeAnalyzer.java @@ -147,6 +147,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; @@ -3558,6 +3559,13 @@ public void visit(BLangInferredTypedescDefaultNode inferTypedescExpr, AnalyzerDa /* Ignore */ } + @Override + public void visit(BLangNaturalExpression naturalExpression, AnalyzerData data) { + analyzeExprs(naturalExpression.arguments, data); + analyzeExprs(naturalExpression.strings, data); + analyzeExprs(naturalExpression.insertions, data); + } + // private methods private void analyzeExpr(E node, AnalyzerData data) { diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/DataflowAnalyzer.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/DataflowAnalyzer.java index 663d32078caf..b68c34b3fc5a 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/DataflowAnalyzer.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/DataflowAnalyzer.java @@ -128,6 +128,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryExpr; @@ -2496,6 +2497,21 @@ public void visit(BLangErrorVariableDef bLangErrorVariableDef) { analyzeNode(bLangErrorVariableDef.errorVariable, env); } + @Override + public void visit(BLangNaturalExpression naturalExpression) { + for (BLangExpression argumentExpr : naturalExpression.arguments) { + analyzeNode(argumentExpr, env); + } + + for (BLangLiteral string : naturalExpression.strings) { + analyzeNode(string, env); + } + + for (BLangExpression expr : naturalExpression.insertions) { + analyzeNode(expr, env); + } + } + private void addUninitializedVar(BLangVariable variable) { if (!this.uninitializedVars.containsKey(variable.symbol)) { this.uninitializedVars.put(variable.symbol, InitStatus.UN_INIT); diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/IsolationAnalyzer.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/IsolationAnalyzer.java index 182ec315066f..a41235781223 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/IsolationAnalyzer.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/IsolationAnalyzer.java @@ -158,6 +158,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; @@ -2090,6 +2091,13 @@ public void visit(BLangRegExpTemplateLiteral regExpTemplateLiteral) { interpolationsList.forEach(interpolation -> analyzeNode(interpolation, env)); } + @Override + public void visit(BLangNaturalExpression naturalExpression) { + for (BLangExpression insertion : naturalExpression.insertions) { + analyzeNode(insertion, env); + } + } + private void analyzeInvocation(BLangInvocation invocationExpr) { List requiredArgs = invocationExpr.requiredArgs; List restArgs = invocationExpr.restArgs; diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeChecker.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeChecker.java index 042a9370d57a..0df8ffde2334 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeChecker.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/semantics/analyzer/TypeChecker.java @@ -143,6 +143,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; @@ -6643,6 +6644,45 @@ public void visit(BLangAnnotAccessExpr annotAccessExpr, AnalyzerData data) { data.resultType = this.types.checkType(annotAccessExpr, actualType, data.expType); } + @Override + public void visit(BLangNaturalExpression naturalExpression, AnalyzerData data) { + BType type = data.expType; + SemType expTypeSemType = type.semType(); + boolean constNaturalExpr = naturalExpression.constExpr; + + if (!constNaturalExpr && !types.isSubtype(symTable.errorType, expTypeSemType)) { + dlog.error(naturalExpression.pos, DiagnosticErrorCode.EXPECTED_TYPE_FOR_NATURAL_EXPR_MUST_CONTAIN_ERROR); + type = symTable.semanticError; + } else if (types.isSubtype(expTypeSemType, symTable.errorType.semType())) { + dlog.error(naturalExpression.pos, constNaturalExpr ? + DiagnosticErrorCode.EXPECTED_TYPE_FOR_CONST_NATURAL_EXPR_MUST_BE_A_SUBTYPE_OF_ANYDATA : + DiagnosticErrorCode.EXPECTED_TYPE_FOR_NATURAL_EXPR_MUST_CONTAIN_A_UNION_OF_NON_ERROR_AND_ERROR); + type = symTable.semanticError; + } + + SemType errorLiftedType = types.getErrorLiftType(expTypeSemType); + if (constNaturalExpr) { + if (!types.isSubtype(errorLiftedType, symTable.anydataType.semType())) { + dlog.error(naturalExpression.pos, + DiagnosticErrorCode.EXPECTED_TYPE_FOR_CONST_NATURAL_EXPR_MUST_BE_A_SUBTYPE_OF_ANYDATA); + type = symTable.semanticError; + } + } else if (!types.isSubtype(errorLiftedType, symTable.pureType.semType())) { + dlog.error(naturalExpression.pos, + DiagnosticErrorCode.EXPECTED_TYPE_FOR_NATURAL_EXPR_MUST_BE_A_SUBTYPE_OF_ANYDATA_OR_ERROR); + type = symTable.semanticError; + } + checkNaturalExprArguments(naturalExpression, data); + checkNaturalExprInsertions(naturalExpression, data); + data.resultType = type; + } + + private void checkNaturalExprArguments(BLangNaturalExpression naturalExpression, AnalyzerData data) { + for (BLangExpression expr : naturalExpression.arguments) { + checkExpr(expr, symTable.anyType, data); + } + } + // Private methods private boolean isValidVariableReference(BLangExpression varRef) { @@ -9934,6 +9974,16 @@ public void restoreGlobalState(GlobalStateSnapshot globalStateSnapshot) { this.dlog.setErrorCount(globalStateSnapshot.errorCount); } + private void checkNaturalExprInsertions(BLangNaturalExpression naturalExpression, AnalyzerData data) { + boolean constNaturalExpr = naturalExpression.constExpr; + for (BLangExpression expr : naturalExpression.insertions) { + checkExpr(expr, symTable.anydataType, data); + if (constNaturalExpr && !isConstExpression(expr)) { + dlog.error(expr.pos, DiagnosticErrorCode.CONST_NATURAL_EXPR_CAN_HAVE_ONLY_CONST_EXPR_INSERTION); + } + } + } + /** * @since 2.0.0 */ diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/BLangNodeAnalyzer.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/BLangNodeAnalyzer.java index 682cffeb66fa..5e6d0f6ad943 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/BLangNodeAnalyzer.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/BLangNodeAnalyzer.java @@ -80,6 +80,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryExpr; @@ -683,4 +684,6 @@ public abstract class BLangNodeAnalyzer { public abstract void visit(BLangXMLMethodCallStepExtend node, T props); public abstract void visit(BLangExtendedXMLNavigationAccess node, T props); + + public abstract void visit(BLangNaturalExpression node, T props); } diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/BLangNodeTransformer.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/BLangNodeTransformer.java index 77f47f2cfb38..6666a6946dcd 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/BLangNodeTransformer.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/BLangNodeTransformer.java @@ -79,6 +79,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryExpr; @@ -1144,4 +1145,8 @@ public R transform(BLangXMLMethodCallStepExtend node, T data) { public R transform(BLangExtendedXMLNavigationAccess node, T data) { return transformNode(node, data); } + + public R transform(BLangNaturalExpression node, T data) { + return transformNode(node, data); + } } diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/BLangNodeVisitor.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/BLangNodeVisitor.java index eb3681de235b..eff557ca008e 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/BLangNodeVisitor.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/BLangNodeVisitor.java @@ -95,6 +95,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNumericLiteral; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; @@ -1165,4 +1166,8 @@ public void visit(BLangXMLMethodCallStepExtend xmlMethodCallStepExtend) { public void visit(BLangExtendedXMLNavigationAccess extendedXMLNavigationAccess) { throw new AssertionError(); } + + public void visit(BLangNaturalExpression naturalExpression) { + throw new AssertionError(); + } } diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/SimpleBLangNodeAnalyzer.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/SimpleBLangNodeAnalyzer.java index 47d0eb8e62b4..4856a38cdd56 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/SimpleBLangNodeAnalyzer.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/SimpleBLangNodeAnalyzer.java @@ -83,6 +83,7 @@ import org.wso2.ballerinalang.compiler.tree.expressions.BLangMatchGuard; import org.wso2.ballerinalang.compiler.tree.expressions.BLangMultipleWorkerReceive; import org.wso2.ballerinalang.compiler.tree.expressions.BLangNamedArgsExpression; +import org.wso2.ballerinalang.compiler.tree.expressions.BLangNaturalExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangObjectConstructorExpression; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryAction; import org.wso2.ballerinalang.compiler.tree.expressions.BLangQueryExpr; @@ -1825,6 +1826,14 @@ public void visit(BLangValueType node, T data) { analyzeNode(node, data); } + @Override + public void visit(BLangNaturalExpression node, T data) { + analyzeNode(node, data); + visitNode(node.arguments, data); + visitNode(node.strings, data); + visitNode(node.insertions, data); + } + // Private methods private void visitBLangVariableNode(BLangVariable node, T data) { diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/expressions/BLangNaturalExpression.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/expressions/BLangNaturalExpression.java new file mode 100644 index 000000000000..0e867a91b995 --- /dev/null +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/tree/expressions/BLangNaturalExpression.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 Inc. 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.wso2.ballerinalang.compiler.tree.expressions; + +import org.ballerinalang.model.tree.NodeKind; +import org.ballerinalang.model.tree.expressions.ExpressionNode; +import org.ballerinalang.model.tree.expressions.NaturalExpressionNode; +import org.wso2.ballerinalang.compiler.tree.BLangNodeAnalyzer; +import org.wso2.ballerinalang.compiler.tree.BLangNodeTransformer; +import org.wso2.ballerinalang.compiler.tree.BLangNodeVisitor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a natural expression. + * + * @since 2201.13.0 + */ +public class BLangNaturalExpression extends BLangExpression implements NaturalExpressionNode { + + public boolean constExpr; + public List arguments; + public List strings; + public List insertions; + + public BLangNaturalExpression() { + arguments = new ArrayList<>(); + strings = new ArrayList<>(); + insertions = new ArrayList<>(); + } + + @Override + public void accept(BLangNodeVisitor visitor) { + visitor.visit(this); + } + + @Override + public void accept(BLangNodeAnalyzer analyzer, T props) { + analyzer.visit(this, props); + } + + @Override + public R apply(BLangNodeTransformer modifier, T props) { + return modifier.transform(this, props); + } + + @Override + public NodeKind getKind() { + return NodeKind.NATURAL_EXPR; + } + + @Override + public String toString() { + return "BLangNaturalExpression: arguments " + arguments + ", strings " + strings + ", insertions " + insertions; + } + + @Override + public List getInsertions() { + return insertions; + } + + @Override + public void addInsertion(ExpressionNode expression) { + insertions.add((BLangExpression) expression); + } + + @Override + public List getStrings() { + return strings; + } + + @Override + public void addString(ExpressionNode stringLiteral) { + strings.add((BLangLiteral) stringLiteral); + } +} diff --git a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/util/Names.java b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/util/Names.java index c59f1622323b..ea83e1c68379 100644 --- a/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/util/Names.java +++ b/compiler/ballerina-lang/src/main/java/org/wso2/ballerinalang/compiler/util/Names.java @@ -65,6 +65,7 @@ public class Names { public static final Name QUERY = new Name("query"); public static final Name RUNTIME = new Name("runtime"); public static final Name TRANSACTION = new Name("transaction"); + public static final Name NATURAL_PROGRAMMING = new Name("np"); public static final Name OBSERVE = new Name("observe"); public static final Name CLOUD = new Name("cloud"); public static final Name TABLE = new Name("table"); diff --git a/compiler/ballerina-lang/src/main/resources/compiler.properties b/compiler/ballerina-lang/src/main/resources/compiler.properties index 58b4fa297116..96f77d92b0a0 100644 --- a/compiler/ballerina-lang/src/main/resources/compiler.properties +++ b/compiler/ballerina-lang/src/main/resources/compiler.properties @@ -2027,3 +2027,18 @@ error.invalid.binding.pattern.in.on.fail=\ error.invalid.usage.of.check.in.parameter.default=\ cannot use ''check'' in the default value of a parameter + +error.expected.type.for.natural.expr.must.contain.error=\ + the expected type for a ''natural'' expression must contain ''error'' + +error.expected.type.for.natural.expr.must.contain.a.union.of.non.error.and.error=\ + the expected type for a ''natural'' expression must contain both a non-''error'' type and ''error'' + +error.expected.type.for.natural.expr.must.be.a.subtype.of.anydata.or.error=\ + the expected type for a ''natural'' expression must be a subtype of ''anydata|error'' + +error.const.natural.expr.can.have.only.const.expr.insertion=\ + an insertion in a ''const'' ''natural'' expression must be a constant expression + +error.expected.type.for.const.natural.expr.must.be.a.subtype.of.anydata=\ + the expected type for a ''const'' ''natural'' expression must be a subtype of ''anydata'' diff --git a/langlib/lang.annotations/src/main/ballerina/annotations.bal b/langlib/lang.annotations/src/main/ballerina/annotations.bal index 68219be653ff..61999d92d481 100644 --- a/langlib/lang.annotations/src/main/ballerina/annotations.bal +++ b/langlib/lang.annotations/src/main/ballerina/annotations.bal @@ -104,3 +104,9 @@ public const annotation record { } display on source type, source class, source function, source return, source parameter, source field, source listener, source var, source const, source annotation, source service, source external, source worker; + +# Denotes that the body of a function has to be generated at compile-time. +public const annotation record {| + # The natural language description of the code to be generated. + string prompt; +|} code on source external; diff --git a/language-server/modules/langserver-core/src/test/resources/completion/annotation_ctx/config/externalFunctionAnnotation1.json b/language-server/modules/langserver-core/src/test/resources/completion/annotation_ctx/config/externalFunctionAnnotation1.json index 0140641c0aa5..df9fde4fce85 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/annotation_ctx/config/externalFunctionAnnotation1.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/annotation_ctx/config/externalFunctionAnnotation1.json @@ -401,6 +401,15 @@ "insertText": "display {\n\tlabel: ${1:\"\"}\n}", "insertTextFormat": "Snippet", "additionalTextEdits": [] + }, + { + "label": "code", + "kind": "Property", + "detail": "Annotation", + "sortText": "B", + "insertText": "code {\n\tprompt: ${1:\"\"}\n}", + "insertTextFormat": "Snippet", + "additionalTextEdits": [] } ] } diff --git a/language-server/modules/langserver-core/src/test/resources/completion/annotation_ctx/config/externalFunctionAnnotation2.json b/language-server/modules/langserver-core/src/test/resources/completion/annotation_ctx/config/externalFunctionAnnotation2.json index 83d291b37c27..b72ce956dcb7 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/annotation_ctx/config/externalFunctionAnnotation2.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/annotation_ctx/config/externalFunctionAnnotation2.json @@ -401,6 +401,15 @@ "insertText": "display {\n\tlabel: ${1:\"\"}\n}", "insertTextFormat": "Snippet", "additionalTextEdits": [] + }, + { + "label": "code", + "kind": "Property", + "detail": "Annotation", + "sortText": "B", + "insertText": "code {\n\tprompt: ${1:\"\"}\n}", + "insertTextFormat": "Snippet", + "additionalTextEdits": [] } ] } diff --git a/language-server/modules/langserver-core/src/test/resources/completion/annotation_ctx/config/moduleLevelAnnotation2.json b/language-server/modules/langserver-core/src/test/resources/completion/annotation_ctx/config/moduleLevelAnnotation2.json index ab389ed6b0ac..c231281b2bf2 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/annotation_ctx/config/moduleLevelAnnotation2.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/annotation_ctx/config/moduleLevelAnnotation2.json @@ -496,6 +496,15 @@ "sortText": "P", "insertText": "typedesc", "insertTextFormat": "Snippet" + }, + { + "label": "code", + "kind": "Property", + "detail": "Annotation", + "sortText": "B", + "insertText": "code {\n\tprompt: ${1:\"\"}\n}", + "insertTextFormat": "Snippet", + "additionalTextEdits": [] } ] } diff --git a/language-server/modules/langserver-core/src/test/resources/completion/expression_context/config/typeof_expression_ctx_config5.json b/language-server/modules/langserver-core/src/test/resources/completion/expression_context/config/typeof_expression_ctx_config5.json index f9305d8d438a..c4aaeb3fbe49 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/expression_context/config/typeof_expression_ctx_config5.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/expression_context/config/typeof_expression_ctx_config5.json @@ -469,6 +469,15 @@ "sortText": "P", "insertText": "typedesc", "insertTextFormat": "Snippet" + }, + { + "label": "code", + "kind": "Property", + "detail": "Annotation", + "sortText": "B", + "insertText": "code {\n\tprompt: ${1:\"\"}\n}", + "insertTextFormat": "Snippet", + "additionalTextEdits": [] } ] } diff --git a/language-server/modules/langserver-core/src/test/resources/completion/variable-declaration/config/var_def_ctx_config18.json b/language-server/modules/langserver-core/src/test/resources/completion/variable-declaration/config/var_def_ctx_config18.json index 70ea20e546ad..ff9252b49282 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/variable-declaration/config/var_def_ctx_config18.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/variable-declaration/config/var_def_ctx_config18.json @@ -460,6 +460,15 @@ "sortText": "P", "insertText": "typedesc", "insertTextFormat": "Snippet" + }, + { + "label": "code", + "kind": "Property", + "detail": "Annotation", + "sortText": "B", + "insertText": "code {\n\tprompt: ${1:\"\"}\n}", + "insertTextFormat": "Snippet", + "additionalTextEdits": [] } ] } diff --git a/tests/ballerina-compiler-api-test/src/test/java/io/ballerina/semantic/api/test/SymbolBIRTest.java b/tests/ballerina-compiler-api-test/src/test/java/io/ballerina/semantic/api/test/SymbolBIRTest.java index 92fdd37f800b..ea71e91d2232 100644 --- a/tests/ballerina-compiler-api-test/src/test/java/io/ballerina/semantic/api/test/SymbolBIRTest.java +++ b/tests/ballerina-compiler-api-test/src/test/java/io/ballerina/semantic/api/test/SymbolBIRTest.java @@ -334,7 +334,7 @@ private List getAnnotationModuleSymbolInfo() { {"deprecated", ANNOTATION}, {"untainted", ANNOTATION}, {"tainted", ANNOTATION}, {"display", ANNOTATION}, {"strand", ANNOTATION}, {"StrandData", TYPE_DEFINITION}, {"typeParam", ANNOTATION}, {"Thread", TYPE_DEFINITION}, {"builtinSubtype", ANNOTATION}, - {"isolatedParam", ANNOTATION}, {"error", TYPE_DEFINITION} + {"isolatedParam", ANNOTATION}, {"error", TYPE_DEFINITION}, {"code", ANNOTATION} }); } diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/annotations/CodeAnnotationTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/annotations/CodeAnnotationTest.java new file mode 100644 index 000000000000..25050f091b5e --- /dev/null +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/annotations/CodeAnnotationTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). + * + * 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.annotations; + +import org.ballerinalang.test.BAssertUtil; +import org.ballerinalang.test.BCompileUtil; +import org.ballerinalang.test.CompileResult; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Negative tests for the code annotation. + * + * @since 2201.13.0 + */ +public class CodeAnnotationTest { + + @Test + public void testExternalDependencyAnnotation() { + CompileResult compileResult = + BCompileUtil.compile("test-src/annotations/code_annotation_negative.bal"); + int i = 0; + BAssertUtil.validateError(compileResult, i++, + "annotation value expected for annotation of record type " + + "'record {| string prompt; |}' with required fields", 17, 17); + BAssertUtil.validateError(compileResult, i++, + "missing non-defaultable required record field 'prompt'", 19, 23); + BAssertUtil.validateError(compileResult, i++, + "incompatible types: expected 'string', found 'int'", 22, 13); + Assert.assertEquals(compileResult.getErrorCount(), i); + } +} diff --git a/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/natural/NaturalExpressionTest.java b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/natural/NaturalExpressionTest.java new file mode 100644 index 000000000000..555cbcb6edd5 --- /dev/null +++ b/tests/jballerina-unit-test/src/test/java/org/ballerinalang/test/expressions/natural/NaturalExpressionTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, WSO2 LLC. (http://wso2.com). + * + * 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 org.ballerinalang.test.expressions.natural; + +import org.ballerinalang.test.BAssertUtil; +import org.ballerinalang.test.BCompileUtil; +import org.ballerinalang.test.CompileResult; +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Negative semantic test cases for natural expressions. + * + * @since 2201.13.0 + */ +public class NaturalExpressionTest { + + @Test + public void testNaturalExprNegative() { + CompileResult negativeRes = BCompileUtil.compile( + "test-src/expressions/naturalexpr/natural_expr_negative.bal"); + int i = 0; + BAssertUtil.validateError(negativeRes, i++, + "the expected type for a 'natural' expression must contain 'error'", 17, 9); + BAssertUtil.validateError(negativeRes, i++, + "the expected type for a 'natural' expression must be a subtype of 'anydata|error'", 26, 55); + BAssertUtil.validateError(negativeRes, i++, "undefined symbol 'day'", 37, 15); + BAssertUtil.validateError(negativeRes, i++, "incompatible types: expected 'anydata', found 'PersonObject'", + 37, 29); + BAssertUtil.validateError(negativeRes, i++, + "the expected type for a 'natural' expression must contain both a non-'error' type and 'error'", + 40, 32); + BAssertUtil.validateError(negativeRes, i++, + "an insertion in a 'const' 'natural' expression must be a constant expression", 46, 15); + BAssertUtil.validateError(negativeRes, i++, + "the expected type for a 'const' 'natural' expression must be a subtype of 'anydata'", 49, 11); + BAssertUtil.validateError(negativeRes, i++, + "the expected type for a 'const' 'natural' expression must be a subtype of 'anydata'", 55, 18); + Assert.assertEquals(negativeRes.getErrorCount(), i); + } +} diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/annotations/code_annotation_negative.bal b/tests/jballerina-unit-test/src/test/resources/test-src/annotations/code_annotation_negative.bal new file mode 100644 index 000000000000..ac56c570fc48 --- /dev/null +++ b/tests/jballerina-unit-test/src/test/resources/test-src/annotations/code_annotation_negative.bal @@ -0,0 +1,23 @@ +// Copyright (c) 2025, WSO2 LLC. (http://www.wso2.com). +// +// 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. + +function f1() = @code external; + +function f2() = @code {} external; + +function f3(string str) returns int[] = @code { + prompt: 1 +} external; diff --git a/tests/jballerina-unit-test/src/test/resources/test-src/expressions/naturalexpr/natural_expr_negative.bal b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/naturalexpr/natural_expr_negative.bal new file mode 100644 index 000000000000..3637fdd6806c --- /dev/null +++ b/tests/jballerina-unit-test/src/test/resources/test-src/expressions/naturalexpr/natural_expr_negative.bal @@ -0,0 +1,57 @@ +// Copyright (c) 2025 WSO2 LLC. (http://www.wso2.org). +// +// WSO2 Inc. 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. + +int a = natural { + What is 1 + 1? +}; + +type PersonObject object { + string name; + int age; +}; + +function f1(string day) returns PersonObject|error => natural { + Who is a popular person born on ${day}? +}; + +type Person record {| + string name; + int age; +|}; + +function f2(PersonObject person) returns Person|error => natural { + Who is a popular person + born on ${day}? Maybe ${person}? +}; + +function f3() returns error => natural { + What's the colour of this IDE? +}; + +int count = 5; +int[] b = const natural { + Give me ${count} integers between 1 and 100 +}; + +error c = const natural { + What's the colour of this IDE? +}; + +const DAY = "9th April"; + +PersonObject d = const natural { + Who is a popular person born on ${DAY}? +}; diff --git a/tests/jballerina-unit-test/src/test/resources/testng.xml b/tests/jballerina-unit-test/src/test/resources/testng.xml index d5ff93328fc7..53cf454d9510 100644 --- a/tests/jballerina-unit-test/src/test/resources/testng.xml +++ b/tests/jballerina-unit-test/src/test/resources/testng.xml @@ -53,6 +53,7 @@ +