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 71fcb80f8..0e392fbb6 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 @@ -1,13 +1,16 @@ -import { findLocalReferences, getContainerOfType, hasContainerOfType, ValidationAcceptor } from 'langium'; +import { AstNode, findLocalReferences, getContainerOfType, hasContainerOfType, ValidationAcceptor } from 'langium'; import { isSdsCallable, isSdsClass, + isSdsClassMember, isSdsDeclaration, isSdsNamedTypeDeclaration, isSdsParameterList, isSdsUnionType, + SdsClass, SdsTypeParameter, } from '../../../generated/ast.js'; +import { isStatic } from '../../../helpers/nodeProperties.js'; export const CODE_TYPE_PARAMETER_INSUFFICIENT_CONTEXT = 'type-parameter/insufficient-context'; export const CODE_TYPE_PARAMETER_USAGE = 'type-parameter/usage'; @@ -49,10 +52,7 @@ export const typeParameterMustHaveSufficientContext = (node: SdsTypeParameter, a } }; -export const typeParameterMustNotBeUsedInNestedNamedTypeDeclarations = ( - node: SdsTypeParameter, - accept: ValidationAcceptor, -) => { +export const typeParameterMustBeUsedInCorrectContext = (node: SdsTypeParameter, accept: ValidationAcceptor) => { // Only classes can have nested named type declarations const declarationWithTypeParameter = getContainerOfType(node.$container, isSdsDeclaration); if (!isSdsClass(declarationWithTypeParameter)) { @@ -61,16 +61,29 @@ export const typeParameterMustNotBeUsedInNestedNamedTypeDeclarations = ( findLocalReferences(node).forEach((it) => { const reference = it.$refNode?.astNode; - const containingNamedTypeDeclaration = getContainerOfType(reference, isSdsNamedTypeDeclaration); - if ( - reference && - containingNamedTypeDeclaration && - containingNamedTypeDeclaration !== declarationWithTypeParameter - ) { - accept('error', 'Type parameters cannot be used in nested named type declarations.', { + if (reference && !classTypeParameterIsUsedInCorrectContext(declarationWithTypeParameter, reference)) { + accept('error', 'This type parameter of a containing class cannot be used here.', { node: reference, code: CODE_TYPE_PARAMETER_USAGE, }); } }); }; + +const classTypeParameterIsUsedInCorrectContext = (classWithTypeParameter: SdsClass, reference: AstNode) => { + const containingClassMember = getContainerOfType(reference, isSdsClassMember); + + // Handle usage in constructor + if (!containingClassMember || containingClassMember === classWithTypeParameter) { + return true; + } + + // Handle usage in static context + if (isStatic(containingClassMember)) { + return false; + } + + // Handle usage inside nested enums and classes (could be an instance attribute/function) + const containingNamedTypeDeclaration = getContainerOfType(reference, isSdsNamedTypeDeclaration); + return !containingNamedTypeDeclaration || containingNamedTypeDeclaration === classWithTypeParameter; +}; diff --git a/packages/safe-ds-lang/src/language/validation/other/types/namedTypes.ts b/packages/safe-ds-lang/src/language/validation/other/types/namedTypes.ts index 153104dc1..2871ed613 100644 --- a/packages/safe-ds-lang/src/language/validation/other/types/namedTypes.ts +++ b/packages/safe-ds-lang/src/language/validation/other/types/namedTypes.ts @@ -11,14 +11,14 @@ export const CODE_NAMED_TYPE_TOO_MANY_TYPE_ARGUMENTS = 'named-type/too-many-type export const namedTypeMustNotSetTypeParameterMultipleTimes = (services: SafeDsServices) => { const nodeMapper = services.helpers.NodeMapper; - const typeArgumentToTypeParameterOrUndefined = nodeMapper.typeArgumentToTypeParameter.bind(nodeMapper); + const typeArgumentToTypeParameter = nodeMapper.typeArgumentToTypeParameter.bind(nodeMapper); return (node: SdsNamedType, accept: ValidationAcceptor): void => { const typeArguments = getTypeArguments(node.typeArgumentList); - const duplicates = duplicatesBy(typeArguments, typeArgumentToTypeParameterOrUndefined); + const duplicates = duplicatesBy(typeArguments, typeArgumentToTypeParameter); for (const duplicate of duplicates) { - const correspondingTypeParameter = typeArgumentToTypeParameterOrUndefined(duplicate)!; + const correspondingTypeParameter = typeArgumentToTypeParameter(duplicate)!; accept('error', `The type parameter '${correspondingTypeParameter.name}' is already set.`, { node: duplicate, code: CODE_NAMED_TYPE_DUPLICATE_TYPE_PARAMETER, 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 1025b880f..57b262944 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 @@ -83,8 +83,8 @@ import { } from './other/declarations/segments.js'; import { typeParameterConstraintLeftOperandMustBeOwnTypeParameter } from './other/declarations/typeParameterConstraints.js'; import { + typeParameterMustBeUsedInCorrectContext, typeParameterMustHaveSufficientContext, - typeParameterMustNotBeUsedInNestedNamedTypeDeclarations, } from './other/declarations/typeParameters.js'; import { callArgumentMustBeConstantIfParameterIsConstant, callMustNotBeRecursive } from './other/expressions/calls.js'; import { divisionDivisorMustNotBeZero } from './other/expressions/infixOperations.js'; @@ -350,10 +350,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { SdsStatement: [statementMustDoSomething(services)], SdsTemplateString: [templateStringMustHaveExpressionBetweenTwoStringParts], SdsTypeArgumentList: [typeArgumentListsShouldBeUsedWithCaution(services)], - SdsTypeParameter: [ - typeParameterMustHaveSufficientContext, - typeParameterMustNotBeUsedInNestedNamedTypeDeclarations, - ], + SdsTypeParameter: [typeParameterMustHaveSufficientContext, typeParameterMustBeUsedInCorrectContext], SdsTypeParameterConstraint: [typeParameterConstraintLeftOperandMustBeOwnTypeParameter], SdsTypeParameterList: [ typeParameterListMustNotHaveRequiredTypeParametersAfterOptionalTypeParameters, diff --git a/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/usage of class type parameters/main.sdstest b/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/usage of class type parameters/main.sdstest new file mode 100644 index 000000000..5ad6b2b4d --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/usage of class type parameters/main.sdstest @@ -0,0 +1,43 @@ +package tests.validation.other.declarations.typeParameters.usageOfClassTypeParameters + +// $TEST$ no error "This type parameter of a containing class cannot be used here." +class MyClass(p: »T«) { + // $TEST$ no error "This type parameter of a containing class cannot be used here." + attr a: »T« + + // $TEST$ error "This type parameter of a containing class cannot be used here." + static attr a: »T« + + // $TEST$ no error "This type parameter of a containing class cannot be used here." + // $TEST$ no error "This type parameter of a containing class cannot be used here." + // $TEST$ no error "This type parameter of a containing class cannot be used here." + // $TEST$ no error "This type parameter of a containing class cannot be used here." + fun f(p1: »T«, p2: »S«) -> (r1: »T«, r2: »S«) + + // $TEST$ error "This type parameter of a containing class cannot be used here." + // $TEST$ no error "This type parameter of a containing class cannot be used here." + // $TEST$ error "This type parameter of a containing class cannot be used here." + // $TEST$ no error "This type parameter of a containing class cannot be used here." + static fun f(p1: »T«, p2: »S«) -> (r1: »T«, r2: »S«) + + // $TEST$ error "This type parameter of a containing class cannot be used here." + // $TEST$ no error "This type parameter of a containing class cannot be used here." + class MyInnerClass(p1: »T«, p2: »S«) { + // $TEST$ error "This type parameter of a containing class cannot be used here." + attr a: »T« + + // $TEST$ error "This type parameter of a containing class cannot be used here." + static attr a: »T« + + // $TEST$ error "This type parameter of a containing class cannot be used here." + fun f(p: »T«) + + // $TEST$ error "This type parameter of a containing class cannot be used here." + static fun f(p: »T«) + } + + enum MyInnerEnum { + // $TEST$ error "This type parameter of a containing class cannot be used here." + MyEnumVariant(p1: »T«) + } +} diff --git a/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/usage/main.sdstest b/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/usage/main.sdstest deleted file mode 100644 index aa6772ff8..000000000 --- a/packages/safe-ds-lang/tests/resources/validation/other/declarations/type parameters/usage/main.sdstest +++ /dev/null @@ -1,28 +0,0 @@ -package tests.validation.other.declarations.typeParameters.usage - -// $TEST$ no error "Type parameters cannot be used in nested named type declarations." -class MyClass(p: »T«) { - // $TEST$ no error "Type parameters cannot be used in nested named type declarations." - attr a: »T« - - // $TEST$ no error "Type parameters cannot be used in nested named type declarations." - // $TEST$ no error "Type parameters cannot be used in nested named type declarations." - // $TEST$ no error "Type parameters cannot be used in nested named type declarations." - // $TEST$ no error "Type parameters cannot be used in nested named type declarations." - fun f(p1: »T«, p2: »S«) -> (r1: »T«, r2: »S«) - - // $TEST$ error "Type parameters cannot be used in nested named type declarations." - // $TEST$ no error "Type parameters cannot be used in nested named type declarations." - class MyInnerClass(p1: »T«, p2: »S«) { - // $TEST$ error "Type parameters cannot be used in nested named type declarations." - attr a: »T« - - // $TEST$ error "Type parameters cannot be used in nested named type declarations." - fun f(p: »T«) - } - - enum MyInnerEnum { - // $TEST$ error "Type parameters cannot be used in nested named type declarations." - MyEnumVariant(p1: »T«) - } -}