diff --git a/package.json b/package.json index 7d81ea72b..96eb9c325 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,8 @@ ], "configurationDefaults": { "[safe-ds]": { - "editor.wordSeparators": "`~!@#$%^&*()-=+[]{}\\|;:'\",.<>/?»«" + "editor.wordSeparators": "`~!@#$%^&*()-=+[]{}\\|;:'\",.<>/?»«", + "files.trimTrailingWhitespace": true } } }, diff --git a/src/language/builtins/safe-ds-annotations.ts b/src/language/builtins/safe-ds-annotations.ts index aeabecd95..d0faedf0f 100644 --- a/src/language/builtins/safe-ds-annotations.ts +++ b/src/language/builtins/safe-ds-annotations.ts @@ -4,7 +4,9 @@ import { SafeDsModuleMembers } from './safe-ds-module-members.js'; import { resourceNameToUri } from '../../helpers/resources.js'; import { URI } from 'langium'; -const CORE_ANNOTATIONS_URI = resourceNameToUri('builtins/safeds/lang/coreAnnotations.sdsstub'); +const ANNOTATION_USAGE_URI = resourceNameToUri('builtins/safeds/lang/annotationUsage.sdsstub'); +const IDE_INTEGRATION_URI = resourceNameToUri('builtins/safeds/lang/ideIntegration.sdsstub'); +const MATURITY_URI = resourceNameToUri('builtins/safeds/lang/maturity.sdsstub'); export class SafeDsAnnotations extends SafeDsModuleMembers { isDeprecated(node: SdsAnnotatedObject | undefined): boolean { @@ -12,7 +14,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { } private get Deprecated(): SdsAnnotation | undefined { - return this.getAnnotation(CORE_ANNOTATIONS_URI, 'Deprecated'); + return this.getAnnotation(MATURITY_URI, 'Deprecated'); } isExperimental(node: SdsAnnotatedObject | undefined): boolean { @@ -20,7 +22,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { } private get Experimental(): SdsAnnotation | undefined { - return this.getAnnotation(CORE_ANNOTATIONS_URI, 'Experimental'); + return this.getAnnotation(MATURITY_URI, 'Experimental'); } isExpert(node: SdsParameter | undefined): boolean { @@ -28,7 +30,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { } private get Expert(): SdsAnnotation | undefined { - return this.getAnnotation(CORE_ANNOTATIONS_URI, 'Expert'); + return this.getAnnotation(IDE_INTEGRATION_URI, 'Expert'); } isRepeatable(node: SdsAnnotation | undefined): boolean { @@ -36,7 +38,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { } private get Repeatable(): SdsAnnotation | undefined { - return this.getAnnotation(CORE_ANNOTATIONS_URI, 'Repeatable'); + return this.getAnnotation(ANNOTATION_USAGE_URI, 'Repeatable'); } private hasAnnotationCallOf(node: SdsAnnotatedObject | undefined, expected: SdsAnnotation | undefined): boolean { diff --git a/src/language/helpers/safe-ds-node-mapper.ts b/src/language/helpers/safe-ds-node-mapper.ts index 8d7937df5..e2ca1df83 100644 --- a/src/language/helpers/safe-ds-node-mapper.ts +++ b/src/language/helpers/safe-ds-node-mapper.ts @@ -7,6 +7,8 @@ import { isSdsBlock, isSdsCall, isSdsCallable, + isSdsClass, + isSdsEnumVariant, isSdsNamedType, isSdsReference, isSdsSegment, @@ -108,8 +110,17 @@ export class SafeDsNodeMapper { } } - // If the RHS is a call, the assignee gets the corresponding result + // If the RHS instantiates a class or enum variant, the first assignee gets the entire RHS const callable = this.callToCallableOrUndefined(expression); + if (isSdsClass(callable) || isSdsEnumVariant(callable)) { + if (assigneePosition === 0) { + return expression; + } else { + return undefined; + } + } + + // Otherwise, the assignee gets the result at the same position const abstractResults = abstractResultsOrEmpty(callable); return abstractResults[assigneePosition]; } diff --git a/src/language/typing/model.ts b/src/language/typing/model.ts index d82651aaf..8af216fa3 100644 --- a/src/language/typing/model.ts +++ b/src/language/typing/model.ts @@ -1,11 +1,13 @@ import { isSdsNull, + SdsAbstractResult, SdsCallable, SdsClass, SdsDeclaration, SdsEnum, SdsEnumVariant, SdsLiteral, + SdsParameter, } from '../generated/ast.js'; /* c8 ignore start */ @@ -28,8 +30,8 @@ export class CallableType extends Type { constructor( readonly sdsCallable: SdsCallable, - readonly inputType: NamedTupleType, - readonly outputType: NamedTupleType, + readonly inputType: NamedTupleType, + readonly outputType: NamedTupleType, ) { super(); } @@ -99,10 +101,10 @@ export class LiteralType extends Type { } } -export class NamedTupleType extends Type { +export class NamedTupleType extends Type { override readonly isNullable = false; - constructor(readonly entries: NamedTupleEntry[]) { + constructor(readonly entries: NamedTupleEntry[]) { super(); } @@ -125,7 +127,7 @@ export class NamedTupleType extends Type { return this; } - override copyWithNullability(_isNullable: boolean): NamedTupleType { + override copyWithNullability(_isNullable: boolean): NamedTupleType { return this; } @@ -159,8 +161,9 @@ export class NamedTupleType extends Type { } } -export class NamedTupleEntry { +export class NamedTupleEntry { constructor( + readonly sdsDeclaration: T | undefined, readonly name: string, readonly type: Type, ) {} @@ -169,8 +172,8 @@ export class NamedTupleEntry { return `${this.name}: ${this.type}`; } - equals(other: NamedTupleEntry): boolean { - return this.name === other.name && this.type.equals(other.type); + equals(other: NamedTupleEntry): boolean { + return this.sdsDeclaration === other.sdsDeclaration && this.name === other.name && this.type.equals(other.type); } } diff --git a/src/language/typing/safe-ds-type-computer.ts b/src/language/typing/safe-ds-type-computer.ts index afdfd055c..fd817feea 100644 --- a/src/language/typing/safe-ds-type-computer.ts +++ b/src/language/typing/safe-ds-type-computer.ts @@ -59,6 +59,7 @@ import { isSdsTypeProjection, isSdsUnionType, isSdsYield, + SdsAbstractResult, SdsAssignee, SdsCall, SdsCallableType, @@ -164,7 +165,7 @@ export class SafeDsTypeComputer { private computeTypeOfDeclaration(node: SdsDeclaration): Type { if (isSdsAnnotation(node)) { const parameterEntries = parametersOrEmpty(node).map( - (it) => new NamedTupleEntry(it.name, this.computeType(it.type)), + (it) => new NamedTupleEntry(it, it.name, this.computeType(it.type)), ); return new CallableType(node, new NamedTupleType(parameterEntries), new NamedTupleType([])); @@ -193,10 +194,10 @@ export class SafeDsTypeComputer { private computeTypeOfCallableWithManifestTypes(node: SdsFunction | SdsSegment | SdsCallableType): Type { const parameterEntries = parametersOrEmpty(node).map( - (it) => new NamedTupleEntry(it.name, this.computeType(it.type)), + (it) => new NamedTupleEntry(it, it.name, this.computeType(it.type)), ); const resultEntries = resultsOrEmpty(node.resultList).map( - (it) => new NamedTupleEntry(it.name, this.computeType(it.type)), + (it) => new NamedTupleEntry(it, it.name, this.computeType(it.type)), ); return new CallableType(node, new NamedTupleType(parameterEntries), new NamedTupleType(resultEntries)); @@ -278,18 +279,20 @@ export class SafeDsTypeComputer { return this.computeTypeOfCall(node); } else if (isSdsBlockLambda(node)) { const parameterEntries = parametersOrEmpty(node).map( - (it) => new NamedTupleEntry(it.name, this.computeType(it)), + (it) => new NamedTupleEntry(it, it.name, this.computeType(it)), ); const resultEntries = blockLambdaResultsOrEmpty(node).map( - (it) => new NamedTupleEntry(it.name, this.computeType(it)), + (it) => new NamedTupleEntry(it, it.name, this.computeType(it)), ); return new CallableType(node, new NamedTupleType(parameterEntries), new NamedTupleType(resultEntries)); } else if (isSdsExpressionLambda(node)) { const parameterEntries = parametersOrEmpty(node).map( - (it) => new NamedTupleEntry(it.name, this.computeType(it)), + (it) => new NamedTupleEntry(it, it.name, this.computeType(it)), ); - const resultEntries = [new NamedTupleEntry('result', this.computeType(node.result))]; + const resultEntries = [ + new NamedTupleEntry(undefined, 'result', this.computeType(node.result)), + ]; return new CallableType(node, new NamedTupleType(parameterEntries), new NamedTupleType(resultEntries)); } else if (isSdsIndexedAccess(node)) { diff --git a/src/language/validation/other/statements/assignments.ts b/src/language/validation/other/statements/assignments.ts index 88fc75a23..5d0d6e0e9 100644 --- a/src/language/validation/other/statements/assignments.ts +++ b/src/language/validation/other/statements/assignments.ts @@ -1,8 +1,10 @@ -import { isSdsPipeline, SdsAssignment, SdsYield } from '../../../generated/ast.js'; +import { isSdsCall, isSdsPipeline, SdsAssignment, SdsYield } from '../../../generated/ast.js'; import { getContainerOfType, ValidationAcceptor } from 'langium'; import { SafeDsServices } from '../../../safe-ds-module.js'; -import { assigneesOrEmpty } from '../../../helpers/nodeProperties.js'; +import { abstractResultsOrEmpty, assigneesOrEmpty } from '../../../helpers/nodeProperties.js'; +import { pluralize } from '../../../helpers/stringUtils.js'; +export const CODE_ASSIGNMENT_IMPLICITLY_IGNORED_RESULT = 'assignment/implicitly-ignored-result'; export const CODE_ASSIGMENT_NOTHING_ASSIGNED = 'assignment/nothing-assigned'; export const CODE_ASSIGMENT_YIELD_FORBIDDEN_IN_PIPELINE = 'assignment/yield-forbidden-in-pipeline'; @@ -19,6 +21,34 @@ export const assignmentAssigneeMustGetValue = } }; +export const assignmentShouldNotImplicitlyIgnoreResult = (services: SafeDsServices) => { + const nodeMapper = services.helpers.NodeMapper; + + return (node: SdsAssignment, accept: ValidationAcceptor): void => { + const expression = node.expression; + if (!isSdsCall(expression)) { + return; + } + + const assignees = assigneesOrEmpty(node); + const callable = nodeMapper.callToCallableOrUndefined(expression); + const results = abstractResultsOrEmpty(callable); + + if (results.length > assignees.length) { + const kind = pluralize(results.length - assignees.length, 'result'); + const names = results + .slice(assignees.length) + .map((result) => `'${result.name}'`) + .join(', '); + + accept('warning', `The assignment implicitly ignores the ${kind} ${names}.`, { + node, + code: CODE_ASSIGNMENT_IMPLICITLY_IGNORED_RESULT, + }); + } + }; +}; + export const yieldMustNotBeUsedInPipeline = (node: SdsYield, accept: ValidationAcceptor): void => { const containingPipeline = getContainerOfType(node, isSdsPipeline); diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index 6fde2ba94..afe6d4775 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -34,9 +34,14 @@ import { unionTypeShouldNotHaveASingularTypeArgument, } from './style.js'; import { templateStringMustHaveExpressionBetweenTwoStringParts } from './other/expressions/templateStrings.js'; -import { assignmentAssigneeMustGetValue, yieldMustNotBeUsedInPipeline } from './other/statements/assignments.js'; +import { + assignmentAssigneeMustGetValue, + assignmentShouldNotImplicitlyIgnoreResult, + yieldMustNotBeUsedInPipeline, +} from './other/statements/assignments.js'; import { attributeMustHaveTypeHint, + callReceiverMustBeCallable, namedTypeMustSetAllTypeParameters, parameterMustHaveTypeHint, resultMustHaveTypeHint, @@ -99,7 +104,11 @@ export const registerValidationChecks = function (services: SafeDsServices) { assigneeAssignedResultShouldNotBeDeprecated(services), assigneeAssignedResultShouldNotBeExperimental(services), ], - SdsAssignment: [assignmentAssigneeMustGetValue(services), assignmentShouldHaveMoreThanWildcardsAsAssignees], + SdsAssignment: [ + assignmentAssigneeMustGetValue(services), + assignmentShouldNotImplicitlyIgnoreResult(services), + assignmentShouldHaveMoreThanWildcardsAsAssignees, + ], SdsAnnotation: [ annotationMustContainUniqueNames, annotationParameterListShouldNotBeEmpty, @@ -118,7 +127,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { SdsArgumentList: [argumentListMustNotHavePositionalArgumentsAfterNamedArguments], SdsAttribute: [attributeMustHaveTypeHint], SdsBlockLambda: [blockLambdaMustContainUniqueNames], - SdsCall: [callArgumentListShouldBeNeeded(services)], + SdsCall: [callArgumentListShouldBeNeeded(services), callReceiverMustBeCallable(services)], SdsCallableType: [ callableTypeMustContainUniqueNames, callableTypeMustNotHaveOptionalParameters, diff --git a/src/language/validation/types.ts b/src/language/validation/types.ts index af0ccb477..0e49bba60 100644 --- a/src/language/validation/types.ts +++ b/src/language/validation/types.ts @@ -1,13 +1,65 @@ import { getContainerOfType, ValidationAcceptor } from 'langium'; -import { isSdsCallable, isSdsLambda, SdsAttribute, SdsNamedType, SdsParameter, SdsResult } from '../generated/ast.js'; +import { + isSdsAnnotation, + isSdsCallable, + isSdsClass, + isSdsLambda, + isSdsMemberAccess, + isSdsPipeline, + isSdsReference, + isSdsSchema, + SdsAttribute, + SdsCall, + SdsNamedType, + SdsParameter, + SdsResult, +} from '../generated/ast.js'; import { typeArgumentsOrEmpty, typeParametersOrEmpty } from '../helpers/nodeProperties.js'; import { isEmpty } from 'radash'; import { SafeDsServices } from '../safe-ds-module.js'; import { pluralize } from '../helpers/stringUtils.js'; +export const CODE_TYPE_CALLABLE_RECEIVER = 'type/callable-receiver'; export const CODE_TYPE_MISSING_TYPE_ARGUMENTS = 'type/missing-type-arguments'; export const CODE_TYPE_MISSING_TYPE_HINT = 'type/missing-type-hint'; +// ----------------------------------------------------------------------------- +// Type checking +// ----------------------------------------------------------------------------- + +export const callReceiverMustBeCallable = (services: SafeDsServices) => { + const nodeMapper = services.helpers.NodeMapper; + + return (node: SdsCall, accept: ValidationAcceptor): void => { + let receiver = node.receiver; + if (isSdsMemberAccess(receiver)) { + receiver = receiver.member; + } + + if (isSdsReference(receiver)) { + const target = receiver.target.ref; + + // We already report other errors at this position in those cases + if (!target || isSdsAnnotation(target) || isSdsPipeline(target) || isSdsSchema(target)) { + return; + } + } + + const callable = nodeMapper.callToCallableOrUndefined(node); + if (!callable) { + accept('error', 'This expression is not callable.', { + node: node.receiver, + code: CODE_TYPE_CALLABLE_RECEIVER, + }); + } else if (isSdsClass(callable) && !callable.parameterList) { + accept('error', 'Cannot instantiate a class that has no constructor.', { + node: node.receiver, + code: CODE_TYPE_CALLABLE_RECEIVER, + }); + } + }; +}; + // ----------------------------------------------------------------------------- // Missing type arguments // ----------------------------------------------------------------------------- diff --git a/src/resources/builtins/safeds/lang/annotationUsage.sdsstub b/src/resources/builtins/safeds/lang/annotationUsage.sdsstub new file mode 100644 index 000000000..cc68ec009 --- /dev/null +++ b/src/resources/builtins/safeds/lang/annotationUsage.sdsstub @@ -0,0 +1,51 @@ +package safeds.lang + +@Description("The annotation can target these declaration types. If the @Target annotation is not used any declaration type can be targeted.") +@Target(AnnotationTarget.Annotation) +annotation Target( + @Description("The valid targets.") + targets: List +) + +@Description("The declaration types that can be targeted by annotations.") +enum AnnotationTarget { + @Description("The annotation can be called on annotations.") + Annotation + + @Description("The annotation can be called on attributes.") + Attribute + + @Description("The annotation can be called on classes.") + Class + + @Description("The annotation can be called on enums.") + Enum + + @Description("The annotation can be called on enum variants.") + EnumVariant + + @Description("The annotation can be called on functions.") + Function + + @Description("The annotation can be called on modules (i.e. files).") + Module + + @Description("The annotation can be called on parameters.") + Parameter + + @Description("The annotation can be called on pipelines.") + Pipeline + + @Description("The annotation can be called on results.") + Result + + @Description("The annotation can be called on segments.") + Segment + + @Description("The annotation can be called on type parameters.") + TypeParameter +} + +@Description("The annotation can be called multiple times for the same declaration.") +@Target([AnnotationTarget.Annotation]) +annotation Repeatable diff --git a/src/resources/builtins/safeds/lang/coreAnnotations.sdsstub b/src/resources/builtins/safeds/lang/coreAnnotations.sdsstub deleted file mode 100644 index 2935009a4..000000000 --- a/src/resources/builtins/safeds/lang/coreAnnotations.sdsstub +++ /dev/null @@ -1,106 +0,0 @@ -package safeds.lang - -@Description("The annotation can target these declaration types. If the @Target annotation is not used any declaration type can be targeted.") -@Target(AnnotationTarget.Annotation) -annotation Target( - @Description("The valid targets.") - targets: List -) - -@Description("The declaration types that can be targeted by annotations.") -enum AnnotationTarget { - @Description("The annotation can be called on annotations.") - Annotation - - @Description("The annotation can be called on attributes.") - Attribute - - @Description("The annotation can be called on classes.") - Class - - @Description("The annotation can be called on enums.") - Enum - - @Description("The annotation can be called on enum variants.") - EnumVariant - - @Description("The annotation can be called on functions.") - Function - - @Description("The annotation can be called on modules (i.e. files).") - Module - - @Description("The annotation can be called on parameters.") - Parameter - - @Description("The annotation can be called on pipelines.") - Pipeline - - @Description("The annotation can be called on results.") - Result - - @Description("The annotation can be called on segments.") - Segment - - @Description("The annotation can be called on type parameters.") - TypeParameter -} - -@Description("The annotation can be called multiple times for the same declaration.") -@Target([AnnotationTarget.Annotation]) -annotation Repeatable - -@Description("The declaration should no longer be used.") -@Target([ - AnnotationTarget.Annotation, - AnnotationTarget.Attribute, - AnnotationTarget.Class, - AnnotationTarget.Enum, - AnnotationTarget.EnumVariant, - AnnotationTarget.Function, - AnnotationTarget.Parameter, - AnnotationTarget.Result, - AnnotationTarget.Segment, -]) -annotation Deprecated( - @Description("What to use instead.") - alternative: String? = null, - - @Description("Why the declaration was deprecated.") - reason: String? = null, - - @Description("When the declaration was deprecated.") - sinceVersion: String? = null, - - @Description("When the declaration will be removed.") - removalVersion: String? = null, -) - -@Description("The declaration might change without a major version bump.") -@Target([ - AnnotationTarget.Annotation, - AnnotationTarget.Attribute, - AnnotationTarget.Class, - AnnotationTarget.Enum, - AnnotationTarget.EnumVariant, - AnnotationTarget.Function, - AnnotationTarget.Parameter, - AnnotationTarget.Result, - AnnotationTarget.Segment, -]) -annotation Experimental - -@Experimental -@Description("This parameter should only be used by expert users.") -@Target([AnnotationTarget.Parameter]) -annotation Expert - -@Experimental -@Description("The function has no side effects and returns the same results for the same arguments.") -@Target([AnnotationTarget.Function]) -annotation Pure - -@Experimental -@Description("The function has no side effects.") -@Target([AnnotationTarget.Function]) -annotation NoSideEffects diff --git a/src/resources/builtins/safeds/lang/ideIntegration.sdsstub b/src/resources/builtins/safeds/lang/ideIntegration.sdsstub new file mode 100644 index 000000000..812c18e25 --- /dev/null +++ b/src/resources/builtins/safeds/lang/ideIntegration.sdsstub @@ -0,0 +1,6 @@ +package safeds.lang + +@Experimental +@Description("This parameter should only be used by expert users.") +@Target([AnnotationTarget.Parameter]) +annotation Expert diff --git a/src/resources/builtins/safeds/lang/maturity.sdsstub b/src/resources/builtins/safeds/lang/maturity.sdsstub new file mode 100644 index 000000000..2b53d06a8 --- /dev/null +++ b/src/resources/builtins/safeds/lang/maturity.sdsstub @@ -0,0 +1,41 @@ +package safeds.lang + +@Description("The declaration should no longer be used.") +@Target([ + AnnotationTarget.Annotation, + AnnotationTarget.Attribute, + AnnotationTarget.Class, + AnnotationTarget.Enum, + AnnotationTarget.EnumVariant, + AnnotationTarget.Function, + AnnotationTarget.Parameter, + AnnotationTarget.Result, + AnnotationTarget.Segment, +]) +annotation Deprecated( + @Description("What to use instead.") + alternative: String? = null, + + @Description("Why the declaration was deprecated.") + reason: String? = null, + + @Description("When the declaration was deprecated.") + sinceVersion: String? = null, + + @Description("When the declaration will be removed.") + removalVersion: String? = null, +) + +@Description("The declaration might change without a major version bump.") +@Target([ + AnnotationTarget.Annotation, + AnnotationTarget.Attribute, + AnnotationTarget.Class, + AnnotationTarget.Enum, + AnnotationTarget.EnumVariant, + AnnotationTarget.Function, + AnnotationTarget.Parameter, + AnnotationTarget.Result, + AnnotationTarget.Segment, +]) +annotation Experimental diff --git a/src/resources/builtins/safeds/lang/purity.sdsstub b/src/resources/builtins/safeds/lang/purity.sdsstub new file mode 100644 index 000000000..5ce277f46 --- /dev/null +++ b/src/resources/builtins/safeds/lang/purity.sdsstub @@ -0,0 +1,11 @@ +package safeds.lang + +@Experimental +@Description("The function has no side effects and returns the same results for the same arguments.") +@Target([AnnotationTarget.Function]) +annotation Pure + +@Experimental +@Description("The function has no side effects.") +@Target([AnnotationTarget.Function]) +annotation NoSideEffects diff --git a/tests/language/helpers/safe-ds-node-mapper/assigneeToAssignedObjectOrUndefined.test.ts b/tests/language/helpers/safe-ds-node-mapper/assigneeToAssignedObjectOrUndefined.test.ts index 08acda8a7..44fb12958 100644 --- a/tests/language/helpers/safe-ds-node-mapper/assigneeToAssignedObjectOrUndefined.test.ts +++ b/tests/language/helpers/safe-ds-node-mapper/assigneeToAssignedObjectOrUndefined.test.ts @@ -73,6 +73,32 @@ describe('SafeDsNodeMapper', () => { expect(nodeMapper.assigneeToAssignedObjectOrUndefined(placeholder)?.$type).toBe('SdsInt'); }); + it('should return the entire RHS of an assignment if it is a call of a class', async () => { + const code = ` + class C + segment mySegment() { + val a = C(); + }; + `; + + const placeholder = await getNodeOfType(services, code, isSdsPlaceholder); + expect(nodeMapper.assigneeToAssignedObjectOrUndefined(placeholder)?.$type).toBe('SdsCall'); + }); + + it('should return the entire RHS of an assignment if it is a call of an enum variant', async () => { + const code = ` + enum E { + V + } + segment mySegment() { + val a = E.V(); + }; + `; + + const placeholder = await getNodeOfType(services, code, isSdsPlaceholder); + expect(nodeMapper.assigneeToAssignedObjectOrUndefined(placeholder)?.$type).toBe('SdsCall'); + }); + it('should return the entire RHS of an assignment if it is not a call (unresolved reference)', async () => { const code = ` segment mySegment() { diff --git a/tests/resources/typing/expressions/calls/of enum variants/main.sdstest b/tests/resources/typing/expressions/calls/of enum variants/main.sdstest index 860194613..38b068be4 100644 --- a/tests/resources/typing/expressions/calls/of enum variants/main.sdstest +++ b/tests/resources/typing/expressions/calls/of enum variants/main.sdstest @@ -1,13 +1,37 @@ package tests.typing.expressions.calls.ofEnumVariants -enum E { - V +enum MyEnum { + MyEnumVariantWithoutParameters + MyEnumVariantWithParameters(p: Int) } pipeline myPipeline { - // $TEST$ serialization V - »E.V()«; + // $TEST$ serialization MyEnumVariantWithoutParameters + »MyEnum.MyEnumVariantWithoutParameters()«; + + // $TEST$ serialization MyEnumVariantWithoutParameters + val alias1 = MyEnum.MyEnumVariantWithoutParameters; + »alias1()«; + + // $TEST$ serialization $Unknown + »MyEnum.MyEnumVariantWithoutParameters()()«; + + // $TEST$ serialization $Unknown + val alias2 = MyEnum.MyEnumVariantWithoutParameters(); + »alias2()«; + + + // $TEST$ serialization MyEnumVariantWithParameters + »MyEnum.MyEnumVariantWithParameters(1)«; + + // $TEST$ serialization MyEnumVariantWithParameters + val alias3 = MyEnum.MyEnumVariantWithParameters; + »alias3(1)«; + + // $TEST$ serialization $Unknown + »MyEnum.MyEnumVariantWithParameters(1)()«; // $TEST$ serialization $Unknown - »E.V()()«; + val alias4 = MyEnum.MyEnumVariantWithParameters(1); + »alias4()«; } diff --git a/tests/resources/typing/expressions/member accesses/to enum variants/main.sdstest b/tests/resources/typing/expressions/member accesses/to enum variants/main.sdstest index 160c880f0..535f20ea9 100644 --- a/tests/resources/typing/expressions/member accesses/to enum variants/main.sdstest +++ b/tests/resources/typing/expressions/member accesses/to enum variants/main.sdstest @@ -1,10 +1,14 @@ package tests.typing.expressions.references.toEnumVariants enum MyEnum { - MyEnumVariant + MyEnumVariantWithoutParameters + MyEnumVariantWithParameters(p: Int) } pipeline myPipeline { - // $TEST$ serialization $type - »MyEnum.MyEnumVariant«; + // $TEST$ serialization $type + »MyEnum.MyEnumVariantWithoutParameters«; + + // $TEST$ serialization $type + »MyEnum.MyEnumVariantWithParameters«; } diff --git a/tests/resources/validation/other/statements/assignments/implicitly ignore result/main.sdstest b/tests/resources/validation/other/statements/assignments/implicitly ignore result/main.sdstest new file mode 100644 index 000000000..1747c2f10 --- /dev/null +++ b/tests/resources/validation/other/statements/assignments/implicitly ignore result/main.sdstest @@ -0,0 +1,52 @@ +package tests.validation.other.statements.assignments.implicitlyIgnoredResult + +fun noResults() +fun oneResult() -> first: Int +fun twoResults() -> (first: Int, second: Int) +fun threeResults() -> (first: Int, second: Int, third: Int) + +class MyClass() + +enum MyEnum { + MyEnumVariant +} + +pipeline myPipeline { + // $TEST$ warning "The assignment implicitly ignores the result 'second'." + »_ = twoResults();« + // $TEST$ warning "The assignment implicitly ignores the results 'second', 'third'." + »_ = threeResults();« + + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_ = oneResult();« + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_, _ = twoResults();« + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_, _, _ = threeResults();« + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_ = MyClass();« + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_ = MyEnum.MyEnumVariant();« + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_ = 1;« + + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_ = noResults();« + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_, _ = oneResult();« + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_, _, _ = twoResults();« + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_, _, _, _ = threeResults();« + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_, _ = MyClass();« + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_, _ = MyEnum.MyEnumVariant();« + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_, _ = 1;« + + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_ = unresolved;« + // $TEST$ no warning r"The assignment implicitly ignores the result\.*" + »_ = unresolved();« +} diff --git a/tests/resources/validation/other/statements/assignments/nothing assigned/main.sdstest b/tests/resources/validation/other/statements/assignments/nothing assigned/main.sdstest index dcd48f85f..c0c6a2be9 100644 --- a/tests/resources/validation/other/statements/assignments/nothing assigned/main.sdstest +++ b/tests/resources/validation/other/statements/assignments/nothing assigned/main.sdstest @@ -4,6 +4,12 @@ fun noResults() fun oneResult() -> first: Int fun twoResults() -> (first: Int, second: Int) +class MyClass() + +enum MyEnum { + MyEnumVariant +} + segment mySegment() -> ( r1: Any?, r2: Any?, @@ -31,6 +37,12 @@ segment mySegment() -> ( »_«, »_«, »_« = twoResults(); // $TEST$ no error "No value is assigned to this assignee." // $TEST$ error "No value is assigned to this assignee." + »_«, »_« = MyClass(); + // $TEST$ no error "No value is assigned to this assignee." + // $TEST$ error "No value is assigned to this assignee." + »_«, »_« = MyEnum.MyEnumVariant(); + // $TEST$ no error "No value is assigned to this assignee." + // $TEST$ error "No value is assigned to this assignee." »_«, »_« = 1; // $TEST$ no error "No value is assigned to this assignee." // $TEST$ error "No value is assigned to this assignee." @@ -50,13 +62,19 @@ segment mySegment() -> ( »val d«, »val e«, »val f« = twoResults(); // $TEST$ no error "No value is assigned to this assignee." // $TEST$ error "No value is assigned to this assignee." - »val g«, »val h« = 1; + »val g«, »val h« = MyClass(); // $TEST$ no error "No value is assigned to this assignee." // $TEST$ error "No value is assigned to this assignee." - »val i«, »val j« = unresolved; + »val i«, »val j« = MyEnum.MyEnumVariant(); + // $TEST$ no error "No value is assigned to this assignee." + // $TEST$ error "No value is assigned to this assignee." + »val k«, »val l« = 1; + // $TEST$ no error "No value is assigned to this assignee." // $TEST$ error "No value is assigned to this assignee." + »val m«, »val n« = unresolved; // $TEST$ error "No value is assigned to this assignee." - »val k«, »val l« = unresolved(); + // $TEST$ error "No value is assigned to this assignee." + »val o«, »val p« = unresolved(); // $TEST$ error "No value is assigned to this assignee." »yield r1« = noResults(); @@ -69,15 +87,17 @@ segment mySegment() -> ( »yield r4«, »yield r5«, »yield r6« = twoResults(); // $TEST$ no error "No value is assigned to this assignee." // $TEST$ error "No value is assigned to this assignee." - »yield r7«, »yield r8« = 1; + »yield r7«, »yield r8« = MyClass(); // $TEST$ no error "No value is assigned to this assignee." - »yield r9« = oneResult(); + // $TEST$ error "No value is assigned to this assignee." + »yield r9«, »yield r10« = MyEnum.MyEnumVariant(); // $TEST$ no error "No value is assigned to this assignee." - »yield r10« = twoResults(); + // $TEST$ error "No value is assigned to this assignee." + »yield r11«, »yield r12« = 1; // $TEST$ no error "No value is assigned to this assignee." // $TEST$ error "No value is assigned to this assignee." - »yield r11«, »yield r12« = unresolved; + »yield r13«, »yield r14« = unresolved; // $TEST$ error "No value is assigned to this assignee." // $TEST$ error "No value is assigned to this assignee." - »yield r13«, »yield r14« = unresolved(); + »yield r15«, »yield r16« = unresolved(); } diff --git a/tests/resources/validation/types/checking/called class must have constructor/main.sdstest b/tests/resources/validation/types/checking/called class must have constructor/main.sdstest new file mode 100644 index 000000000..d42292ebe --- /dev/null +++ b/tests/resources/validation/types/checking/called class must have constructor/main.sdstest @@ -0,0 +1,14 @@ +package tests.validation.types.checking.calledClassMustHaveConstructor + +class A + +class B() + +pipeline test { + + // $TEST$ error "Cannot instantiate a class that has no constructor." + val a = »A«(); + + // $TEST$ no error "Cannot instantiate a class that has no constructor." + val b = »B«(); +} diff --git a/tests/resources/validation/types/checking/receiver of call must be callable/main.sdstest b/tests/resources/validation/types/checking/receiver of call must be callable/main.sdstest new file mode 100644 index 000000000..2fe59420b --- /dev/null +++ b/tests/resources/validation/types/checking/receiver of call must be callable/main.sdstest @@ -0,0 +1,87 @@ +package tests.validation.types.checking.receiverofCallMustBeCallable + +annotation MyAnnotation + +class MyClass { + static attr myAttribute: Int + class MyClass + enum MyEnum + static fun myFunction() +} + +enum MyEnum { + MySimpleEnumVariant + MyComplexEnumVariant() +} + +fun myFunction() + +pipeline myPipeline {} + +schema MySchema {} + +segment mySegment1() {} + +segment mySegment2(a: Int, b: () -> ()) { + val c = 1; + val d = () {}; + val e = () -> 1; + + // $TEST$ no error "This expression is not callable." + »MyAnnotation«(); + // $TEST$ no error "This expression is not callable." + »MyClass«(); + // $TEST$ error "This expression is not callable." + »MyClass.myAttribute«(); + // $TEST$ no error "This expression is not callable." + »MyClass.MyClass«(); + // $TEST$ error "This expression is not callable." + »MyClass.MyEnum«(); + // $TEST$ no error "This expression is not callable." + »MyClass.myFunction«(); + // $TEST$ error "This expression is not callable." + »MyEnum«(); + // $TEST$ no error "This expression is not callable." + »MyEnum.MySimpleEnumVariant«(); + // $TEST$ no error "This expression is not callable." + »MyEnum.MyComplexEnumVariant«(); + // $TEST$ no error "This expression is not callable." + »myFunction«(); + // $TEST$ no error "This expression is not callable." + »myPipeline«(); + // $TEST$ no error "This expression is not callable." + »MySchema«(); + // $TEST$ no error "This expression is not callable." + »mySegment1«(); + // $TEST$ error "This expression is not callable." + »a«(); + // $TEST$ no error "This expression is not callable." + »b«(); + // $TEST$ error "This expression is not callable." + »c«(); + // $TEST$ no error "This expression is not callable." + »d«(); + // $TEST$ no error "This expression is not callable." + »e«(); + // $TEST$ no error "This expression is not callable." + »(d)«(); + // $TEST$ no error "This expression is not callable." + »(e)«(); + + /* + * If a scoping error for the receiver is reported we should not report another error. If the receiver is + * resolved, but it eventually points to something unresolved, we should still report another error, since the + * distance to the scoping error might be too large. + */ + + // $TEST$ no error "This expression is not callable." + »unknownGlobal«(); + // $TEST$ no error "This expression is not callable." + »MyClass.unknownMember«(); + // $TEST$ error "This expression is not callable." + val alias1 = unknownGlobal; + »alias1«(); + // $TEST$ error "This expression is not callable." + val alias2 = MyClass.unknownMember; + »alias2«(); +}