Skip to content

Commit

Permalink
Merge pull request #647 from nipunayf/fix-inferred-param
Browse files Browse the repository at this point in the history
Improve the forms containing inferred type parameters
  • Loading branch information
hasithaa authored Mar 5, 2025
2 parents 0709e3f + 3d2f4fd commit 5884ca7
Show file tree
Hide file tree
Showing 198 changed files with 15,952 additions and 13,215 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
hs_err_pid*

.idea
**/bin/

# mac
.DS_Store
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,17 @@
import io.ballerina.flowmodelgenerator.core.model.Property;
import io.ballerina.flowmodelgenerator.core.model.node.AssignBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.BinaryBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.CallBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.DataMapperBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.FailBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.FunctionCall;
import io.ballerina.flowmodelgenerator.core.model.node.IfBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.JsonPayloadBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.MethodCall;
import io.ballerina.flowmodelgenerator.core.model.node.NewConnectionBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.PanicBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.RemoteActionCallBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.ResourceActionCallBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.ReturnBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.RollbackBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.StartBuilder;
Expand Down Expand Up @@ -280,7 +285,7 @@ public void visit(RemoteMethodCallActionNode remoteMethodCallActionNode) {
.description(functionData.description())
.stepOut()
.codedata()
.nodeInfo(remoteMethodCallActionNode)
.nodeInfo(remoteMethodCallActionNode)
.object(classSymbol.get().getName().orElse(""))
.symbol(functionName)
.stepOut()
Expand Down Expand Up @@ -607,7 +612,7 @@ private void buildPropsFromFuncCallArgs(SeparatedNodeList<FunctionArgumentNode>
String escapedParamName = parameterSymbol.getName().get();
ParameterData paramResult = funcParamMap.get(escapedParamName);
if (paramResult == null) {
escapedParamName = CommonUtil.escapeReservedKeyword(parameterSymbol.getName().get());
continue;
}
paramResult = funcParamMap.get(escapedParamName);
Node paramValue;
Expand Down Expand Up @@ -1062,6 +1067,11 @@ private void handleVariableNode(NonTerminalNode variableDeclarationNode) {
nodeBuilder.properties()
.dataVariable(this.typedBindingPatternNode, NewConnectionBuilder.CONNECTION_NAME_LABEL,
NewConnectionBuilder.CONNECTION_TYPE_LABEL, false, new HashSet<>());
} else if (nodeBuilder instanceof RemoteActionCallBuilder || nodeBuilder instanceof ResourceActionCallBuilder ||
nodeBuilder instanceof FunctionCall || nodeBuilder instanceof MethodCall) {
nodeBuilder.properties()
.dataVariable(this.typedBindingPatternNode, Property.VARIABLE_NAME, Property.TYPE_LABEL, false,
new HashSet<>());
} else {
nodeBuilder.properties().dataVariable(this.typedBindingPatternNode, implicit, new HashSet<>());
}
Expand Down Expand Up @@ -1238,9 +1248,60 @@ private void processFunctionSymbol(NonTerminalNode callNode, SeparatedNodeList<F
final Map<String, Node> namedArgValueMap = new HashMap<>();
final Queue<Node> positionalArgs = new LinkedList<>();
calculateFunctionArgs(namedArgValueMap, positionalArgs, arguments);
buildPropsFromFuncCallArgs(arguments, functionSymbol.typeDescriptor(), functionData.parameters(),
positionalArgs, namedArgValueMap);
handleCheckFlag(callNode, functionSymbol.typeDescriptor());

Map<String, ParameterData> funcParamMap = new HashMap<>();
FunctionTypeSymbol functionTypeSymbol = functionSymbol.typeDescriptor();
functionData.parameters().forEach((key, paramResult) -> {
if (paramResult.kind() != ParameterData.Kind.PARAM_FOR_TYPE_INFER) {
funcParamMap.put(key, paramResult);
return;
}
String returnType = functionData.returnType();

// Derive the value of the inferred type name
String inferredTypeName;
// Check if the value exists in the named arg map
Node node = namedArgValueMap.get(key);
if (node != null) {
inferredTypeName = node.toSourceCode();
} else if (typedBindingPatternNode == null) {
// Get the default value if the variable is absent
inferredTypeName = paramResult.defaultValue();
} else {
// Derive the inferred type from the variable type
Optional<Symbol> symbol = semanticModel.symbol(typedBindingPatternNode);
if (symbol.isEmpty() || symbol.get().kind() != SymbolKind.VARIABLE) {
return;
}
String variableType =
CommonUtils.getTypeSignature(((VariableSymbol) symbol.get()).typeDescriptor(), moduleInfo);

inferredTypeName = deriveInferredType(variableType, returnType, key);
}

// Generate the property of the inferred type param
nodeBuilder.codedata().inferredReturnType(functionData.returnError() ? returnType : null);
CallBuilder.buildInferredTypeProperty(nodeBuilder, paramResult, inferredTypeName);
});
buildPropsFromFuncCallArgs(arguments, functionTypeSymbol, funcParamMap, positionalArgs, namedArgValueMap);
handleCheckFlag(callNode, functionTypeSymbol);
}

private static String deriveInferredType(String variableType, String returnType, String key) {
int keyIndex = returnType.indexOf(key);
if (keyIndex == -1) {
// If key is not found, fallback to returning variableType.
return variableType;
}
String prefix = returnType.substring(0, keyIndex);
String suffix = returnType.substring(keyIndex + key.length());

// Check if variableType has the same structure as returnType.
if (variableType.startsWith(prefix) && variableType.endsWith(suffix)) {
return variableType.substring(prefix.length(), variableType.length() - suffix.length());
}
// If the structure doesn't match, return variableType as fallback.
return variableType;
}

private static String getIdentifierName(NameReferenceNode nameReferenceNode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,8 @@ private Codedata getCodedataWithoutPosition(Codedata codedata) {
codedata.resourcePath(),
codedata.id(),
codedata.isNew(),
codedata.isGenerated()
codedata.isGenerated(),
codedata.inferredReturnType()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,26 @@
/**
* Represents the properties that uniquely identifies a node in the diagram.
*
* @param node The kind of the component
* @param org The organization which the component belongs to
* @param module The module which the component belongs to
* @param object The object of the component if it is a method or an action call
* @param symbol The symbol of the component
* @param version The version of the component
* @param lineRange The line range of the component
* @param sourceCode The source code of the component
* @param parentSymbol The parent symbol of the component
* @param resourcePath The path of the resource function
* @param id The unique identifier of the component if exists
* @param isNew Whether the component is a node template
* @param isGenerated The component is auto generated or not
* @param node The kind of the component
* @param org The organization which the component belongs to
* @param module The module which the component belongs to
* @param object The object of the component if it is a method or an action call
* @param symbol The symbol of the component
* @param version The version of the component
* @param lineRange The line range of the component
* @param sourceCode The source code of the component
* @param parentSymbol The parent symbol of the component
* @param resourcePath The path of the resource function
* @param id The unique identifier of the component if exists
* @param isNew Whether the component is a node template
* @param isGenerated The component is auto generated or not
* @param inferredReturnType The inferred return type of the component if exists
* @since 2.0.0
*/
public record Codedata(NodeKind node, String org, String module, String object, String symbol,
String version, LineRange lineRange, String sourceCode, String parentSymbol,
String resourcePath, Integer id, Boolean isNew, Boolean isGenerated) {
String resourcePath, Integer id, Boolean isNew, Boolean isGenerated,
String inferredReturnType) {

@Override
public String toString() {
Expand Down Expand Up @@ -79,6 +81,7 @@ public static class Builder<T> extends FacetedBuilder<T> {
private Integer id;
private Boolean isNew;
private Boolean isGenerated;
private String inferredReturnType;

public Builder(T parentBuilder) {
super(parentBuilder);
Expand Down Expand Up @@ -155,9 +158,14 @@ public Builder<T> isGenerated(Boolean isGenerated) {
return this;
}

public Builder<T> inferredReturnType(String inferredReturnType) {
this.inferredReturnType = inferredReturnType;
return this;
}

public Codedata build() {
return new Codedata(node, org, module, object, symbol, version, lineRange, sourceCode, parentSymbol,
resourcePath, id, isNew, isGenerated);
resourcePath, id, isNew, isGenerated, inferredReturnType);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public FormBuilder<T> data(Node node, String label, String doc, String templateN

if (node != null && !assignment) {
propertyBuilder.codedata()
.lineRange(node.lineRange());
.lineRange(node.lineRange());
} else {
propertyBuilder.editable();
}
Expand Down Expand Up @@ -172,16 +172,24 @@ public FormBuilder<T> type(Node node, String label, boolean editable) {
return type(typeName, label, editable, node == null ? null : node.lineRange());
}

public FormBuilder<T> type(String typeName, boolean editable) {
return type(typeName, Property.TYPE_LABEL, editable, null);
public FormBuilder<T> type(String typeName, boolean editable, String importStatements) {
return type(typeName, Property.TYPE_LABEL, editable, null, importStatements);
}

public FormBuilder<T> type(String typeName, String label, boolean editable, LineRange lineRange) {
return type(typeName, label, editable, lineRange, null);
}

public FormBuilder<T> type(String typeName, String label, boolean editable, LineRange lineRange,
String importStatements) {
propertyBuilder
.metadata()
.label(label)
.description(Property.TYPE_DOC)
.stepOut()
.codedata()
.importStatements(importStatements)
.stepOut()
.placeholder("var")
.value(typeName)
.type(Property.ValueType.TYPE)
Expand Down Expand Up @@ -713,7 +721,7 @@ public FormBuilder<T> functionName(IdentifierToken identifierToken) {

if (!functionName.equals(Constants.MAIN_FUNCTION_NAME)) {
propertyBuilder.codedata()
.lineRange(identifierToken.lineRange());
.lineRange(identifierToken.lineRange());
}

addProperty(Property.FUNCTION_NAME_KEY);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,35 @@ public SourceBuilder newVariable() {
return newVariable(Property.TYPE_KEY);
}

public SourceBuilder newVariableWithInferredType() {
Optional<Property> optionalType = flowNode.getProperty(Property.TYPE_KEY);
Optional<Property> variable = flowNode.getProperty(Property.VARIABLE_KEY);

if (optionalType.isEmpty() || variable.isEmpty()) {
return this;
}

// Derive the type name form the inferred type
Property type = optionalType.get();
String typeName = type.value().toString();
if (flowNode.codedata().inferredReturnType() != null) {
Optional<Property> inferredParam = flowNode.properties().values().stream()
.filter(property -> property.codedata() != null && property.codedata().kind() != null &&
property.codedata().kind().equals(ParameterData.Kind.PARAM_FOR_TYPE_INFER.name()))
.findFirst();
if (inferredParam.isPresent()) {
String returnType = flowNode.codedata().inferredReturnType();
String inferredType = inferredParam.get().value().toString();
String inferredTypeDef = inferredParam.get()
.metadata().label();
typeName = returnType.replace(inferredTypeDef, inferredType);
}
}

tokenBuilder.expressionWithType(typeName, variable.get()).keyword(SyntaxKind.EQUAL_TOKEN);
return this;
}

public SourceBuilder newVariable(String typeKey) {
Optional<Property> type = flowNode.getProperty(typeKey);
Optional<Property> variable = flowNode.getProperty(Property.VARIABLE_KEY);
Expand Down Expand Up @@ -146,7 +175,42 @@ public SourceBuilder acceptImport(Path resolvedPath) {
Codedata codedata = flowNode.codedata();
String org = codedata.org();
String module = codedata.module();
return acceptImport(resolvedPath, org, module);
}

public SourceBuilder acceptImportWithVariableType() {
Optional<Property> optionalType = flowNode.getProperty(Property.TYPE_KEY);
if (optionalType.isPresent()) {
Property type = optionalType.get();

// TODO: There can be cases where the return type and the value type both come from imported modules. We
// have
// to optimize how we handle the return type, as the current implementation does not allow the user to
// assign the error to a variable and handle it.
// Add the import statements if exists in the return type
if (type.codedata() != null && type.codedata().importStatements() != null &&
flowNode.getProperty(Property.CHECK_ERROR_KEY).map(property -> property.value().equals("false"))
.orElse(true)) {
// TODO: Improve this logic to process all the imports at once
for (String importStatement : type.codedata().importStatements().split(",")) {
String[] importParts = importStatement.split("/");
acceptImport(importParts[0], importParts[1]);
}
}
}
acceptImport();
return this;
}

public SourceBuilder acceptImport() {
return acceptImport(filePath);
}

public SourceBuilder acceptImport(String org, String module) {
return acceptImport(filePath, org, module);
}

public SourceBuilder acceptImport(Path resolvedPath, String org, String module) {
if (org == null || module == null || org.equals(CommonUtil.BALLERINA_ORG_NAME) &&
CommonUtil.PRE_DECLARED_LANG_LIBS.contains(module)) {
return this;
Expand Down Expand Up @@ -184,12 +248,12 @@ public SourceBuilder acceptImport(Path resolvedPath) {
// Add the import statement
if (!importExists) {
String importSignature;
Boolean generated = codedata.isGenerated();
Boolean generated = flowNode.codedata().isGenerated();
// TODO: Check this condition for other cases like persist module
if (!currentModuleName.isEmpty() && generated != null && generated) {
importSignature = currentModuleName + "." + module;
} else {
importSignature = codedata.getImportSignature();
importSignature = CommonUtils.getImportStatement(org, module, module);
}
tokenBuilder
.keyword(SyntaxKind.IMPORT_KEYWORD)
Expand All @@ -200,10 +264,6 @@ public SourceBuilder acceptImport(Path resolvedPath) {
return this;
}

public SourceBuilder acceptImport() {
return acceptImport(filePath);
}

public Optional<TypeDefinitionSymbol> getTypeDefinitionSymbol(String typeName) {
try {
workspaceManager.loadProject(filePath);
Expand Down Expand Up @@ -318,6 +378,10 @@ public SourceBuilder functionParameters(FlowNode nodeTemplate, Set<String> ignor
String kind = prop.codedata().kind();
boolean optional = prop.optional();

if (kind.equals(ParameterData.Kind.PARAM_FOR_TYPE_INFER.name())) {
continue;
}

if (firstParamAdded) {
if ((kind.equals(ParameterData.Kind.REST_PARAMETER.name()))) {
if (isPropValueEmpty(prop) || ((List<?>) prop.value()).isEmpty()) {
Expand Down Expand Up @@ -530,6 +594,11 @@ public TokenBuilder expressionWithType(Property type, Property variable) {
return this;
}

public TokenBuilder expressionWithType(String type, Property variable) {
sb.append(type).append(WHITE_SPACE).append(variable.toSourceCode()).append(WHITE_SPACE);
return this;
}

public TokenBuilder expressionWithType(Property property) {
sb.append(property.valueType()).append(WHITE_SPACE).append(property.toSourceCode());
return this;
Expand Down
Loading

0 comments on commit 5884ca7

Please sign in to comment.