From 780f48dfce9ad2681028df1ba99b065c85a902a5 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sat, 21 Oct 2023 14:55:08 +0200 Subject: [PATCH 1/4] feat: compute type of type arguments --- package.json | 2 +- src/language/typing/safe-ds-type-computer.ts | 6 ++-- .../typing/types/type arguments/main.sdstest | 29 +++++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 tests/resources/typing/types/type arguments/main.sdstest diff --git a/package.json b/package.json index 863774ccf..477079d07 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "configurationDefaults": { "[safe-ds]": { "editor.semanticHighlighting.enabled": true, - "editor.wordSeparators": "`~!@#$%^&*()-=+[]{}\\|;:'\",.<>/?»«", + "editor.wordSeparators": "`~!@#%^&*()-=+[]{}\\|;:'\",.<>/?»«", "files.trimTrailingWhitespace": true } } diff --git a/src/language/typing/safe-ds-type-computer.ts b/src/language/typing/safe-ds-type-computer.ts index 90b432b81..f99a79acf 100644 --- a/src/language/typing/safe-ds-type-computer.ts +++ b/src/language/typing/safe-ds-type-computer.ts @@ -50,7 +50,7 @@ import { isSdsResult, isSdsSegment, isSdsTemplateString, - isSdsType, + isSdsType, isSdsTypeArgument, isSdsTypeProjection, isSdsUnionType, isSdsYield, @@ -140,7 +140,9 @@ export class SafeDsTypeComputer { return this.computeTypeOfExpression(node); } else if (isSdsType(node)) { return this.computeTypeOfType(node); - } else if (isSdsTypeProjection(node)) { + } else if (isSdsTypeArgument(node)) { + return this.computeType(node.value); + }else if (isSdsTypeProjection(node)) { return this.computeTypeOfType(node.type); } /* c8 ignore start */ else { return UnknownType; diff --git a/tests/resources/typing/types/type arguments/main.sdstest b/tests/resources/typing/types/type arguments/main.sdstest new file mode 100644 index 000000000..61b3ddefa --- /dev/null +++ b/tests/resources/typing/types/type arguments/main.sdstest @@ -0,0 +1,29 @@ +package tests.typing.types.typeArguments + +class MyClass + +enum MyEnum { + MyEnumVariant +} + +fun myFunction( + // $TEST$ serialization Int + a: MyClass<»Int«>, + // $TEST$ serialization Int + b: MyClass<»T = Int«>, + + // $TEST$ serialization String + c: MyEnum.MyEnumVariant<»T = String«>, + // $TEST$ serialization String + d: MyEnum.MyEnumVariant<»T = String«>, + + // $TEST$ serialization Boolean + e: unresolved<»Boolean«>, + // $TEST$ serialization Boolean + f: unresolved<»T = Boolean«>, + + // $TEST$ serialization $Unknown + g: MyClass<»unresolved«>, + // $TEST$ serialization $Unknown + h: MyClass<»T = unresolved«>, +) From 4580c0cc419a6e8d4661ebda662a3b0b9b2016ff Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sat, 21 Oct 2023 14:57:54 +0200 Subject: [PATCH 2/4] feat: warning if union type has duplicate entries --- .../validation/other/types/unionTypes.ts | 32 ++++++++++++++++--- src/language/validation/safe-ds-validator.ts | 11 +++++-- .../duplicate types/empty list.sdstest | 7 ++++ .../union types/duplicate types/main.sdstest | 14 ++++++++ .../must have type arguments/main.sdstest | 16 ---------- .../union types/must have types/main.sdstest | 16 ++++++++++ 6 files changed, 74 insertions(+), 22 deletions(-) create mode 100644 tests/resources/validation/other/types/union types/duplicate types/empty list.sdstest create mode 100644 tests/resources/validation/other/types/union types/duplicate types/main.sdstest delete mode 100644 tests/resources/validation/other/types/union types/must have type arguments/main.sdstest create mode 100644 tests/resources/validation/other/types/union types/must have types/main.sdstest diff --git a/src/language/validation/other/types/unionTypes.ts b/src/language/validation/other/types/unionTypes.ts index 7c2c3bd73..6ff57a5c7 100644 --- a/src/language/validation/other/types/unionTypes.ts +++ b/src/language/validation/other/types/unionTypes.ts @@ -2,15 +2,39 @@ import { SdsUnionType } from '../../../generated/ast.js'; import { ValidationAcceptor } from 'langium'; import { isEmpty } from 'radash'; import { typeArgumentsOrEmpty } from '../../../helpers/nodeProperties.js'; +import { SafeDsServices } from '../../../safe-ds-module.js'; +import { Type } from '../../../typing/model.js'; -export const CODE_UNION_TYPE_MISSING_TYPE_ARGUMENTS = 'union-type/missing-type-arguments'; +export const CODE_UNION_TYPE_DUPLICATE_TYPE = 'union-type/duplicate-type'; +export const CODE_UNION_TYPE_MISSING_TYPES = 'union-type/missing-types'; -export const unionTypeMustHaveTypeArguments = (node: SdsUnionType, accept: ValidationAcceptor): void => { +export const unionTypeMustHaveTypes = (node: SdsUnionType, accept: ValidationAcceptor): void => { if (isEmpty(typeArgumentsOrEmpty(node.typeArgumentList))) { - accept('error', 'A union type must have at least one type argument.', { + accept('error', 'A union type must have at least one type.', { node, property: 'typeArgumentList', - code: CODE_UNION_TYPE_MISSING_TYPE_ARGUMENTS, + code: CODE_UNION_TYPE_MISSING_TYPES, }); } }; + +export const unionTypeShouldNotHaveDuplicateTypes = (services: SafeDsServices) => { + const typeComputer = services.types.TypeComputer; + + return (node: SdsUnionType, accept: ValidationAcceptor): void => { + const typeArguments = typeArgumentsOrEmpty(node.typeArgumentList); + const knownTypes: Type[] = []; + + for (const typeArgument of typeArguments) { + const type = typeComputer.computeType(typeArgument); + if (knownTypes.some(it => it.equals(type))) { + accept('warning', `The type '${type.toString()}' was already listed.`, { + node: typeArgument, + code: CODE_UNION_TYPE_DUPLICATE_TYPE, + }); + } else { + knownTypes.push(type); + } + } + }; +}; diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index c752e28bb..c4619e2a8 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -53,7 +53,10 @@ import { import { moduleDeclarationsMustMatchFileKind, moduleWithDeclarationsMustStatePackage } from './other/modules.js'; import { typeParameterConstraintLeftOperandMustBeOwnTypeParameter } from './other/declarations/typeParameterConstraints.js'; import { parameterListMustNotHaveRequiredParametersAfterOptionalParameters } from './other/declarations/parameterLists.js'; -import { unionTypeMustHaveTypeArguments } from './other/types/unionTypes.js'; +import { + unionTypeMustHaveTypes, + unionTypeShouldNotHaveDuplicateTypes +} from './other/types/unionTypes.js'; import { callableTypeMustNotHaveOptionalParameters, callableTypeParameterMustNotHaveConstModifier, @@ -261,7 +264,11 @@ export const registerValidationChecks = function (services: SafeDsServices) { SdsTemplateString: [templateStringMustHaveExpressionBetweenTwoStringParts], SdsTypeParameterConstraint: [typeParameterConstraintLeftOperandMustBeOwnTypeParameter], SdsTypeParameterList: [typeParameterListShouldNotBeEmpty], - SdsUnionType: [unionTypeMustHaveTypeArguments, unionTypeShouldNotHaveASingularTypeArgument], + SdsUnionType: [ + unionTypeMustHaveTypes, + unionTypeShouldNotHaveDuplicateTypes(services), + unionTypeShouldNotHaveASingularTypeArgument, + ], SdsYield: [yieldMustNotBeUsedInPipeline], }; registry.register(checks); diff --git a/tests/resources/validation/other/types/union types/duplicate types/empty list.sdstest b/tests/resources/validation/other/types/union types/duplicate types/empty list.sdstest new file mode 100644 index 000000000..59bd07f44 --- /dev/null +++ b/tests/resources/validation/other/types/union types/duplicate types/empty list.sdstest @@ -0,0 +1,7 @@ +package tests.validation.other.types.unionTypes.duplicateTypes + +// $TEST$ no warning r"The type .* was already listed." + +segment mySegment1( + p: union<> +) {} diff --git a/tests/resources/validation/other/types/union types/duplicate types/main.sdstest b/tests/resources/validation/other/types/union types/duplicate types/main.sdstest new file mode 100644 index 000000000..f0d7f0ef7 --- /dev/null +++ b/tests/resources/validation/other/types/union types/duplicate types/main.sdstest @@ -0,0 +1,14 @@ +package tests.validation.other.types.unionTypes.duplicateTypes + +segment mySegment( + // $TEST$ no warning r"The type .* was already listed." + p: union<»Int«>, + q: union< + // $TEST$ no warning r"The type .* was already listed." + »Int«, + // $TEST$ no warning r"The type .* was already listed." + »String«, + // $TEST$ warning r"The type 'Int' was already listed." + »Int«, + >, +) {} diff --git a/tests/resources/validation/other/types/union types/must have type arguments/main.sdstest b/tests/resources/validation/other/types/union types/must have type arguments/main.sdstest deleted file mode 100644 index 17c8df750..000000000 --- a/tests/resources/validation/other/types/union types/must have type arguments/main.sdstest +++ /dev/null @@ -1,16 +0,0 @@ -package tests.validation.other.types.unionTypes.mustHaveTypeArguments - -// $TEST$ error "A union type must have at least one type argument." -segment mySegment1( - p: union»<>« -) {} - -// $TEST$ no error "A union type must have at least one type argument." -segment mySegment2( - p: union»« -) {} - -// $TEST$ no error "A union type must have at least one type argument." -segment mySegment3( - p: union»« -) {} diff --git a/tests/resources/validation/other/types/union types/must have types/main.sdstest b/tests/resources/validation/other/types/union types/must have types/main.sdstest new file mode 100644 index 000000000..54a5629ea --- /dev/null +++ b/tests/resources/validation/other/types/union types/must have types/main.sdstest @@ -0,0 +1,16 @@ +package tests.validation.other.types.unionTypes.mustHaveTypes + +// $TEST$ error "A union type must have at least one type." +segment mySegment1( + p: union»<>« +) {} + +// $TEST$ no error "A union type must have at least one type." +segment mySegment2( + p: union»« +) {} + +// $TEST$ no error "A union type must have at least one type." +segment mySegment3( + p: union»« +) {} From 36a6ae631623b546de473a97da8a33fc93498b6d Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Sat, 21 Oct 2023 15:01:30 +0200 Subject: [PATCH 3/4] feat: warning if literal type has duplicate entries --- .../validation/other/types/literalTypes.ts | 28 +++++++++++++++++-- src/language/validation/safe-ds-validator.ts | 3 +- .../duplicate literals/empty list.sdstest | 7 +++++ .../duplicate literals/main.sdstest | 14 ++++++++++ 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 tests/resources/validation/other/types/literal types/duplicate literals/empty list.sdstest create mode 100644 tests/resources/validation/other/types/literal types/duplicate literals/main.sdstest diff --git a/src/language/validation/other/types/literalTypes.ts b/src/language/validation/other/types/literalTypes.ts index a30e400e4..f26db0154 100644 --- a/src/language/validation/other/types/literalTypes.ts +++ b/src/language/validation/other/types/literalTypes.ts @@ -2,17 +2,20 @@ import { isSdsList, isSdsMap, SdsLiteralType } from '../../../generated/ast.js'; import { ValidationAcceptor } from 'langium'; import { literalsOrEmpty } from '../../../helpers/nodeProperties.js'; import { isEmpty } from 'radash'; +import { SafeDsServices } from '../../../safe-ds-module.js'; +import { EvaluatedNode } from '../../../partialEvaluation/model.js'; -export const CODE_UNION_TYPE_MISSING_LITERALS = 'union-type/missing-literals'; +export const CODE_LITERAL_TYPE_DUPLICATE_LITERAL = 'literal-type/duplicate-literal'; export const CODE_LITERAL_TYPE_LIST_LITERAL = 'literal-type/list-literal'; export const CODE_LITERAL_TYPE_MAP_LITERAL = 'literal-type/map-literal'; +export const CODE_LITERAL_TYPE_MISSING_LITERALS = 'literal-type/missing-literals'; export const literalTypeMustHaveLiterals = (node: SdsLiteralType, accept: ValidationAcceptor): void => { if (isEmpty(literalsOrEmpty(node))) { accept('error', 'A literal type must have at least one literal.', { node, property: 'literalList', - code: CODE_UNION_TYPE_MISSING_LITERALS, + code: CODE_LITERAL_TYPE_MISSING_LITERALS, }); } }; @@ -38,3 +41,24 @@ export const literalTypeMustNotContainMapLiteral = (node: SdsLiteralType, accept } } }; + +export const literalTypeShouldNotHaveDuplicateLiteral = (services: SafeDsServices) => { + const partialEvaluator = services.evaluation.PartialEvaluator; + + return (node: SdsLiteralType, accept: ValidationAcceptor): void => { + const literals = literalsOrEmpty(node); + const constants: EvaluatedNode[] = []; + + for (const literal of literals) { + const constant = partialEvaluator.evaluate(literal); + if (constants.some((it) => it.equals(constant))) { + accept('warning', `The literal ${constant.toString()} was already listed.`, { + node: literal, + code: CODE_LITERAL_TYPE_DUPLICATE_LITERAL, + }); + } else { + constants.push(constant); + } + } + }; +}; diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index c4619e2a8..118286eb5 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -126,7 +126,7 @@ import { callArgumentsMustBeConstantIfParameterIsConstant } from './other/expres import { literalTypeMustHaveLiterals, literalTypeMustNotContainListLiteral, - literalTypeMustNotContainMapLiteral, + literalTypeMustNotContainMapLiteral, literalTypeShouldNotHaveDuplicateLiteral, } from './other/types/literalTypes.js'; /** @@ -214,6 +214,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { literalTypeMustNotContainListLiteral, literalTypeMustNotContainMapLiteral, literalTypesShouldBeUsedWithCaution, + literalTypeShouldNotHaveDuplicateLiteral(services), ], SdsMap: [mapsShouldBeUsedWithCaution], SdsMemberAccess: [ diff --git a/tests/resources/validation/other/types/literal types/duplicate literals/empty list.sdstest b/tests/resources/validation/other/types/literal types/duplicate literals/empty list.sdstest new file mode 100644 index 000000000..ad8a02370 --- /dev/null +++ b/tests/resources/validation/other/types/literal types/duplicate literals/empty list.sdstest @@ -0,0 +1,7 @@ +package tests.validation.other.types.literalTypes.duplicateLiterals + +// $TEST$ no warning r"The literal .* was already listed." + +segment mySegment1( + p: literal<> +) {} diff --git a/tests/resources/validation/other/types/literal types/duplicate literals/main.sdstest b/tests/resources/validation/other/types/literal types/duplicate literals/main.sdstest new file mode 100644 index 000000000..7ecff256c --- /dev/null +++ b/tests/resources/validation/other/types/literal types/duplicate literals/main.sdstest @@ -0,0 +1,14 @@ +package tests.validation.other.types.literalTypes.duplicateLiterals + +segment mySegment( + // $TEST$ no warning r"The literal .* was already listed." + p: literal<»1«>, + q: literal< + // $TEST$ no warning r"The literal .* was already listed." + »1«, + // $TEST$ no warning r"The literal .* was already listed." + »2«, + // $TEST$ warning r"The literal 1 was already listed." + »1«, + >, +) {} From a788df447c6b4fe1ade7664b21c53caf808ee82b Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Sat, 21 Oct 2023 13:05:01 +0000 Subject: [PATCH 4/4] style: apply automated linter fixes --- src/language/typing/safe-ds-type-computer.ts | 5 +++-- src/language/validation/other/types/unionTypes.ts | 2 +- src/language/validation/safe-ds-validator.ts | 8 +++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/language/typing/safe-ds-type-computer.ts b/src/language/typing/safe-ds-type-computer.ts index f99a79acf..2e803e66d 100644 --- a/src/language/typing/safe-ds-type-computer.ts +++ b/src/language/typing/safe-ds-type-computer.ts @@ -50,7 +50,8 @@ import { isSdsResult, isSdsSegment, isSdsTemplateString, - isSdsType, isSdsTypeArgument, + isSdsType, + isSdsTypeArgument, isSdsTypeProjection, isSdsUnionType, isSdsYield, @@ -142,7 +143,7 @@ export class SafeDsTypeComputer { return this.computeTypeOfType(node); } else if (isSdsTypeArgument(node)) { return this.computeType(node.value); - }else if (isSdsTypeProjection(node)) { + } else if (isSdsTypeProjection(node)) { return this.computeTypeOfType(node.type); } /* c8 ignore start */ else { return UnknownType; diff --git a/src/language/validation/other/types/unionTypes.ts b/src/language/validation/other/types/unionTypes.ts index 6ff57a5c7..6249c24a1 100644 --- a/src/language/validation/other/types/unionTypes.ts +++ b/src/language/validation/other/types/unionTypes.ts @@ -27,7 +27,7 @@ export const unionTypeShouldNotHaveDuplicateTypes = (services: SafeDsServices) = for (const typeArgument of typeArguments) { const type = typeComputer.computeType(typeArgument); - if (knownTypes.some(it => it.equals(type))) { + if (knownTypes.some((it) => it.equals(type))) { accept('warning', `The type '${type.toString()}' was already listed.`, { node: typeArgument, code: CODE_UNION_TYPE_DUPLICATE_TYPE, diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index 118286eb5..15fd46a26 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -53,10 +53,7 @@ import { import { moduleDeclarationsMustMatchFileKind, moduleWithDeclarationsMustStatePackage } from './other/modules.js'; import { typeParameterConstraintLeftOperandMustBeOwnTypeParameter } from './other/declarations/typeParameterConstraints.js'; import { parameterListMustNotHaveRequiredParametersAfterOptionalParameters } from './other/declarations/parameterLists.js'; -import { - unionTypeMustHaveTypes, - unionTypeShouldNotHaveDuplicateTypes -} from './other/types/unionTypes.js'; +import { unionTypeMustHaveTypes, unionTypeShouldNotHaveDuplicateTypes } from './other/types/unionTypes.js'; import { callableTypeMustNotHaveOptionalParameters, callableTypeParameterMustNotHaveConstModifier, @@ -126,7 +123,8 @@ import { callArgumentsMustBeConstantIfParameterIsConstant } from './other/expres import { literalTypeMustHaveLiterals, literalTypeMustNotContainListLiteral, - literalTypeMustNotContainMapLiteral, literalTypeShouldNotHaveDuplicateLiteral, + literalTypeMustNotContainMapLiteral, + literalTypeShouldNotHaveDuplicateLiteral, } from './other/types/literalTypes.js'; /**