From 8776ce07ec7f5da72ba192d85bf769350546a371 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sun, 11 Feb 2024 16:49:24 +0100 Subject: [PATCH] fix: check for duplicate bounds if type parameters occur as right operand (#882) Closes #881 ### Summary of Changes We now correctly show an error if a type parameter has multiple lower/upper bounds but occurs as the right operand in subsequent bounds. --- .../src/language/helpers/nodeProperties.ts | 46 ++++++- .../other/declarations/typeParameters.ts | 45 +++---- .../expressions/indexed access/input.sdstest | 5 - .../multiple bounds/main.sdstest | 120 ++++++++++++------ 4 files changed, 143 insertions(+), 73 deletions(-) diff --git a/packages/safe-ds-lang/src/language/helpers/nodeProperties.ts b/packages/safe-ds-lang/src/language/helpers/nodeProperties.ts index d43f2fa4d..cbbc00965 100644 --- a/packages/safe-ds-lang/src/language/helpers/nodeProperties.ts +++ b/packages/safe-ds-lang/src/language/helpers/nodeProperties.ts @@ -174,11 +174,49 @@ export namespace TypeParameter { return isSdsTypeParameter(node) && !node.variance; }; - export const getBounds = (node: SdsTypeParameter | undefined): SdsTypeParameterBound[] => { + export const getLowerBounds = (node: SdsTypeParameter | undefined): SdsTypeParameterBound[] => { + return getBounds(node).filter((it) => { + if (it.operator === 'super') { + // Type parameter is the left operand + return it.leftOperand?.ref === node; + } else if (it.operator === 'sub') { + // Type parameter is the right operand + return isSdsNamedType(it.rightOperand) && it.rightOperand.declaration?.ref === node; + } else { + /* c8 ignore next 2 */ + return false; + } + }); + }; + + export const getUpperBounds = (node: SdsTypeParameter | undefined): SdsTypeParameterBound[] => { + return getBounds(node).filter((it) => { + if (it.operator === 'sub') { + // Type parameter is the left operand + return it.leftOperand?.ref === node; + } else if (it.operator === 'super') { + // Type parameter is the right operand + return isSdsNamedType(it.rightOperand) && it.rightOperand.declaration?.ref === node; + } else { + /* c8 ignore next 2 */ + return false; + } + }); + }; + + const getBounds = (node: SdsTypeParameter | undefined): SdsTypeParameterBound[] => { const declarationContainingTypeParameter = getContainerOfType(node?.$container, isSdsDeclaration); - return getConstraints(declarationContainingTypeParameter).filter( - (it) => isSdsTypeParameterBound(it) && it.leftOperand?.ref === node, - ) as SdsTypeParameterBound[]; + return getConstraints(declarationContainingTypeParameter).filter((it) => { + if (!isSdsTypeParameterBound(it)) { + /* c8 ignore next 2 */ + return false; + } + + return ( + it.leftOperand?.ref === node || + (isSdsNamedType(it.rightOperand) && it.rightOperand.declaration?.ref === node) + ); + }) as SdsTypeParameterBound[]; }; } 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 9e747afb7..99b6380f3 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 @@ -67,34 +67,27 @@ export const typeParameterMustNotHaveMultipleBounds = (services: SafeDsServices) const typeComputer = services.types.TypeComputer; return (node: SdsTypeParameter, accept: ValidationAcceptor) => { - const bounds = TypeParameter.getBounds(node); - - let foundLowerBound = false; - let foundUpperBound = false; + TypeParameter.getLowerBounds(node).forEach((it, index) => { + if (index === 0) { + checkIfBoundIsValid(it, typeComputer, accept); + } else { + accept('error', `The type parameter '${node.name}' can only have a single lower bound.`, { + node: it, + 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; - } + TypeParameter.getUpperBounds(node).forEach((it, index) => { + if (index === 0) { + checkIfBoundIsValid(it, typeComputer, accept); + } else { + accept('error', `The type parameter '${node.name}' can only have a single upper bound.`, { + node: it, + code: CODE_TYPE_PARAMETER_MULTIPLE_BOUNDS, + }); } - } + }); }; }; diff --git a/packages/safe-ds-lang/tests/resources/generation/expressions/indexed access/input.sdstest b/packages/safe-ds-lang/tests/resources/generation/expressions/indexed access/input.sdstest index ae7cfa66d..b4ea97aa9 100644 --- a/packages/safe-ds-lang/tests/resources/generation/expressions/indexed access/input.sdstest +++ b/packages/safe-ds-lang/tests/resources/generation/expressions/indexed access/input.sdstest @@ -6,8 +6,3 @@ segment test(param1: List, param2: List?) { f(param1[0]); f(param2?[0]); } - -class C where { - T1 sub Int, - T2 super T1 -} diff --git a/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/multiple bounds/main.sdstest b/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/multiple bounds/main.sdstest index 5c039ae2a..5de2ee098 100644 --- a/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/multiple bounds/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/multiple bounds/main.sdstest @@ -1,94 +1,138 @@ package tests.validation.other.typeParameters.multipleBounds -class MyGlobalClass where { - // $TEST$ no error "A type parameter can only have a single upper bound." +class MyGlobalClass where { + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 sub Int«, - // $TEST$ error "A type parameter can only have a single upper bound." + // $TEST$ error "The type parameter 'T1' can only have a single upper bound." »T1 sub Number«, - // $TEST$ no error "A type parameter can only have a single lower bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 super Int«, - // $TEST$ error "A type parameter can only have a single lower bound." + // $TEST$ error "The type parameter 'T1' can only have a single lower bound." »T1 super Number«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T2 sub Int«, - // $TEST$ error "A type parameter can only have a single upper bound." + // $TEST$ error "The type parameter 'T2' can only have a single upper bound." »T2 sub Number«, - // $TEST$ no error "A type parameter can only have a single lower bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T2 super Int«, - // $TEST$ error "A type parameter can only have a single lower bound." + // $TEST$ error "The type parameter 'T2' can only have a single lower bound." »T2 super Number«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." + »T3 sub Int«, + // $TEST$ error "The type parameter 'T3' can only have a single upper bound." + »T4 super T3«, + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." + »T5 super Int«, + // $TEST$ error "The type parameter 'T5' can only have a single lower bound." + »T6 sub T5«, + + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." + »T7 sub Int«, + // $TEST$ error "The type parameter 'T7' can only have a single upper bound." + »T7 sub T7«, + // $TEST$ error "The type parameter 'T7' can only have a single upper bound." + »T7 super T7«, + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." + »T8 super Int«, + // $TEST$ error "The type parameter 'T8' can only have a single lower bound." + »T8 super T8«, + // $TEST$ error "The type parameter 'T8' can only have a single lower bound." + »T8 sub T8«, + + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »Unresolved sub Int«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »Unresolved sub Number«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »Unresolved super Int«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »Unresolved super Number«, } { class MyNestedClass where { - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 sub Int«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 sub Number«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 super Int«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 super Number«, } enum MyNestedEnum { MyNestedEnumVariant where { - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 sub Int«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 sub Number«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 super Int«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 super Number«, } } @Pure fun myMethod() where { - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 sub Int«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 sub Number«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 super Int«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 super Number«, } } -@Pure fun myGlobalFunction() where { - // $TEST$ no error "A type parameter can only have a single upper bound." +@Pure fun myGlobalFunction() where { + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 sub Int«, - // $TEST$ error "A type parameter can only have a single upper bound." + // $TEST$ error "The type parameter 'T1' can only have a single upper bound." »T1 sub Number«, - // $TEST$ no error "A type parameter can only have a single lower bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T1 super Int«, - // $TEST$ error "A type parameter can only have a single lower bound." + // $TEST$ error "The type parameter 'T1' can only have a single lower bound." »T1 super Number«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T2 sub Int«, - // $TEST$ error "A type parameter can only have a single upper bound." + // $TEST$ error "The type parameter 'T2' can only have a single upper bound." »T2 sub Number«, - // $TEST$ no error "A type parameter can only have a single lower bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »T2 super Int«, - // $TEST$ error "A type parameter can only have a single lower bound." + // $TEST$ error "The type parameter 'T2' can only have a single lower bound." »T2 super Number«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." + »T3 sub Int«, + // $TEST$ error "The type parameter 'T3' can only have a single upper bound." + »T4 super T3«, + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." + »T5 super Int«, + // $TEST$ error "The type parameter 'T5' can only have a single lower bound." + »T6 sub T5«, + + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." + »T7 sub Int«, + // $TEST$ error "The type parameter 'T7' can only have a single upper bound." + »T7 sub T7«, + // $TEST$ error "The type parameter 'T7' can only have a single upper bound." + »T7 super T7«, + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." + »T8 super Int«, + // $TEST$ error "The type parameter 'T8' can only have a single lower bound." + »T8 super T8«, + // $TEST$ error "The type parameter 'T8' can only have a single lower bound." + »T8 sub T8«, + + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »Unresolved sub Int«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »Unresolved sub Number«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »Unresolved super Int«, - // $TEST$ no error "A type parameter can only have a single upper bound." + // $TEST$ no error r"The type parameter .* can only have a single .* bound\." »Unresolved super Number«, }