From 2031708e406e2a21e8ab5a48db9e7ffa57c0ab83 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 25 Feb 2024 12:08:01 +0100 Subject: [PATCH] feat: type check type arguments of named types --- .../language/validation/safe-ds-validator.ts | 2 + .../src/language/validation/types.ts | 38 +++++++++++++++++++ .../main.sdstest | 29 ++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 packages/safe-ds-lang/tests/resources/validation/types/checking/type parameter bounds for named types/main.sdstest diff --git a/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts b/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts index dcc88c864..1bd88d35f 100644 --- a/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts +++ b/packages/safe-ds-lang/src/language/validation/safe-ds-validator.ts @@ -166,6 +166,7 @@ import { listMustNotContainNamedTuples, mapMustNotContainNamedTuples, namedTypeMustSetAllTypeParameters, + namedTypeTypeArgumentsMustMatchBounds, parameterDefaultValueTypeMustMatchParameterType, parameterMustHaveTypeHint, prefixOperationOperandMustHaveCorrectType, @@ -314,6 +315,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { namedTypeMustSetAllTypeParameters(services), namedTypeTypeArgumentListShouldBeNeeded(services), namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments, + namedTypeTypeArgumentsMustMatchBounds(services), ], SdsParameter: [ constantParameterMustHaveConstantDefaultValue(services), diff --git a/packages/safe-ds-lang/src/language/validation/types.ts b/packages/safe-ds-lang/src/language/validation/types.ts index f9515e110..75ea66e0b 100644 --- a/packages/safe-ds-lang/src/language/validation/types.ts +++ b/packages/safe-ds-lang/src/language/validation/types.ts @@ -252,6 +252,44 @@ export const mapMustNotContainNamedTuples = (services: SafeDsServices) => { }; }; +export const namedTypeTypeArgumentsMustMatchBounds = (services: SafeDsServices) => { + const nodeMapper = services.helpers.NodeMapper; + const typeChecker = services.types.TypeChecker; + const typeComputer = services.types.TypeComputer; + + return (node: SdsNamedType, accept: ValidationAcceptor): void => { + const type = typeComputer.computeType(node); + if (!(type instanceof ClassType) || isEmpty(type.substitutions)) { + return; + } + + for (const typeArgument of getTypeArguments(node)) { + const typeParameter = nodeMapper.typeArgumentToTypeParameter(typeArgument); + if (!typeParameter) { + continue; + } + + const typeArgumentType = type.substitutions.get(typeParameter); + if (!typeArgumentType) { + /* c8 ignore next 2 */ + continue; + } + + const upperBound = typeComputer + .computeUpperBound(typeParameter, { stopAtTypeParameterType: true }) + .substituteTypeParameters(type.substitutions); + + if (!typeChecker.isSubtypeOf(typeArgumentType, upperBound, { strictTypeParameterTypeCheck: true })) { + accept('error', `Expected type '${upperBound}' but got '${typeArgumentType}'.`, { + node: typeArgument, + property: 'value', + code: CODE_TYPE_MISMATCH, + }); + } + } + }; +}; + export const parameterDefaultValueTypeMustMatchParameterType = (services: SafeDsServices) => { const typeChecker = services.types.TypeChecker; const typeComputer = services.types.TypeComputer; diff --git a/packages/safe-ds-lang/tests/resources/validation/types/checking/type parameter bounds for named types/main.sdstest b/packages/safe-ds-lang/tests/resources/validation/types/checking/type parameter bounds for named types/main.sdstest new file mode 100644 index 000000000..d0524371e --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/validation/types/checking/type parameter bounds for named types/main.sdstest @@ -0,0 +1,29 @@ +package tests.validation.types.checking.typeParameterBoundsForNamedTypes + +class C1 +class C2 +class C3 + +@Pure fun f( + // $TEST$ no error r"Expected type '.*' but got '.*'\." + a1: C1<»Any?«>, + // $TEST$ no error r"Expected type '.*' but got '.*'\." + a2: C1, + // $TEST$ no error r"Expected type '.*' but got '.*'\." + a3: C1, + + // $TEST$ no error r"Expected type '.*' but got '.*'\." + b1: C2<»Number«>, + // $TEST$ error "Expected type 'Number' but got 'String'." + b2: C2<»String«>, + + // $TEST$ no error r"Expected type '.*' but got '.*'\." + // $TEST$ no error r"Expected type '.*' but got '.*'\." + c1: C3<»Number«, »Number«>, + // $TEST$ no error r"Expected type '.*' but got '.*'\." + // $TEST$ no error r"Expected type '.*' but got '.*'\." + c2: C3<»Number«, »Int«>, + // $TEST$ no error r"Expected type '.*' but got '.*'\." + // $TEST$ error "Expected type 'Int' but got 'Number'." + c3: C3<»Int«, »Number«>, +)