diff --git a/packages/safe-ds-lang/src/language/validation/other/declarations/typeParameters.ts b/packages/safe-ds-lang/src/language/validation/other/declarations/typeParameters.ts index dc06a0b51..9e747afb7 100644 --- a/packages/safe-ds-lang/src/language/validation/other/declarations/typeParameters.ts +++ b/packages/safe-ds-lang/src/language/validation/other/declarations/typeParameters.ts @@ -12,12 +12,16 @@ import { SdsClass, SdsDeclaration, SdsTypeParameter, + SdsTypeParameterBound, } from '../../../generated/ast.js'; import { isStatic, TypeParameter } from '../../../helpers/nodeProperties.js'; import { SafeDsServices } from '../../../safe-ds-module.js'; import { SafeDsNodeMapper } from '../../../helpers/safe-ds-node-mapper.js'; +import { NamedType, UnknownType } from '../../../typing/model.js'; +import { SafeDsTypeComputer } from '../../../typing/safe-ds-type-computer.js'; export const CODE_TYPE_PARAMETER_INSUFFICIENT_CONTEXT = 'type-parameter/insufficient-context'; +export const CODE_TYPE_PARAMETER_INVALID_BOUND = 'type-parameter/invalid-bound'; export const CODE_TYPE_PARAMETER_MULTIPLE_BOUNDS = 'type-parameter/multiple-bounds'; export const CODE_TYPE_PARAMETER_USAGE = 'type-parameter/usage'; export const CODE_TYPE_PARAMETER_VARIANCE = 'type-parameter/variance'; @@ -59,32 +63,54 @@ export const typeParameterMustHaveSufficientContext = (node: SdsTypeParameter, a } }; -export const typeParameterMustNotHaveMultipleBounds = (node: SdsTypeParameter, accept: ValidationAcceptor) => { - const bounds = TypeParameter.getBounds(node); +export const typeParameterMustNotHaveMultipleBounds = (services: SafeDsServices) => { + const typeComputer = services.types.TypeComputer; - let foundLowerBound = false; - let foundUpperBound = false; + return (node: SdsTypeParameter, accept: ValidationAcceptor) => { + const bounds = TypeParameter.getBounds(node); - for (const bound of bounds) { - if (bound.operator === 'super') { - if (foundLowerBound) { - accept('error', 'A type parameter can only have a single lower bound.', { - node: bound, - code: CODE_TYPE_PARAMETER_MULTIPLE_BOUNDS, - }); - } + let foundLowerBound = false; + let foundUpperBound = false; - foundLowerBound = true; - } else if (bound.operator === 'sub') { - if (foundUpperBound) { - accept('error', 'A type parameter can only have a single upper bound.', { - node: bound, - code: CODE_TYPE_PARAMETER_MULTIPLE_BOUNDS, - }); + for (const bound of bounds) { + if (bound.operator === 'super') { + if (foundLowerBound) { + accept('error', 'A type parameter can only have a single lower bound.', { + node: bound, + code: CODE_TYPE_PARAMETER_MULTIPLE_BOUNDS, + }); + } else { + checkIfBoundIsValid(bound, typeComputer, accept); + foundLowerBound = true; + } + } else if (bound.operator === 'sub') { + if (foundUpperBound) { + accept('error', 'A type parameter can only have a single upper bound.', { + node: bound, + code: CODE_TYPE_PARAMETER_MULTIPLE_BOUNDS, + }); + } else { + checkIfBoundIsValid(bound, typeComputer, accept); + foundUpperBound = true; + } } - - foundUpperBound = true; } + }; +}; + +const checkIfBoundIsValid = ( + node: SdsTypeParameterBound, + typeComputer: SafeDsTypeComputer, + accept: ValidationAcceptor, +) => { + const boundType = typeComputer.computeType(node.rightOperand); + + if (boundType !== UnknownType && !(boundType instanceof NamedType)) { + accept('error', 'Bounds of type parameters must be named types.', { + node, + property: 'rightOperand', + code: CODE_TYPE_PARAMETER_INVALID_BOUND, + }); } }; 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 da3929eb5..8e8eafc1e 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 @@ -352,7 +352,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { SdsTypeParameter: [ typeParameterMustHaveSufficientContext, typeParameterMustBeUsedInCorrectPosition(services), - typeParameterMustNotHaveMultipleBounds, + typeParameterMustNotHaveMultipleBounds(services), typeParameterMustOnlyBeVariantOnClass, ], SdsTypeParameterBound: [typeParameterBoundLeftOperandMustBeOwnTypeParameter], diff --git a/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/type parameter bounds/left operand must be own type parameter/main.sdstest b/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/type parameter bounds/left operand must be own type parameter/main.sdstest index bfd72bf5e..fb5461869 100644 --- a/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/type parameter bounds/left operand must be own type parameter/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/type parameter bounds/left operand must be own type parameter/main.sdstest @@ -1,4 +1,4 @@ -package tests.validation.other.declarations.constraints.typeParameterBounds.typeParameterOnContainer +package tests.validation.other.declarations.constraints.typeParameterBounds.leftOperandMustBeOwnTypeParameter annotation MyAnnotation where { // $TEST$ no error "The left operand must refer to a type parameter of the declaration with the bound." diff --git a/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/bound must be named type/main.sdstest b/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/bound must be named type/main.sdstest new file mode 100644 index 000000000..47483893a --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/bound must be named type/main.sdstest @@ -0,0 +1,102 @@ +package tests.validation.other.declarations.typeParameters.boundMustBeNamedType + +class C +enum E { + V +} + +class MyClass1 where { + // $TEST$ error "Bounds of type parameters must be named types." + T sub »() -> ()«, + // $TEST$ no error "Bounds of type parameters must be named types." + T sub »() -> ()«, + + // $TEST$ error "Bounds of type parameters must be named types." + T super »() -> ()«, + // $TEST$ no error "Bounds of type parameters must be named types." + T super »() -> ()«, +} + +class MyClass2 where { + // $TEST$ error "Bounds of type parameters must be named types." + T sub »literal<1>«, + // $TEST$ no error "Bounds of type parameters must be named types." + T sub »literal<1>«, + + // $TEST$ error "Bounds of type parameters must be named types." + T super »literal<1>«, + // $TEST$ no error "Bounds of type parameters must be named types." + T super »literal<1>«, +} + +class MyClass3 where { + // $TEST$ no error "Bounds of type parameters must be named types." + T sub »C«, + // $TEST$ no error "Bounds of type parameters must be named types." + T sub »C«, + + // $TEST$ no error "Bounds of type parameters must be named types." + T super »C«, + // $TEST$ no error "Bounds of type parameters must be named types." + T super »C«, +} + +class MyClass4 where { + // $TEST$ no error "Bounds of type parameters must be named types." + T sub »E«, + // $TEST$ no error "Bounds of type parameters must be named types." + T sub »E«, + + // $TEST$ no error "Bounds of type parameters must be named types." + T super »E«, + // $TEST$ no error "Bounds of type parameters must be named types." + T super »E«, +} + +class MyClass5 where { + // $TEST$ no error "Bounds of type parameters must be named types." + T sub »E.V«, + // $TEST$ no error "Bounds of type parameters must be named types." + T sub »E.V«, + + // $TEST$ no error "Bounds of type parameters must be named types." + T super »E.V«, + // $TEST$ no error "Bounds of type parameters must be named types." + T super »E.V«, +} + +class MyClass6 where { + // $TEST$ no error "Bounds of type parameters must be named types." + T1 sub »T2«, + // $TEST$ no error "Bounds of type parameters must be named types." + T1 sub »T2«, + + // $TEST$ no error "Bounds of type parameters must be named types." + T1 super »T2«, + // $TEST$ no error "Bounds of type parameters must be named types." + T1 super »T2«, +} + +class MyClass7 where { + // $TEST$ error "Bounds of type parameters must be named types." + T sub »union«, + // $TEST$ no error "Bounds of type parameters must be named types." + T sub »union«, + + // $TEST$ error "Bounds of type parameters must be named types." + T super »union«, + // $TEST$ no error "Bounds of type parameters must be named types." + T super »union«, +} + +class MyClass8 where { + // $TEST$ no error "Bounds of type parameters must be named types." + T sub »Unresolved«, + // $TEST$ no error "Bounds of type parameters must be named types." + T sub »Unresolved«, + + // $TEST$ no error "Bounds of type parameters must be named types." + T super »Unresolved«, + // $TEST$ no error "Bounds of type parameters must be named types." + T super »Unresolved«, +}