diff --git a/src/language/validation/other/types/namedTypes.ts b/src/language/validation/other/types/namedTypes.ts new file mode 100644 index 000000000..e53e6967e --- /dev/null +++ b/src/language/validation/other/types/namedTypes.ts @@ -0,0 +1,48 @@ +import { SdsNamedType } from '../../../generated/ast.js'; +import { ValidationAcceptor } from 'langium'; +import { SafeDsServices } from '../../../safe-ds-module.js'; +import { typeArgumentsOrEmpty } from '../../../helpers/nodeProperties.js'; +import { duplicatesBy } from '../../../helpers/collectionUtils.js'; + +export const CODE_NAMED_TYPE_DUPLICATE_TYPE_PARAMETER = 'named-type/duplicate-type-parameter'; +export const CODE_NAMED_TYPE_POSITIONAL_AFTER_NAMED = 'named-type/positional-after-named'; + +export const namedTypeMustNotSetTypeParameterMultipleTimes = (services: SafeDsServices) => { + const nodeMapper = services.helpers.NodeMapper; + const typeArgumentToTypeParameterOrUndefined = nodeMapper.typeArgumentToTypeParameterOrUndefined.bind(nodeMapper); + + return (node: SdsNamedType, accept: ValidationAcceptor): void => { + const typeArguments = typeArgumentsOrEmpty(node.typeArgumentList); + const duplicates = duplicatesBy(typeArguments, typeArgumentToTypeParameterOrUndefined); + + for (const duplicate of duplicates) { + const correspondingTypeParameter = typeArgumentToTypeParameterOrUndefined(duplicate)!; + accept('error', `The type parameter '${correspondingTypeParameter.name}' is already set.`, { + node: duplicate, + code: CODE_NAMED_TYPE_DUPLICATE_TYPE_PARAMETER, + }); + } + }; +}; + +export const namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments = ( + node: SdsNamedType, + accept: ValidationAcceptor, +): void => { + const typeArgumentList = node.typeArgumentList; + if (!typeArgumentList) { + return; + } + + let foundNamed = false; + for (const typeArgument of typeArgumentList.typeArguments) { + if (typeArgument.typeParameter) { + foundNamed = true; + } else if (foundNamed) { + accept('error', 'After the first named type argument all type arguments must be named.', { + node: typeArgument, + code: CODE_NAMED_TYPE_POSITIONAL_AFTER_NAMED, + }); + } + } +}; diff --git a/src/language/validation/other/types/typeArgumentLists.ts b/src/language/validation/other/types/typeArgumentLists.ts deleted file mode 100644 index aae56f5ce..000000000 --- a/src/language/validation/other/types/typeArgumentLists.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { SdsTypeArgumentList } from '../../../generated/ast.js'; -import { ValidationAcceptor } from 'langium'; - -export const CODE_TYPE_ARGUMENT_LIST_POSITIONAL_AFTER_NAMED = 'type-argument-list/positional-after-named'; - -export const typeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments = ( - node: SdsTypeArgumentList, - accept: ValidationAcceptor, -): void => { - let foundNamed = false; - for (const typeArgument of node.typeArguments) { - if (typeArgument.typeParameter) { - foundNamed = true; - } else if (foundNamed) { - accept('error', 'After the first named type argument all type arguments must be named.', { - node: typeArgument, - code: CODE_TYPE_ARGUMENT_LIST_POSITIONAL_AFTER_NAMED, - }); - } - } -}; diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index c3b00059f..7c2b9a396 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -48,7 +48,6 @@ import { callableTypeMustNotHaveOptionalParameters, callableTypeParameterMustNotHaveConstModifier, } from './other/types/callableTypes.js'; -import { typeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments } from './other/types/typeArgumentLists.js'; import { argumentListMustNotHavePositionalArgumentsAfterNamedArguments } from './other/argumentLists.js'; import { referenceMustNotBeFunctionPointer, @@ -82,6 +81,10 @@ import { import { memberAccessMustBeNullSafeIfReceiverIsNullable } from './other/expressions/memberAccesses.js'; import { importPackageMustExist, importPackageShouldNotBeEmpty } from './other/imports.js'; import { singleUseAnnotationsMustNotBeRepeated } from './builtins/repeatable.js'; +import { + namedTypeMustNotSetTypeParameterMultipleTimes, + namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments +} from "./other/types/namedTypes.js"; /** * Register custom validation checks. @@ -140,8 +143,10 @@ export const registerValidationChecks = function (services: SafeDsServices) { SdsNamedType: [ namedTypeDeclarationShouldNotBeDeprecated(services), namedTypeDeclarationShouldNotBeExperimental(services), + namedTypeMustNotSetTypeParameterMultipleTimes(services), namedTypeMustSetAllTypeParameters(services), namedTypeTypeArgumentListShouldBeNeeded, + namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments, ], SdsParameter: [ parameterMustHaveTypeHint, @@ -165,7 +170,6 @@ export const registerValidationChecks = function (services: SafeDsServices) { segmentResultListShouldNotBeEmpty, ], SdsTemplateString: [templateStringMustHaveExpressionBetweenTwoStringParts], - SdsTypeArgumentList: [typeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments], SdsTypeParameterConstraint: [typeParameterConstraintLeftOperandMustBeOwnTypeParameter], SdsTypeParameterList: [typeParameterListShouldNotBeEmpty], SdsUnionType: [unionTypeMustHaveTypeArguments, unionTypeShouldNotHaveASingularTypeArgument], diff --git a/tests/resources/validation/other/types/type argument lists/duplicate type parameter/main.sdstest b/tests/resources/validation/other/types/type argument lists/duplicate type parameter/main.sdstest new file mode 100644 index 000000000..0cbf53fec --- /dev/null +++ b/tests/resources/validation/other/types/type argument lists/duplicate type parameter/main.sdstest @@ -0,0 +1,36 @@ +package tests.validation.other.types.typeArgumentLists.duplicateTypeParameters + +class MyClass + +fun myFunction( + f: MyClass< + // $TEST$ no error r"The type parameter '\w+' is already set\." + »Int«, + // $TEST$ error "The type parameter 'A' is already set." + »A = Int« + >, + g: MyClass< + // $TEST$ no error r"The type parameter '\w+' is already set\." + »B = Int«, + // $TEST$ error "The type parameter 'B' is already set." + »B = Int« + >, + h: MyClass< + // $TEST$ no error r"The type parameter '\w+' is already set\." + »A = Int«, + // $TEST$ no error r"The type parameter '\w+' is already set\." + »B = Int« + >, + i: MyClass< + // $TEST$ no error r"The type parameter '\w+' is already set\." + »Int«, + // $TEST$ no error r"The type parameter '\w+' is already set\." + »Int« + >, + j: MyClass< + // $TEST$ no error r"The type parameter '\w+' is already set\." + »Unresolved = Int«, + // $TEST$ no error r"The type parameter '\w+' is already set\." + »Unresolved = Int« + > +)