Skip to content

Commit

Permalink
feat: check whether type parameter bounds are named types (#878)
Browse files Browse the repository at this point in the history
Closes #876

### Summary of Changes

Add a check to ensure that the bound of a type parameter is a named
type.
  • Loading branch information
lars-reimann authored Feb 10, 2024
1 parent aa444d4 commit d8b4168
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
});
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
SdsTypeParameter: [
typeParameterMustHaveSufficientContext,
typeParameterMustBeUsedInCorrectPosition(services),
typeParameterMustNotHaveMultipleBounds,
typeParameterMustNotHaveMultipleBounds(services),
typeParameterMustOnlyBeVariantOnClass,
],
SdsTypeParameterBound: [typeParameterBoundLeftOperandMustBeOwnTypeParameter],
Expand Down
Original file line number Diff line number Diff line change
@@ -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."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package tests.validation.other.declarations.typeParameters.boundMustBeNamedType

class C
enum E {
V
}

class MyClass1<T> 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<T> 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<T> 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<T> 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<T> 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<T1, T2> 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<T> where {
// $TEST$ error "Bounds of type parameters must be named types."
T sub »union<C, E>«,
// $TEST$ no error "Bounds of type parameters must be named types."
T sub »union<C, E>«,

// $TEST$ error "Bounds of type parameters must be named types."
T super »union<C, E>«,
// $TEST$ no error "Bounds of type parameters must be named types."
T super »union<C, E>«,
}

class MyClass8<T> 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«,
}

0 comments on commit d8b4168

Please sign in to comment.