Skip to content

Commit

Permalink
feat: type checking for list & map literals (#751)
Browse files Browse the repository at this point in the history
Closes #712

### Summary of Changes

Don't allow calls that produce no or multiple results inside list & map
literals. In other cases, the existing type checking already took care
of this.
  • Loading branch information
lars-reimann authored Nov 10, 2023
1 parent 52374aa commit dc14223
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ import {
indexedAccessIndexMustHaveCorrectType,
indexedAccessReceiverMustBeListOrMap,
infixOperationOperandsMustHaveCorrectType,
listMustNotContainNamedTuples,
mapMustNotContainNamedTuples,
namedTypeMustSetAllTypeParameters,
parameterDefaultValueTypeMustMatchParameterType,
parameterMustHaveTypeHint,
Expand Down Expand Up @@ -253,14 +255,15 @@ export const registerValidationChecks = function (services: SafeDsServices) {
lambdaParametersMustNotBeAnnotated,
lambdaParameterMustNotHaveConstModifier,
],
SdsList: [listMustNotContainNamedTuples(services)],
SdsLiteralType: [
literalTypeMustHaveLiterals,
literalTypeMustNotContainListLiteral,
literalTypeMustNotContainMapLiteral,
literalTypesShouldBeUsedWithCaution,
literalTypeShouldNotHaveDuplicateLiteral(services),
],
SdsMap: [mapsShouldBeUsedWithCaution],
SdsMap: [mapMustNotContainNamedTuples(services), mapsShouldBeUsedWithCaution],
SdsMemberAccess: [
memberAccessMustBeNullSafeIfReceiverIsNullable(services),
memberAccessNullSafetyShouldBeNeeded(services),
Expand Down
45 changes: 45 additions & 0 deletions packages/safe-ds-lang/src/language/validation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
SdsCall,
SdsIndexedAccess,
SdsInfixOperation,
SdsList,
SdsMap,
SdsNamedType,
SdsParameter,
SdsPrefixOperation,
Expand All @@ -23,6 +25,7 @@ import {
} from '../generated/ast.js';
import { getTypeArguments, getTypeParameters } from '../helpers/nodeProperties.js';
import { SafeDsServices } from '../safe-ds-module.js';
import { NamedTupleType } from '../typing/model.js';

export const CODE_TYPE_CALLABLE_RECEIVER = 'type/callable-receiver';
export const CODE_TYPE_MISMATCH = 'type/mismatch';
Expand Down Expand Up @@ -191,6 +194,48 @@ export const infixOperationOperandsMustHaveCorrectType = (services: SafeDsServic
};
};

export const listMustNotContainNamedTuples = (services: SafeDsServices) => {
const typeComputer = services.types.TypeComputer;

return (node: SdsList, accept: ValidationAcceptor): void => {
for (const element of node.elements) {
const elementType = typeComputer.computeType(element);
if (elementType instanceof NamedTupleType) {
accept('error', `Cannot add a value of type '${elementType}' to a list.`, {
node: element,
code: CODE_TYPE_MISMATCH,
});
}
}
};
};

export const mapMustNotContainNamedTuples = (services: SafeDsServices) => {
const typeComputer = services.types.TypeComputer;

return (node: SdsMap, accept: ValidationAcceptor): void => {
for (const entry of node.entries) {
const keyType = typeComputer.computeType(entry.key);
if (keyType instanceof NamedTupleType) {
accept('error', `Cannot use a value of type '${keyType}' as a map key.`, {
node: entry,
property: 'key',
code: CODE_TYPE_MISMATCH,
});
}

const valueKey = typeComputer.computeType(entry.value);
if (valueKey instanceof NamedTupleType) {
accept('error', `Cannot use a value of type '${valueKey}' as a map value.`, {
node: entry,
property: 'value',
code: CODE_TYPE_MISMATCH,
});
}
}
};
};

export const parameterDefaultValueTypeMustMatchParameterType = (services: SafeDsServices) => {
const typeChecker = services.types.TypeChecker;
const typeComputer = services.types.TypeComputer;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package tests.validation.types.checking.lists

fun noResults()
fun oneResult() -> r: Int
fun twoResults() -> (r1: Int, r2: Int)

segment mySegment() {
[
// $TEST$ error "Cannot add a value of type '()' to a list."
»noResults()«,
// $TEST$ no error r"Cannot add a value of type '.*' to a list\."
»oneResult()«,
// $TEST$ error "Cannot add a value of type '(r1: Int, r2: Int)' to a list."
»twoResults()«
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package tests.validation.types.checking.maps

fun noResults()
fun oneResult() -> r: Int
fun twoResults() -> (r1: Int, r2: Int)

segment mySegment() {
{
// $TEST$ error "Cannot use a value of type '()' as a map key."
// $TEST$ error "Cannot use a value of type '()' as a map value."
»noResults()«: »noResults()«,
// $TEST$ no error r"Cannot use a value of type '.*' as a map key\."
// $TEST$ no error r"Cannot use a value of type '.*' as a map value\."
»oneResult()«: »oneResult()«,
// $TEST$ error "Cannot use a value of type '(r1: Int, r2: Int)' as a map key."
// $TEST$ error "Cannot use a value of type '(r1: Int, r2: Int)' as a map value."
»twoResults()«: »twoResults()«
};
}

0 comments on commit dc14223

Please sign in to comment.