Skip to content

Commit

Permalink
feat: error if type parameter of class is used in static context (#830)
Browse files Browse the repository at this point in the history
Closes #809

### Summary of Changes

Show an error if a type parameter of a class is used to denote
* the type of a static attribute
* the type of a parameter/result of a static method.
  • Loading branch information
lars-reimann authored Jan 23, 2024
1 parent 0e9f67a commit d5cf420
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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)) {
Expand All @@ -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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T>(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<S>(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<S>(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<S>(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«)
}
}

This file was deleted.

0 comments on commit d5cf420

Please sign in to comment.