Skip to content

Commit

Permalink
Merge pull request #40835 from LakshanWeerasinghe/fix-#40013
Browse files Browse the repository at this point in the history
[Master] Fix issues when there is union type in the select clause
  • Loading branch information
pcnfernando authored Aug 17, 2023
2 parents cc94e5e + 6185bb3 commit a1902ad
Show file tree
Hide file tree
Showing 8 changed files with 456 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,15 @@ public BType resolveQueryType(SymbolEnv env, BLangExpression selectExp, BType ta
List<BType> resolvedTypes = new ArrayList<>();
BType selectType;
BLangExpression collectionNode = (BLangExpression) ((BLangFromClause) clauses.get(0)).getCollection();
for (BType type : safeResultTypes) {
solveSelectTypeAndResolveType(queryExpr, selectExp, type, collectionNode.getBType(), selectTypes,
resolvedTypes, env, data, false);
}
solveSelectTypeAndResolveType(queryExpr, selectExp, safeResultTypes, collectionNode.getBType(), selectTypes,
resolvedTypes, env, data, false);

if (selectTypes.size() == 1) {
selectType = selectTypes.get(0);
checkExpr(selectExp, env, selectType, data);
List<BType> collectionTypes = getCollectionTypes(clauses);
BType completionType = getCompletionType(collectionTypes, types.getQueryConstructType(queryExpr), data);
selectType = selectTypes.get(0);

if (queryExpr.isStream) {
return new BStreamType(TypeTags.STREAM, selectType, completionType, null);
} else if (queryExpr.isTable) {
Expand Down Expand Up @@ -327,89 +328,162 @@ public BType resolveQueryType(SymbolEnv env, BLangExpression selectExp, BType ta
}
}

void solveSelectTypeAndResolveType(BLangQueryExpr queryExpr, BLangExpression selectExp, BType expType,
void solveSelectTypeAndResolveType(BLangQueryExpr queryExpr, BLangExpression selectExp, List<BType> expTypes,
BType collectionType, List<BType> selectTypes, List<BType> resolvedTypes,
SymbolEnv env, TypeChecker.AnalyzerData data, boolean isReadonly) {
BType selectType, resolvedType;
BType type = Types.getImpliedType(expType);
switch (type.tag) {
case TypeTags.ARRAY:
BType elementType = ((BArrayType) type).eType;
selectType = checkExpr(selectExp, env, elementType, data);
BType queryResultType = new BArrayType(selectType);
resolvedType = getResolvedType(queryResultType, type, isReadonly, env);
break;
case TypeTags.TABLE:
selectType = checkExpr(selectExp, env, types.getSafeType(((BTableType) type).constraint,
true, true), data);
resolvedType = getResolvedType(symTable.tableType, type, isReadonly, env);
break;
case TypeTags.STREAM:
selectType = checkExpr(selectExp, env, types.getSafeType(((BStreamType) type).constraint,
true, true), data);
resolvedType = symTable.streamType;
break;
case TypeTags.MAP:
List<BTupleMember> memberTypeList = new ArrayList<>(2);
BVarSymbol stringVarSymbol = new BVarSymbol(0, null, null,
symTable.semanticError, null, symTable.builtinPos, SymbolOrigin.VIRTUAL);
memberTypeList.add(new BTupleMember(symTable.stringType, stringVarSymbol));
BType memberType = ((BMapType) type).getConstraint();
BVarSymbol varSymbol = Symbols.createVarSymbolForTupleMember(memberType);
memberTypeList.add(new BTupleMember(memberType, varSymbol));
BTupleType newExpType = new BTupleType(null, memberTypeList);
selectType = checkExpr(selectExp, env, newExpType, data);
resolvedType = getResolvedType(selectType, type, isReadonly, env);
break;
case TypeTags.STRING:
selectType = checkExpr(selectExp, env, type, data);
resolvedType = symTable.stringType;
break;
case TypeTags.XML:
case TypeTags.XML_COMMENT:
case TypeTags.XML_ELEMENT:
case TypeTags.XML_PI:
case TypeTags.XML_TEXT:
selectType = checkExpr(selectExp, env, type, data);
if (types.isAssignable(selectType, symTable.xmlType)) {
resolvedType = getResolvedType(new BXMLType(selectType, null), type, isReadonly, env);
} else {
resolvedType = selectType;
}
break;
case TypeTags.INTERSECTION:
type = ((BIntersectionType) type).effectiveType;
solveSelectTypeAndResolveType(queryExpr, selectExp, type, collectionType, selectTypes,
resolvedTypes, env, data, Symbols.isFlagOn(type.flags, Flags.READONLY));
return;
case TypeTags.NONE:
default:
// contextually expected type not given (i.e var).
selectType = checkExprSilent(nodeCloner.cloneNode(selectExp), type, data);
if (selectType != symTable.semanticError) {
selectType = checkExpr(selectExp, env, type, data);
} else {
selectType = checkExpr(selectExp, env, data);
LinkedHashSet<BType> possibleSelectTypes = new LinkedHashSet<>();
List<BType> possibleResolvedTypes = new ArrayList<>();
LinkedHashSet<BType> errorTypes = new LinkedHashSet<>();
for (BType expType : expTypes) {
BType selectType, resolvedType;
BType type = Types.getReferredType(expType);
switch (type.tag) {
case TypeTags.ARRAY:
BType elementType = ((BArrayType) type).eType;
selectType = checkExprSilent(selectExp, env, elementType, data);
if (selectType == symTable.semanticError) {
errorTypes.add(elementType);
continue;
}
BType queryResultType = new BArrayType(selectType);
resolvedType = getResolvedType(queryResultType, type, isReadonly, env);
break;
case TypeTags.TABLE:
BType tableConstraint = types.getSafeType(((BTableType) type).constraint,
true, true);
selectType = checkExprSilent(selectExp, env, tableConstraint, data);
if (selectType == symTable.semanticError) {
errorTypes.add(tableConstraint);
continue;
}
resolvedType = getResolvedType(symTable.tableType, type, isReadonly, env);
break;
case TypeTags.STREAM:
BType streamConstraint = types.getSafeType(((BStreamType) type).constraint,
true, true);
selectType = checkExprSilent(selectExp, env, streamConstraint, data);
if (selectType == symTable.semanticError) {
errorTypes.add(streamConstraint);
continue;
}
resolvedType = symTable.streamType;
break;
case TypeTags.MAP:
List<BTupleMember> memberTypeList = new ArrayList<>(2);
BVarSymbol stringVarSymbol = new BVarSymbol(0, null, null,
symTable.semanticError, null, symTable.builtinPos, SymbolOrigin.VIRTUAL);
memberTypeList.add(new BTupleMember(symTable.stringType, stringVarSymbol));
BType memberType = ((BMapType) type).getConstraint();
BVarSymbol varSymbol = Symbols.createVarSymbolForTupleMember(memberType);
memberTypeList.add(new BTupleMember(memberType, varSymbol));
BTupleType newExpType = new BTupleType(null, memberTypeList);
selectType = checkExprSilent(selectExp, env, newExpType, data);
if (selectType == symTable.semanticError) {
errorTypes.add(newExpType);
continue;
}
resolvedType = getResolvedType(selectType, type, isReadonly, env);
break;
case TypeTags.STRING:
case TypeTags.XML:
case TypeTags.XML_COMMENT:
case TypeTags.XML_ELEMENT:
case TypeTags.XML_PI:
case TypeTags.XML_TEXT:
selectType = checkExprSilent(selectExp, env, type, data);
if (selectType == symTable.semanticError) {
errorTypes.add(type);
continue;
}

BType refSelectType = Types.getReferredType(selectType);
if (TypeTags.isXMLTypeTag(refSelectType.tag) || TypeTags.isStringTypeTag(refSelectType.tag)) {
selectType = type;
}

if (types.isAssignable(selectType, symTable.xmlType)) {
resolvedType = getResolvedType(new BXMLType(selectType, null), type, isReadonly, env);
} else {
resolvedType = selectType;
}
break;
case TypeTags.INTERSECTION:
type = ((BIntersectionType) type).effectiveType;
solveSelectTypeAndResolveType(queryExpr, selectExp, List.of(type), collectionType, selectTypes,
resolvedTypes, env, data, Symbols.isFlagOn(type.flags, Flags.READONLY));
return;
case TypeTags.NONE:
default:
// contextually expected type not given (i.e var).
BType inferredSelectType = symTable.semanticError;
selectType = checkExprSilent(selectExp, env, type, data);
if (type != symTable.noType) {
inferredSelectType = checkExprSilent(selectExp, env, symTable.noType, data);
}

if (selectType != symTable.semanticError && inferredSelectType != symTable.semanticError
&& inferredSelectType != symTable.noType) {
selectType = types.getTypeIntersection(
Types.IntersectionContext.typeTestIntersectionCalculationContext(),
selectType, inferredSelectType, env);
} else {
BType checkedType = symTable.semanticError;
if (selectType == symTable.semanticError) {
checkedType = checkExpr(selectExp, env, data);
}

if (checkedType != symTable.semanticError && expTypes.size() == 1) {
selectType = checkedType;
}
}

if (queryExpr.isMap) { // A query-expr that constructs a mapping must start with the map keyword.
resolvedType = symTable.mapType;
} else {
resolvedType = getNonContextualQueryType(selectType, collectionType);
}
break;
}
if (selectType != symTable.semanticError) {

if (resolvedType.tag == TypeTags.STREAM) {
queryExpr.isStream = true;
}
if (queryExpr.isMap) { // A query-expr that constructs a mapping must start with the map keyword.
resolvedType = symTable.mapType;
} else {
resolvedType = getNonContextualQueryType(selectType, collectionType);
if (resolvedType.tag == TypeTags.TABLE) {
queryExpr.isTable = true;
}
break;
}
if (selectType != symTable.semanticError) {
if (resolvedType.tag == TypeTags.STREAM) {
queryExpr.isStream = true;
possibleSelectTypes.add(selectType);
possibleResolvedTypes.add(resolvedType);
}
if (resolvedType.tag == TypeTags.TABLE) {
queryExpr.isTable = true;
}
if (!possibleSelectTypes.isEmpty() && !possibleResolvedTypes.isEmpty()) {
selectTypes.addAll(possibleSelectTypes);
resolvedTypes.addAll(possibleResolvedTypes);
return;
}
if (!errorTypes.isEmpty()) {
if (errorTypes.size() > 1) {
BType actualQueryType = silentTypeCheckExpr(queryExpr, symTable.noType, data);
if (actualQueryType != symTable.semanticError) {
types.checkType(queryExpr, actualQueryType,
BUnionType.create(null, new LinkedHashSet<>(expTypes)));
errorTypes.forEach(expType -> {
if (expType.tag == TypeTags.UNION) {
checkExpr(nodeCloner.cloneNode(selectExp), env, expType, data);
}
});
checkExpr(selectExp, env, data);
return;
}
}
selectTypes.add(selectType);
resolvedTypes.add(resolvedType);
errorTypes.forEach(expType -> {
checkExpr(selectExp, env, expType, data);
selectExp.typeChecked = false;
});
selectExp.typeChecked = true;
}
}

private BType getQueryTableType(BLangQueryExpr queryExpr, BType constraintType, BType resolvedType, SymbolEnv env) {
final BTableType tableType = new BTableType(TypeTags.TABLE, constraintType, null);
if (!queryExpr.fieldNameIdentifierList.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3648,6 +3648,14 @@ protected BType checkExprSilent(BLangExpression expr, BType expType, AnalyzerDat
return type;
}

protected BType checkExprSilent(BLangExpression expr, SymbolEnv env, BType expType, AnalyzerData data) {
SymbolEnv prevEnv = data.env;
data.env = env;
BType type = checkExprSilent(nodeCloner.cloneNode(expr), expType, data);
data.env = prevEnv;
return type;
}

private BLangRecordLiteral createRecordLiteralForErrorConstructor(BLangErrorConstructorExpr errorConstructorExpr) {
BLangRecordLiteral recordLiteral = (BLangRecordLiteral) TreeBuilder.createRecordLiteralNode();
for (NamedArgNode namedArg : errorConstructorExpr.getNamedArgs()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ public Object[] dataToTestQueryActionOrExpr() {
"testQueryActionWithQueryExpression",
"testQueryActionWithRegexpLangLibs",
"testQueryExprWithRegExpLangLibs",
"testQueryActionWithInterpolationRegexpLangLibs"
"testQueryActionWithInterpolationRegexpLangLibs",
"testQueryActionOrExpressionWithUnionRecordResultType",
"testQueryActionOrExprWithAnyOrErrResultType",
"testNestedQueryActionOrExprWithClientResourceAccessAction",
"testQueryActionWithQueryExpression"
};
}

Expand Down Expand Up @@ -131,6 +135,40 @@ public void testQueryActionOrExprSemanticsNegative() {
validateError(negativeResult, i++, "action invocation as an expression not allowed here", 315, 23);
validateError(negativeResult, i++, "incompatible types: expected 'int', found 'other'", 316, 21);
validateError(negativeResult, i++, "action invocation as an expression not allowed here", 320, 50);
validateError(negativeResult, i++, "incompatible types: expected '(T3[]|T4[])', found '(T3|T4)[]'",
339, 13);
validateError(negativeResult, i++, "incompatible types: expected '(int[]|string[])', found '(int|string)[]'",
359, 24);
validateError(negativeResult, i++, "incompatible types: expected '(int[]|string[])', found '(int|boolean)[]'",
363, 24);
validateError(negativeResult, i++,
"incompatible types: expected '(string[]|decimal[])', found '(int|float)[]'",
367, 28);
validateError(negativeResult, i++, "incompatible types: expected '(table<FooType>|table<BarType>)', " +
"found 'table<(FooType|BarType)> key(id)'", 371, 39);
validateError(negativeResult, i++, "ambiguous type '[string:Char, string]'", 376, 78);
validateError(negativeResult, i++, "incompatible types: expected 'boolean', " +
"found 'T3'", 382, 11);
validateError(negativeResult, i++, "incompatible types: expected 'T3', " +
"found 'T4'", 384, 13);
validateError(negativeResult, i++, "incompatible types: expected 'T3', " +
"found 'T2'", 386, 13);
validateError(negativeResult, i++, "missing non-defaultable required record field 't3OrT4'",
386, 17);
validateError(negativeResult, i++, "incompatible types: expected 'T3', " +
"found 'T1'", 388, 12);
validateError(negativeResult, i++, "missing non-defaultable required record field 't3s'",
388, 16);
validateError(negativeResult, i++, "incompatible types: expected '(T3[]|T4[])', " +
"found '(T4|T2|T1)[]'", 394, 13);
validateError(negativeResult, i++, "missing non-defaultable required record field 't3OrT4'",
398, 17);
validateError(negativeResult, i++, "missing non-defaultable required record field 't3s'",
400, 16);
validateError(negativeResult, i++, "ambiguous type '(Baz|Qux)'", 439, 16);
validateError(negativeResult, i++, "ambiguous type '(Baz|Qux)'", 446, 16);
validateError(negativeResult, i++, "incompatible types: expected '(boolean[]|float[])', " +
"found 'record {| string c; |}[]'", 452, 8);
Assert.assertEquals(negativeResult.getErrorCount(), i);
}

Expand Down
Loading

0 comments on commit a1902ad

Please sign in to comment.