Skip to content

Commit

Permalink
Merge pull request #41894 from poorna2152/fix_-41236
Browse files Browse the repository at this point in the history
Fix use of binding patterns in closures
  • Loading branch information
chiranSachintha authored Mar 19, 2024
2 parents 1351f82 + 5e3bae1 commit 52c6286
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -746,8 +746,8 @@ public void visit(BLangSimpleVariableDef varDefNode) {
* @return assignment statement created
*/
private BLangAssignment createAssignmentToClosureMap(BLangSimpleVariableDef varDefNode) {
BVarSymbol mapSymbol = createMapSymbolIfAbsent(env.node, blockClosureMapCount);

int absoluteLevel = findResolvedLevel(env, varDefNode.var.symbol);
BVarSymbol mapSymbol = findClosureMapSymbol(absoluteLevel);
// Add the variable to the created map.
BLangIndexBasedAccess accessExpr =
ASTBuilderUtil.createIndexBasesAccessExpr(varDefNode.pos, varDefNode.getBType(), mapSymbol,
Expand All @@ -760,6 +760,17 @@ private BLangAssignment createAssignmentToClosureMap(BLangSimpleVariableDef varD
return ASTBuilderUtil.createAssignmentStmt(varDefNode.pos, accessExpr, varDefNode.var.expr);
}

private BVarSymbol findClosureMapSymbol(int absoluteLevel) {
SymbolEnv symbolEnv = env;
while (symbolEnv.node.getKind() != NodeKind.PACKAGE) {
if (symbolEnv.envCount == absoluteLevel) {
return createMapSymbolIfAbsent(symbolEnv.node, symbolEnv.envCount);
}
symbolEnv = symbolEnv.enclEnv;
}
throw new IllegalStateException("Failed to find the closure symbol defined scope");
}

private BVarSymbol createMapSymbolIfAbsent(BLangNode node, int closureMapCount) {
NodeKind kind = node.getKind();
switch (kind) {
Expand Down Expand Up @@ -1528,26 +1539,8 @@ public void visit(BLangSimpleVarRef.BLangLocalVarRef localVarRef) {

// selfRelativeCount >= selfAbsoluteLevel - absoluteLevel ==> resolved within the same function.
if (selfRelativeCount >= selfAbsoluteLevel - absoluteLevel) {

// Go up within the block node
SymbolEnv symbolEnv = env;
NodeKind nodeKind = symbolEnv.node.getKind();
while (symbolEnv != null && nodeKind != NodeKind.PACKAGE) {
// Check if the node is a sequence statement
if (symbolEnv.envCount == absoluteLevel) {
BVarSymbol mapSym = createMapSymbolIfAbsent(symbolEnv.node, symbolEnv.envCount);

// `mapSym` will not be null for block function bodies, block stmts, functions and classes. And we
// only need to update closure vars for those nodes.
if (mapSym != null) {
updateClosureVars(localVarRef, mapSym);
return;
}
}

symbolEnv = symbolEnv.enclEnv;
nodeKind = symbolEnv.node.getKind();
}
BVarSymbol mapSym = findClosureMapSymbol(absoluteLevel);
updateClosureVars(localVarRef, mapSym);
} else {
// It is resolved from a parameter map.
// Add parameter map symbol if one is not added.
Expand All @@ -1572,10 +1565,10 @@ public void visit(BLangSimpleVarRef.BLangLocalVarRef localVarRef) {
PARAMETER_MAP_NAME + absoluteLevel, env));
updateClosureVars(localVarRef, ((BLangFunction) env.enclInvokable).paramClosureMap.get(absoluteLevel));
}
}

// 3) Add the resolved level of the closure variable to the preceding function maps.
updatePrecedingFunc(env, absoluteLevel);
// 3) Add the resolved level of the closure variable to the preceding function maps.
updatePrecedingFunc(env, absoluteLevel);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

/**
Expand Down Expand Up @@ -271,6 +272,23 @@ public void errorConstructorWithClosureTest() {
public void forEachWithClosure() {
BRunUtil.invoke(compileResult, "test30");
}

@Test(description = "Test closure with binding type param", dataProvider = "closureWithBindingPatternTypeParam")
public void testClosureWithBindingPatternTypeParam(String functionName) {
BRunUtil.invoke(compileResult, functionName);
}

@DataProvider(name = "closureWithBindingPatternTypeParam")
private Object[] closureWithBindingPatternTypeParam() {
return new String[]{
"testClosureWithStructuredBindingTypeParams",
"testClosureWithTupleBindingTypeParams",
"testClosureWithBindingPatternDefaultValues",
"testClosureWithErrorBindingPatterns",
"testClosureWithBindingPatternsInForEach"
};
}

@AfterClass
public void tearDown() {
compileResult = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2024, 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.query;

import org.ballerinalang.test.BCompileUtil;
import org.ballerinalang.test.BRunUtil;
import org.ballerinalang.test.CompileResult;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

/**
* This contains methods to test query expressions and query actions which uses variables defined in binding patterns.
*
* @since 2201.9.0
*/
public class QueryWithBindingPatternVarTest {

private CompileResult result;

@BeforeClass
public void setup() {
result = BCompileUtil.compile("test-src/query/query_with_binding_pattern.bal");
}

@Test(dataProvider = "dataToTestQueryWithBindingPatternVar")
public void testQueryWithBindingPatternVars(String functionName) {
BRunUtil.invoke(result, functionName);
}

@DataProvider
public Object[] dataToTestQueryWithBindingPatternVar() {
return new Object[]{
"testSelectClauseWithBindingVar",
"testCollectClauseWithBindingVar",
"testFunctionPointerInvocationWithBindingVar",
"testQueryActionWithBindingVar",
"testClosureInQueryActionInDoWithBindingVar",
"testNestedQueryInSelectWithBindingVar",
"testLimitClauseAndQueryExprWithBindingVar"
};
}

@AfterClass
public void tearDown() {
result = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ public void anonymousRecordWithTypeInclusion() {
BRunUtil.invoke(compileResult, "anonymousRecordWithTypeInclusion");
}

@Test(description = "Test record type with binding pattern var in default value")
public void testRecordWithDefaultsFromBindingPatternVar() {
BRunUtil.invoke(compileResult, "testRecordWithDefaultsFromBindingPatternVar");
}

@AfterClass
public void tearDown() {
compileResult = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,11 @@ public void testWorkerWithQuery() {
BRunUtil.invoke(result, "testWorkerWithQuery", new Object[0]);
}

@Test
public void testBindingPatternVariablesInWorker() {
BRunUtil.invoke(result, "testBindingPatternVariablesInWorker");
}

@AfterClass
public void tearDown() {
result = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,116 @@ function test30() {
assert(y, 117);
}

type School record {|
string name;
|};

type Doctor record {
string name;
string category;
School school;
};

function testClosureWithStructuredBindingTypeParams() {
string[] categories = ["Orthopedic", "Dentist"];
string[] schools = [];
Doctor doctor = {name: "Dr. Smith", category: "Cardiologist", school: {name: "Medical College"}};
var {category, school: {name}} = doctor;
var f = function () {
categories.push(category);
};
var g = function () {
schools.push(name);
};
if !categories.some(existingCategory => existingCategory == category) {
f();
}
if !schools.some(existingSchool => existingSchool == name) {
g();
}
assert(categories, ["Orthopedic", "Dentist", "Cardiologist"]);
assert(schools, ["Medical College"]);
}

function testClosureWithTupleBindingTypeParams() {
[int, [string, string]] [id, [firstname, _]] = [1,["John", "Doe"]];
var increment = function() returns int {
int len = firstname.length();
return id + len;
};
assert(increment(), 5);
}

function testClosureWithBindingPatternDefaultValues() {
record {|string nt;|} r = {nt: "nt"};
[string] [i] = ["i"];
var {nt} = r;
var f = function(string s = i) returns string {
return i + nt;
};
assert(f(), "int");
}

type SampleErrorData record {|
int code;
string reason;
|};

type SampleError error<SampleErrorData>;

function testClosureWithErrorBindingPatterns() {
SampleError e = error("Transaction Failure", error("Database Error"), code = 20,
reason = "deadlock condition");
var error(code = code, reason = reason) = e;
var formatMessage = function() returns string {
return code.toString() + ":" + reason;
};
assert(formatMessage(), "20:deadlock condition");
}

type R record {|
int a;
string b;
|};

function testClosureWithBindingPatternsInForEach() {
string[] values = ["a", "b", "c"];
string[] restrictedKeys = ["g", "h", "i"];
map<int> data = {
a: 1
};

foreach var [k, _] in data.entries() {
var isRestricted = function() returns boolean {
return restrictedKeys.indexOf(k) !is ();
};
assert(isRestricted(), false);
assert(values.filter(item => item == k), ["a"]);
}

R[] rs = [{a: 1, b: "a"}, {a: 2, b: "b"}, {a: 3, b: "c"}];
int[] allowedNs = [1, 2, 3];

foreach var {a: a, b: b1} in rs {
var isAllowed = function() returns boolean {
return allowedNs.indexOf(a) !is ();
};
assert(isAllowed(), true);
assert(values.filter(item => item == b1), [b1]);
}

SampleError e1 = error("Type Cast Failure", error("Type Error"), code = 21,
reason = "xml cannot be cast to json");
SampleError[] es = [e1];

foreach var error(code = code, reason = reason) in es {
var getErrorMessage = function() returns string {
return code.toString() + ":" + reason;
};
assert(getErrorMessage(), "21:xml cannot be cast to json");
}
}

function assert(anydata actual, anydata expected) {
if (expected == actual) {
return;
Expand Down
Loading

0 comments on commit 52c6286

Please sign in to comment.