Skip to content

Commit

Permalink
fix: check for duplicate bounds if type parameters occur as right ope…
Browse files Browse the repository at this point in the history
…rand (#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.
  • Loading branch information
lars-reimann authored Feb 11, 2024
1 parent f1420a2 commit 8776ce0
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 73 deletions.
46 changes: 42 additions & 4 deletions packages/safe-ds-lang/src/language/helpers/nodeProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
}
}
});
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,3 @@ segment test(param1: List<Int>, param2: List<Int>?) {
f(param1[0]);
f(param2?[0]);
}

class C<T1, T2> where {
T1 sub Int,
T2 super T1
}
Original file line number Diff line number Diff line change
@@ -1,94 +1,138 @@
package tests.validation.other.typeParameters.multipleBounds

class MyGlobalClass<T1, T2> where {
// $TEST$ no error "A type parameter can only have a single upper bound."
class MyGlobalClass<T1, T2, T3, T4, T5, T6, T7, T8> 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<T1, T2>() where {
// $TEST$ no error "A type parameter can only have a single upper bound."
@Pure fun myGlobalFunction<T1, T2, T3, T4, T5, T6, T7, T8>() 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«,
}

0 comments on commit 8776ce0

Please sign in to comment.