diff --git a/src/language/builtins/safe-ds-annotations.ts b/src/language/builtins/safe-ds-annotations.ts new file mode 100644 index 000000000..2b54b241c --- /dev/null +++ b/src/language/builtins/safe-ds-annotations.ts @@ -0,0 +1,34 @@ +import { resolveRelativePathToBuiltinFile } from './fileFinder.js'; +import { isSdsAnnotation, SdsAnnotatedObject, SdsAnnotation } from '../generated/ast.js'; +import { annotationCallsOrEmpty } from '../helpers/nodeProperties.js'; +import { SafeDsModuleMembers } from './safe-ds-module-members.js'; + +const CORE_ANNOTATIONS_URI = resolveRelativePathToBuiltinFile('safeds/lang/coreAnnotations.sdsstub'); + +export class SafeDsAnnotations extends SafeDsModuleMembers { + isDeprecated(node: SdsAnnotatedObject | undefined): boolean { + return annotationCallsOrEmpty(node).some((it) => { + const annotation = it.annotation?.ref; + return annotation === this.Deprecated; + }); + } + + isExperimental(node: SdsAnnotatedObject | undefined): boolean { + return annotationCallsOrEmpty(node).some((it) => { + const annotation = it.annotation?.ref; + return annotation === this.Experimental; + }); + } + + private get Deprecated(): SdsAnnotation | undefined { + return this.getAnnotation('Deprecated'); + } + + private get Experimental(): SdsAnnotation | undefined { + return this.getAnnotation('Experimental'); + } + + private getAnnotation(name: string): SdsAnnotation | undefined { + return this.getModuleMember(CORE_ANNOTATIONS_URI, name, isSdsAnnotation); + } +} diff --git a/src/language/builtins/safe-ds-classes.ts b/src/language/builtins/safe-ds-classes.ts new file mode 100644 index 000000000..a42551246 --- /dev/null +++ b/src/language/builtins/safe-ds-classes.ts @@ -0,0 +1,38 @@ +import { resolveRelativePathToBuiltinFile } from './fileFinder.js'; +import { isSdsClass, SdsClass } from '../generated/ast.js'; +import { SafeDsModuleMembers } from './safe-ds-module-members.js'; + +const CORE_CLASSES_URI = resolveRelativePathToBuiltinFile('safeds/lang/coreClasses.sdsstub'); + +export class SafeDsClasses extends SafeDsModuleMembers { + /* c8 ignore start */ + get Any(): SdsClass | undefined { + return this.getClass('Any'); + } + + /* c8 ignore stop */ + + get Boolean(): SdsClass | undefined { + return this.getClass('Boolean'); + } + + get Float(): SdsClass | undefined { + return this.getClass('Float'); + } + + get Int(): SdsClass | undefined { + return this.getClass('Int'); + } + + get Nothing(): SdsClass | undefined { + return this.getClass('Nothing'); + } + + get String(): SdsClass | undefined { + return this.getClass('String'); + } + + private getClass(name: string): SdsClass | undefined { + return this.getModuleMember(CORE_CLASSES_URI, name, isSdsClass); + } +} diff --git a/src/language/builtins/safe-ds-core-classes.ts b/src/language/builtins/safe-ds-core-classes.ts deleted file mode 100644 index 51cc491cb..000000000 --- a/src/language/builtins/safe-ds-core-classes.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { SafeDsServices } from '../safe-ds-module.js'; -import { resolveRelativePathToBuiltinFile } from './fileFinder.js'; -import { isSdsClass, isSdsModule, SdsClass } from '../generated/ast.js'; -import { LangiumDocuments } from 'langium'; -import { moduleMembersOrEmpty } from '../helpers/nodeProperties.js'; - -const CORE_CLASSES_URI = resolveRelativePathToBuiltinFile('safeds/lang/coreClasses.sdsstub'); - -export class SafeDsCoreClasses { - private readonly langiumDocuments: LangiumDocuments; - - constructor(services: SafeDsServices) { - this.langiumDocuments = services.shared.workspace.LangiumDocuments; - } - - private cachedAny: SdsClass | undefined; - - /* c8 ignore start */ - get Any(): SdsClass | undefined { - if (!this.cachedAny) { - this.cachedAny = this.getClass('Any'); - } - return this.cachedAny; - } - - /* c8 ignore stop */ - - private cachedBoolean: SdsClass | undefined; - - get Boolean(): SdsClass | undefined { - if (!this.cachedBoolean) { - this.cachedBoolean = this.getClass('Boolean'); - } - return this.cachedBoolean; - } - - private cachedFloat: SdsClass | undefined; - - get Float(): SdsClass | undefined { - if (!this.cachedFloat) { - this.cachedFloat = this.getClass('Float'); - } - return this.cachedFloat; - } - - private cachedInt: SdsClass | undefined; - - get Int(): SdsClass | undefined { - if (!this.cachedInt) { - this.cachedInt = this.getClass('Int'); - } - return this.cachedInt; - } - - private cachedNothing: SdsClass | undefined; - - get Nothing(): SdsClass | undefined { - if (!this.cachedNothing) { - this.cachedNothing = this.getClass('Nothing'); - } - return this.cachedNothing; - } - - private cachedString: SdsClass | undefined; - - get String(): SdsClass | undefined { - if (!this.cachedString) { - this.cachedString = this.getClass('String'); - } - return this.cachedString; - } - - private getClass(name: string): SdsClass | undefined { - if (!this.langiumDocuments.hasDocument(CORE_CLASSES_URI)) { - /* c8 ignore next 2 */ - return undefined; - } - - const document = this.langiumDocuments.getOrCreateDocument(CORE_CLASSES_URI); - const root = document.parseResult.value; - if (!isSdsModule(root)) { - /* c8 ignore next 2 */ - return undefined; - } - - const firstMatchingModuleMember = moduleMembersOrEmpty(root).find((m) => m.name === name); - if (!isSdsClass(firstMatchingModuleMember)) { - /* c8 ignore next 2 */ - return undefined; - } - - return firstMatchingModuleMember; - } -} diff --git a/src/language/builtins/safe-ds-module-members.ts b/src/language/builtins/safe-ds-module-members.ts new file mode 100644 index 000000000..743d6e5d0 --- /dev/null +++ b/src/language/builtins/safe-ds-module-members.ts @@ -0,0 +1,43 @@ +import { SafeDsServices } from '../safe-ds-module.js'; +import { isSdsModule, SdsModuleMember } from '../generated/ast.js'; +import { LangiumDocuments, URI, WorkspaceCache } from 'langium'; +import { moduleMembersOrEmpty } from '../helpers/nodeProperties.js'; + +export abstract class SafeDsModuleMembers { + private readonly langiumDocuments: LangiumDocuments; + private readonly cache: WorkspaceCache; + + constructor(services: SafeDsServices) { + this.langiumDocuments = services.shared.workspace.LangiumDocuments; + this.cache = new WorkspaceCache(services.shared); + } + + protected getModuleMember(uri: URI, name: string, predicate: (node: unknown) => node is T): T | undefined { + const key = `${uri.toString()}#${name}`; + + if (this.cache.has(key)) { + return this.cache.get(key); + } + + if (!this.langiumDocuments.hasDocument(uri)) { + /* c8 ignore next 2 */ + return undefined; + } + + const document = this.langiumDocuments.getOrCreateDocument(uri); + const root = document.parseResult.value; + if (!isSdsModule(root)) { + /* c8 ignore next 2 */ + return undefined; + } + + const firstMatchingModuleMember = moduleMembersOrEmpty(root).find((m) => m.name === name); + if (!predicate(firstMatchingModuleMember)) { + /* c8 ignore next 2 */ + return undefined; + } + + this.cache.set(key, firstMatchingModuleMember); + return firstMatchingModuleMember; + } +} diff --git a/src/language/grammar/safe-ds.langium b/src/language/grammar/safe-ds.langium index 6c7fa01e9..a97cfb619 100644 --- a/src/language/grammar/safe-ds.langium +++ b/src/language/grammar/safe-ds.langium @@ -334,7 +334,7 @@ fragment SdsSegmentFragment: interface SdsAnnotationCallList extends SdsAnnotatedObject {} interface SdsAnnotationCall extends SdsAbstractCall { - annotation?: @SdsAnnotation + annotation: @SdsAnnotation } SdsAnnotationCall returns SdsAnnotationCall: diff --git a/src/language/helpers/nodeProperties.ts b/src/language/helpers/nodeProperties.ts index 9770f2ca7..f7a98aca5 100644 --- a/src/language/helpers/nodeProperties.ts +++ b/src/language/helpers/nodeProperties.ts @@ -1,7 +1,9 @@ import { isSdsAssignment, isSdsAttribute, + isSdsBlockLambda, isSdsBlockLambdaResult, + isSdsCallableType, isSdsClass, isSdsDeclaration, isSdsEnum, @@ -13,6 +15,7 @@ import { isSdsSegment, isSdsTypeParameterList, SdsAbstractCall, + SdsAbstractResult, SdsAnnotatedObject, SdsAnnotationCall, SdsArgument, @@ -84,6 +87,24 @@ export const isStatic = (node: SdsClassMember): boolean => { // Accessors for list elements // ------------------------------------------------------------------------------------------------- +export const abstractResultsOrEmpty = (node: SdsCallable | undefined): SdsAbstractResult[] => { + if (!node) { + return []; + } + + if (isSdsBlockLambda(node)) { + return blockLambdaResultsOrEmpty(node); + } else if (isSdsCallableType(node)) { + return resultsOrEmpty(node.resultList); + } else if (isSdsFunction(node)) { + return resultsOrEmpty(node.resultList); + } else if (isSdsSegment(node)) { + return resultsOrEmpty(node.resultList); + } /* c8 ignore start */ else { + return []; + } /* c8 ignore stop */ +}; + export const annotationCallsOrEmpty = (node: SdsAnnotatedObject | undefined): SdsAnnotationCall[] => { if (!node) { /* c8 ignore next 2 */ diff --git a/src/language/helpers/safe-ds-node-mapper.ts b/src/language/helpers/safe-ds-node-mapper.ts index e4d68a9b5..f3dcbc21b 100644 --- a/src/language/helpers/safe-ds-node-mapper.ts +++ b/src/language/helpers/safe-ds-node-mapper.ts @@ -3,6 +3,7 @@ import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js'; import { isSdsAbstractCall, isSdsAnnotationCall, + isSdsAssignment, isSdsBlock, isSdsCall, isSdsCallable, @@ -12,8 +13,11 @@ import { isSdsType, isSdsYield, SdsAbstractCall, + SdsAbstractResult, SdsArgument, + SdsAssignee, SdsCallable, + SdsExpression, SdsParameter, SdsPlaceholder, SdsReference, @@ -25,6 +29,7 @@ import { import { CallableType, StaticType } from '../typing/model.js'; import { findLocalReferences, getContainerOfType } from 'langium'; import { + abstractResultsOrEmpty, argumentsOrEmpty, isNamedArgument, isNamedTypeArgument, @@ -81,6 +86,40 @@ export class SafeDsNodeMapper { return undefined; } + /** + * Returns the result, block lambda result, or expression that is assigned to the given assignee. If nothing is + * assigned, `undefined` is returned. + */ + assigneeToAssignedObjectOrUndefined(node: SdsAssignee | undefined): SdsAbstractResult | SdsExpression | undefined { + if (!node) { + return undefined; + } + + const containingAssignment = getContainerOfType(node, isSdsAssignment); + /* c8 ignore start */ + if (!containingAssignment) { + return undefined; + } + /* c8 ignore stop */ + + const assigneePosition = node.$containerIndex ?? -1; + const expression = containingAssignment.expression; + + // If the RHS is not a call, the first assignee gets the entire RHS + if (!isSdsCall(expression)) { + if (assigneePosition === 0) { + return expression; + } else { + return undefined; + } + } + + // If the RHS is a call, the assignee gets the corresponding result + const callable = this.callToCallableOrUndefined(expression); + const abstractResults = abstractResultsOrEmpty(callable); + return abstractResults[assigneePosition]; + } + /** * Returns the callable that is called by the given call. If no callable can be found, returns undefined. */ diff --git a/src/language/safe-ds-module.ts b/src/language/safe-ds-module.ts index 4d55aace1..d8cd13302 100644 --- a/src/language/safe-ds-module.ts +++ b/src/language/safe-ds-module.ts @@ -17,16 +17,18 @@ import { SafeDsScopeComputation } from './scoping/safe-ds-scope-computation.js'; import { SafeDsScopeProvider } from './scoping/safe-ds-scope-provider.js'; import { SafeDsValueConverter } from './grammar/safe-ds-value-converter.js'; import { SafeDsTypeComputer } from './typing/safe-ds-type-computer.js'; -import { SafeDsCoreClasses } from './builtins/safe-ds-core-classes.js'; +import { SafeDsClasses } from './builtins/safe-ds-classes.js'; import { SafeDsPackageManager } from './workspace/safe-ds-package-manager.js'; import { SafeDsNodeMapper } from './helpers/safe-ds-node-mapper.js'; +import { SafeDsAnnotations } from './builtins/safe-ds-annotations.js'; /** * Declaration of custom services - add your own service classes here. */ export type SafeDsAddedServices = { builtins: { - CoreClasses: SafeDsCoreClasses; + Annotations: SafeDsAnnotations; + Classes: SafeDsClasses; }; helpers: { NodeMapper: SafeDsNodeMapper; @@ -52,7 +54,8 @@ export type SafeDsServices = LangiumServices & SafeDsAddedServices; */ export const SafeDsModule: Module = { builtins: { - CoreClasses: (services) => new SafeDsCoreClasses(services), + Annotations: (services) => new SafeDsAnnotations(services), + Classes: (services) => new SafeDsClasses(services), }, helpers: { NodeMapper: (services) => new SafeDsNodeMapper(services), diff --git a/src/language/scoping/safe-ds-scope-provider.ts b/src/language/scoping/safe-ds-scope-provider.ts index c94bc2ebf..eb45363b0 100644 --- a/src/language/scoping/safe-ds-scope-provider.ts +++ b/src/language/scoping/safe-ds-scope-provider.ts @@ -14,6 +14,7 @@ import { isSdsArgument, isSdsAssignment, isSdsBlock, + isSdsCall, isSdsCallable, isSdsClass, isSdsEnum, @@ -49,6 +50,7 @@ import { } from '../generated/ast.js'; import { isContainedIn } from '../helpers/astUtils.js'; import { + abstractResultsOrEmpty, assigneesOrEmpty, classMembersOrEmpty, enumVariantsOrEmpty, @@ -185,35 +187,41 @@ export class SafeDsScopeProvider extends DefaultScopeProvider { return this.createScopeForNodes(enumVariantsOrEmpty(declaration)); } - // // Call results - // var resultScope = IScope.NULLSCOPE - // if (receiver is SdsCall) { - // val results = receiver.resultsOrNull() - // when { - // results == null -> return IScope.NULLSCOPE - // results.size > 1 -> return Scopes.scopeFor(results) - // results.size == 1 -> resultScope = Scopes.scopeFor(results) - // } - // } - // - // // Members - // val type = (receiver.type() as? NamedType) ?: return resultScope + // Call results + let resultScope = EMPTY_SCOPE; + if (isSdsCall(node.receiver)) { + const callable = this.nodeMapper.callToCallableOrUndefined(node.receiver); + const results = abstractResultsOrEmpty(callable); + + if (results.length === 0) { + return EMPTY_SCOPE; + } else if (results.length > 1) { + return this.createScopeForNodes(results); + } else { + // If there is only one result, it can be accessed by name but members of the result with the same name + // take precedence. + resultScope = this.createScopeForNodes(results); + } + } + + // // Members + // val type = (receiver.type() as? NamedType) ?: return resultScope // - // return when { - // type.isNullable && !context.isNullSafe -> resultScope - // type is ClassType -> { - // val members = type.sdsClass.classMembersOrEmpty().filter { !it.isStatic() } - // val superTypeMembers = type.sdsClass.superClassMembers() - // .filter { !it.isStatic() } - // .toList() + // return when { + // type.isNullable && !context.isNullSafe -> resultScope + // type is ClassType -> { + // val members = type.sdsClass.classMembersOrEmpty().filter { !it.isStatic() } + // val superTypeMembers = type.sdsClass.superClassMembers() + // .filter { !it.isStatic() } + // .toList() // - // Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers, resultScope)) - // } - // type is EnumVariantType -> Scopes.scopeFor(type.sdsEnumVariant.parametersOrEmpty()) - // else -> resultScope + // Scopes.scopeFor(members, Scopes.scopeFor(superTypeMembers, resultScope)) // } + // type is EnumVariantType -> Scopes.scopeFor(type.sdsEnumVariant.parametersOrEmpty()) + // else -> resultScope + // } - return EMPTY_SCOPE; + return resultScope; } /** diff --git a/src/language/typing/model.ts b/src/language/typing/model.ts index 22c10882c..16e190d7d 100644 --- a/src/language/typing/model.ts +++ b/src/language/typing/model.ts @@ -106,6 +106,10 @@ export class NamedTupleType extends Type { return this.entries[position]?.type; } + get length(): number { + return this.entries.length; + } + override copyWithNullability(_isNullable: boolean): NamedTupleType { return this; } diff --git a/src/language/typing/safe-ds-type-computer.ts b/src/language/typing/safe-ds-type-computer.ts index 996be7aaa..ebdf168cd 100644 --- a/src/language/typing/safe-ds-type-computer.ts +++ b/src/language/typing/safe-ds-type-computer.ts @@ -1,6 +1,6 @@ import { AstNode, AstNodeLocator, getContainerOfType, getDocument, WorkspaceCache } from 'langium'; import { SafeDsServices } from '../safe-ds-module.js'; -import { SafeDsCoreClasses } from '../builtins/safe-ds-core-classes.js'; +import { SafeDsClasses } from '../builtins/safe-ds-classes.js'; import { CallableType, ClassType, @@ -83,14 +83,14 @@ import { export class SafeDsTypeComputer { private readonly astNodeLocator: AstNodeLocator; - private readonly coreClasses: SafeDsCoreClasses; + private readonly coreClasses: SafeDsClasses; private readonly nodeMapper: SafeDsNodeMapper; readonly typeCache: WorkspaceCache; constructor(readonly services: SafeDsServices) { this.astNodeLocator = services.workspace.AstNodeLocator; - this.coreClasses = services.builtins.CoreClasses; + this.coreClasses = services.builtins.Classes; this.nodeMapper = services.helpers.NodeMapper; this.typeCache = new WorkspaceCache(services.shared); @@ -377,7 +377,7 @@ export class SafeDsTypeComputer { const leftOperandType = this.computeType(node.leftOperand); const rightOperandType = this.computeType(node.rightOperand); - if (leftOperandType === this.Int() && rightOperandType === this.Int()) { + if (leftOperandType.equals(this.Int()) && rightOperandType.equals(this.Int())) { return this.Int(); } else { return this.Float(); @@ -398,7 +398,7 @@ export class SafeDsTypeComputer { private computeTypeOfArithmeticPrefixOperation(node: SdsPrefixOperation): Type { const leftOperandType = this.computeType(node.operand); - if (leftOperandType === this.Int()) { + if (leftOperandType.equals(this.Int())) { return this.Int(); } else { return this.Float(); @@ -501,49 +501,24 @@ export class SafeDsTypeComputer { // Builtin types // ----------------------------------------------------------------------------------------------------------------- - private cachedBoolean: Type = UnknownType; - private Boolean(): Type { - if (this.cachedBoolean === UnknownType) { - this.cachedBoolean = this.createCoreType(this.coreClasses.Boolean); - } - return this.cachedBoolean; + return this.createCoreType(this.coreClasses.Boolean); } - private cachedFloat: Type = UnknownType; - private Float(): Type { - if (this.cachedFloat === UnknownType) { - this.cachedFloat = this.createCoreType(this.coreClasses.Float); - } - return this.cachedFloat; + return this.createCoreType(this.coreClasses.Float); } - private cachedInt: Type = UnknownType; - private Int(): Type { - if (this.cachedInt === UnknownType) { - this.cachedInt = this.createCoreType(this.coreClasses.Int); - } - return this.cachedInt; + return this.createCoreType(this.coreClasses.Int); } - private cachedNothingOrNull: Type = UnknownType; - private NothingOrNull(): Type { - if (this.cachedNothingOrNull === UnknownType) { - this.cachedNothingOrNull = this.createCoreType(this.coreClasses.Nothing, true); - } - return this.cachedNothingOrNull; + return this.createCoreType(this.coreClasses.Nothing, true); } - private cachedString: Type = UnknownType; - private String(): Type { - if (this.cachedString === UnknownType) { - this.cachedString = this.createCoreType(this.coreClasses.String); - } - return this.cachedString; + return this.createCoreType(this.coreClasses.String); } private createCoreType(coreClass: SdsClass | undefined, isNullable: boolean = false): Type { diff --git a/src/language/validation/builtins/deprecated.ts b/src/language/validation/builtins/deprecated.ts new file mode 100644 index 000000000..9b2335602 --- /dev/null +++ b/src/language/validation/builtins/deprecated.ts @@ -0,0 +1,97 @@ +import { ValidationAcceptor } from 'langium'; +import { + isSdsParameter, + isSdsResult, + isSdsWildcard, + SdsAnnotationCall, + SdsArgument, + SdsAssignee, + SdsNamedType, + SdsReference, +} from '../../generated/ast.js'; +import { SafeDsServices } from '../../safe-ds-module.js'; + +export const CODE_DEPRECATED_ASSIGNED_RESULT = 'deprecated/assigned-result'; +export const CODE_DEPRECATED_CALLED_ANNOTATION = 'deprecated/called-annotation'; +export const CODE_DEPRECATED_CORRESPONDING_PARAMETER = 'deprecated/corresponding-parameter'; +export const CODE_DEPRECATED_REFERENCED_DECLARATION = 'deprecated/referenced-declaration'; + +export const assigneeAssignedResultShouldNotBeDeprecated = + (services: SafeDsServices) => (node: SdsAssignee, accept: ValidationAcceptor) => { + if (isSdsWildcard(node)) { + return; + } + + const assignedObject = services.helpers.NodeMapper.assigneeToAssignedObjectOrUndefined(node); + if (!isSdsResult(assignedObject)) { + return; + } + + if (services.builtins.Annotations.isDeprecated(assignedObject)) { + accept('warning', `The assigned result '${assignedObject.name}' is deprecated.`, { + node, + code: CODE_DEPRECATED_ASSIGNED_RESULT, + }); + } + }; + +export const annotationCallAnnotationShouldNotBeDeprecated = + (services: SafeDsServices) => (node: SdsAnnotationCall, accept: ValidationAcceptor) => { + const annotation = node.annotation.ref; + if (!annotation) { + return; + } + + if (services.builtins.Annotations.isDeprecated(annotation)) { + accept('warning', `The called annotation '${annotation.name}' is deprecated.`, { + node, + property: 'annotation', + code: CODE_DEPRECATED_CALLED_ANNOTATION, + }); + } + }; + +export const argumentCorrespondingParameterShouldNotBeDeprecated = + (services: SafeDsServices) => (node: SdsArgument, accept: ValidationAcceptor) => { + const parameter = services.helpers.NodeMapper.argumentToParameterOrUndefined(node); + if (!parameter) { + return; + } + + if (services.builtins.Annotations.isDeprecated(parameter)) { + accept('warning', `The corresponding parameter '${parameter.name}' is deprecated.`, { + node, + code: CODE_DEPRECATED_CORRESPONDING_PARAMETER, + }); + } + }; + +export const namedTypeDeclarationShouldNotBeDeprecated = + (services: SafeDsServices) => (node: SdsNamedType, accept: ValidationAcceptor) => { + const declaration = node.declaration.ref; + if (!declaration) { + return; + } + + if (services.builtins.Annotations.isDeprecated(declaration)) { + accept('warning', `The referenced declaration '${declaration.name}' is deprecated.`, { + node, + code: CODE_DEPRECATED_REFERENCED_DECLARATION, + }); + } + }; + +export const referenceTargetShouldNotBeDeprecated = + (services: SafeDsServices) => (node: SdsReference, accept: ValidationAcceptor) => { + const target = node.target.ref; + if (!target || isSdsParameter(target)) { + return; + } + + if (services.builtins.Annotations.isDeprecated(target)) { + accept('warning', `The referenced declaration '${target.name}' is deprecated.`, { + node, + code: CODE_DEPRECATED_REFERENCED_DECLARATION, + }); + } + }; diff --git a/src/language/validation/builtins/experimental.ts b/src/language/validation/builtins/experimental.ts new file mode 100644 index 000000000..09b28618d --- /dev/null +++ b/src/language/validation/builtins/experimental.ts @@ -0,0 +1,97 @@ +import { ValidationAcceptor } from 'langium'; +import { + isSdsParameter, + isSdsResult, + isSdsWildcard, + SdsAnnotationCall, + SdsArgument, + SdsAssignee, + SdsNamedType, + SdsReference, +} from '../../generated/ast.js'; +import { SafeDsServices } from '../../safe-ds-module.js'; + +export const CODE_EXPERIMENTAL_ASSIGNED_RESULT = 'experimental/assigned-result'; +export const CODE_EXPERIMENTAL_CALLED_ANNOTATION = 'experimental/called-annotation'; +export const CODE_EXPERIMENTAL_CORRESPONDING_PARAMETER = 'experimental/corresponding-parameter'; +export const CODE_EXPERIMENTAL_REFERENCED_DECLARATION = 'experimental/referenced-declaration'; + +export const assigneeAssignedResultShouldNotBeExperimental = + (services: SafeDsServices) => (node: SdsAssignee, accept: ValidationAcceptor) => { + if (isSdsWildcard(node)) { + return; + } + + const assignedObject = services.helpers.NodeMapper.assigneeToAssignedObjectOrUndefined(node); + if (!isSdsResult(assignedObject)) { + return; + } + + if (services.builtins.Annotations.isExperimental(assignedObject)) { + accept('warning', `The assigned result '${assignedObject.name}' is experimental.`, { + node, + code: CODE_EXPERIMENTAL_ASSIGNED_RESULT, + }); + } + }; + +export const annotationCallAnnotationShouldNotBeExperimental = + (services: SafeDsServices) => (node: SdsAnnotationCall, accept: ValidationAcceptor) => { + const annotation = node.annotation.ref; + if (!annotation) { + return; + } + + if (services.builtins.Annotations.isExperimental(annotation)) { + accept('warning', `The called annotation '${annotation.name}' is experimental.`, { + node, + property: 'annotation', + code: CODE_EXPERIMENTAL_CALLED_ANNOTATION, + }); + } + }; + +export const argumentCorrespondingParameterShouldNotBeExperimental = + (services: SafeDsServices) => (node: SdsArgument, accept: ValidationAcceptor) => { + const parameter = services.helpers.NodeMapper.argumentToParameterOrUndefined(node); + if (!parameter) { + return; + } + + if (services.builtins.Annotations.isExperimental(parameter)) { + accept('warning', `The corresponding parameter '${parameter.name}' is experimental.`, { + node, + code: CODE_EXPERIMENTAL_CORRESPONDING_PARAMETER, + }); + } + }; + +export const namedTypeDeclarationShouldNotBeExperimental = + (services: SafeDsServices) => (node: SdsNamedType, accept: ValidationAcceptor) => { + const declaration = node.declaration.ref; + if (!declaration) { + return; + } + + if (services.builtins.Annotations.isExperimental(declaration)) { + accept('warning', `The referenced declaration '${declaration.name}' is experimental.`, { + node, + code: CODE_EXPERIMENTAL_REFERENCED_DECLARATION, + }); + } + }; + +export const referenceTargetShouldNotExperimental = + (services: SafeDsServices) => (node: SdsReference, accept: ValidationAcceptor) => { + const target = node.target.ref; + if (!target || isSdsParameter(target)) { + return; + } + + if (services.builtins.Annotations.isExperimental(target)) { + accept('warning', `The referenced declaration '${target.name}' is experimental.`, { + node, + code: CODE_EXPERIMENTAL_REFERENCED_DECLARATION, + }); + } + }; diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index 05420011d..db15a5a3b 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -47,6 +47,20 @@ import { typeArgumentListMustNotHavePositionalArgumentsAfterNamedArguments } fro import { argumentListMustNotHavePositionalArgumentsAfterNamedArguments } from './other/argumentLists.js'; import { parameterMustNotBeVariadicAndOptional } from './other/declarations/parameters.js'; import { referenceTargetMustNotBeAnnotationPipelineOrSchema } from './other/expressions/references.js'; +import { + annotationCallAnnotationShouldNotBeDeprecated, + argumentCorrespondingParameterShouldNotBeDeprecated, + assigneeAssignedResultShouldNotBeDeprecated, + namedTypeDeclarationShouldNotBeDeprecated, + referenceTargetShouldNotBeDeprecated, +} from './builtins/deprecated.js'; +import { + annotationCallAnnotationShouldNotBeExperimental, + argumentCorrespondingParameterShouldNotBeExperimental, + assigneeAssignedResultShouldNotBeExperimental, + namedTypeDeclarationShouldNotBeExperimental, + referenceTargetShouldNotExperimental, +} from './builtins/experimental.js'; /** * Register custom validation checks. @@ -54,9 +68,21 @@ import { referenceTargetMustNotBeAnnotationPipelineOrSchema } from './other/expr export const registerValidationChecks = function (services: SafeDsServices) { const registry = services.validation.ValidationRegistry; const checks: ValidationChecks = { + SdsAssignee: [ + assigneeAssignedResultShouldNotBeDeprecated(services), + assigneeAssignedResultShouldNotBeExperimental(services), + ], SdsAssignment: [assignmentShouldHaveMoreThanWildcardsAsAssignees], SdsAnnotation: [annotationMustContainUniqueNames, annotationParameterListShouldNotBeEmpty], - SdsAnnotationCall: [annotationCallArgumentListShouldBeNeeded], + SdsAnnotationCall: [ + annotationCallAnnotationShouldNotBeDeprecated(services), + annotationCallAnnotationShouldNotBeExperimental(services), + annotationCallArgumentListShouldBeNeeded, + ], + SdsArgument: [ + argumentCorrespondingParameterShouldNotBeDeprecated(services), + argumentCorrespondingParameterShouldNotBeExperimental(services), + ], SdsArgumentList: [argumentListMustNotHavePositionalArgumentsAfterNamedArguments], SdsAttribute: [attributeMustHaveTypeHint], SdsBlockLambda: [blockLambdaMustContainUniqueNames], @@ -73,7 +99,11 @@ export const registerValidationChecks = function (services: SafeDsServices) { SdsFunction: [functionMustContainUniqueNames, functionResultListShouldNotBeEmpty], SdsMemberAccess: [memberAccessNullSafetyShouldBeNeeded(services)], SdsModule: [moduleDeclarationsMustMatchFileKind, moduleWithDeclarationsMustStatePackage], - SdsNamedType: [namedTypeTypeArgumentListShouldBeNeeded], + SdsNamedType: [ + namedTypeDeclarationShouldNotBeDeprecated(services), + namedTypeDeclarationShouldNotBeExperimental(services), + namedTypeTypeArgumentListShouldBeNeeded, + ], SdsParameter: [parameterMustHaveTypeHint, parameterMustNotBeVariadicAndOptional], SdsParameterList: [ parameterListMustNotHaveOptionalAndVariadicParameters, @@ -81,7 +111,11 @@ export const registerValidationChecks = function (services: SafeDsServices) { parameterListVariadicParameterMustBeLast, ], SdsPipeline: [pipelineMustContainUniqueNames], - SdsReference: [referenceTargetMustNotBeAnnotationPipelineOrSchema], + SdsReference: [ + referenceTargetMustNotBeAnnotationPipelineOrSchema, + referenceTargetShouldNotBeDeprecated(services), + referenceTargetShouldNotExperimental(services), + ], SdsResult: [resultMustHaveTypeHint], SdsSegment: [segmentMustContainUniqueNames, segmentResultListShouldNotBeEmpty], SdsTemplateString: [templateStringMustHaveExpressionBetweenTwoStringParts], diff --git a/src/resources/builtins/safeds/lang/coreAnnotations.sdsstub b/src/resources/builtins/safeds/lang/coreAnnotations.sdsstub index 1ccdcd91d..c634b7e1a 100644 --- a/src/resources/builtins/safeds/lang/coreAnnotations.sdsstub +++ b/src/resources/builtins/safeds/lang/coreAnnotations.sdsstub @@ -18,9 +18,6 @@ enum AnnotationTarget { @Description("The annotation can be called on classes.") Class - @Description("The annotation can be called on compilation units (i.e. files).") - CompilationUnit - @Description("The annotation can be called on enums.") Enum @@ -30,6 +27,9 @@ enum AnnotationTarget { @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 @@ -39,8 +39,8 @@ enum AnnotationTarget { @Description("The annotation can be called on results.") Result - @Description("The annotation can be called on steps.") - Step + @Description("The annotation can be called on segments.") + Segment @Description("The annotation can be called on type parameters.") TypeParameter @@ -60,8 +60,7 @@ annotation Repeatable AnnotationTarget.Function, AnnotationTarget.Parameter, AnnotationTarget.Result, - AnnotationTarget.Step, - AnnotationTarget.TypeParameter, + AnnotationTarget.Segment, ) annotation Deprecated( @Description("What to use instead.") @@ -87,8 +86,7 @@ annotation Deprecated( AnnotationTarget.Function, AnnotationTarget.Parameter, AnnotationTarget.Result, - AnnotationTarget.Step, - AnnotationTarget.TypeParameter, + AnnotationTarget.Segment, ) annotation Experimental diff --git a/tests/language/helpers/safe-ds-node-mapper/argumentToParameterOrUndefined.test.ts b/tests/language/helpers/safe-ds-node-mapper/argumentToParameterOrUndefined.test.ts index f65f6be0b..844a1315d 100644 --- a/tests/language/helpers/safe-ds-node-mapper/argumentToParameterOrUndefined.test.ts +++ b/tests/language/helpers/safe-ds-node-mapper/argumentToParameterOrUndefined.test.ts @@ -16,7 +16,7 @@ describe('SafeDsNodeMapper', () => { describe('argumentToParameterOrUndefined', () => { it('should return undefined if passed undefined', () => { - expect(nodeMapper.argumentToParameterOrUndefined(undefined)).toBeUndefined(); + expect(nodeMapper.argumentToParameterOrUndefined(undefined)?.$type).toBeUndefined(); }); describe('named argument', () => { diff --git a/tests/language/helpers/safe-ds-node-mapper/assigneeToAssignedObjectOrUndefined.test.ts b/tests/language/helpers/safe-ds-node-mapper/assigneeToAssignedObjectOrUndefined.test.ts new file mode 100644 index 000000000..288dd77b7 --- /dev/null +++ b/tests/language/helpers/safe-ds-node-mapper/assigneeToAssignedObjectOrUndefined.test.ts @@ -0,0 +1,163 @@ +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { createSafeDsServices } from '../../../../src/language/safe-ds-module.js'; +import { clearDocuments } from 'langium/test'; +import { getNodeOfType } from '../../../helpers/nodeFinder.js'; +import { + isSdsAbstractResult, + isSdsAssignment, + isSdsPlaceholder, + SdsAssignee, +} from '../../../../src/language/generated/ast.js'; +import { assigneesOrEmpty } from '../../../../src/language/helpers/nodeProperties.js'; +import { NodeFileSystem } from 'langium/node'; + +const services = createSafeDsServices(NodeFileSystem).SafeDs; +const nodeMapper = services.helpers.NodeMapper; + +describe('SafeDsNodeMapper', () => { + beforeEach(async () => { + // Load the builtin library + await services.shared.workspace.WorkspaceManager.initializeWorkspace([]); + }); + + afterEach(async () => { + await clearDocuments(services); + }); + + describe('assigneeToAssignedObjectOrUndefined', () => { + it('should return an empty list if passed undefined', async () => { + expect(nodeMapper.assigneeToAssignedObjectOrUndefined(undefined)?.$type).toBeUndefined(); + }); + + it.each([ + { + name: 'no value', + code: ` + fun f() + + segment mySegment() { + val a = f(); + }; + `, + }, + { + name: 'only one value', + code: ` + segment mySegment() { + _, val a = 1; + }; + `, + }, + + { + name: 'unresolved receiver of call', + code: ` + segment mySegment() { + val a = unresolved(); + }; + `, + }, + ])('should return undefined if nothing is assigned ($name)', async ({ code }) => { + const placeholder = await getNodeOfType(services, code, isSdsPlaceholder); + expect(nodeMapper.assigneeToAssignedObjectOrUndefined(placeholder)?.$type).toBeUndefined(); + }); + + it('should return the entire RHS of an assignment if it is not a call (constant)', async () => { + const code = ` + segment mySegment() { + val a = 1; + }; + `; + + const placeholder = await getNodeOfType(services, code, isSdsPlaceholder); + expect(nodeMapper.assigneeToAssignedObjectOrUndefined(placeholder)?.$type).toBe('SdsInt'); + }); + + it('should return the entire RHS of an assignment if it is not a call (unresolved reference)', async () => { + const code = ` + segment mySegment() { + val a = unresolved; + }; + `; + + const placeholder = await getNodeOfType(services, code, isSdsPlaceholder); + expect(nodeMapper.assigneeToAssignedObjectOrUndefined(placeholder)?.$type).toBe('SdsReference'); + }); + + it.each([ + { + name: 'block lambda', + code: ` + segment mySegment() { + val f = () { + yield r1 = 1; + yield r2 = 2; + }; + + val a, val b = f(); + }; + `, + expected: ['r1', 'r2'], + index: 3, + }, + { + name: 'callable type', + code: ` + segment mySegment(f: () -> (r1: Int, r2: Int)) { + val a, val b = f(); + }; + `, + expected: ['r1', 'r2'], + }, + { + name: 'function (one result)', + code: ` + fun f() -> (r1: Int) + + segment mySegment() { + val a = f(); + }; + `, + expected: ['r1'], + }, + { + name: 'function (multiple results)', + code: ` + fun f() -> (r1: Int, r2: Int) + + segment mySegment() { + val a, val b = f(); + }; + `, + expected: ['r1', 'r2'], + }, + { + name: 'segment', + code: ` + segment s() -> (r1: Int, r2: Int) + + segment mySegment() { + val a, val b = s(); + }; + `, + expected: ['r1', 'r2'], + }, + ])( + 'should return the corresponding result if the RHS is a call of a $name', + async ({ code, expected, index = 0 }) => { + const assignment = await getNodeOfType(services, code, isSdsAssignment, index); + const abstractResultNames = assigneesOrEmpty(assignment).map(abstractResultNameOrNull); + expect(abstractResultNames).toStrictEqual(expected); + }, + ); + + const abstractResultNameOrNull = (node: SdsAssignee): string | undefined => { + const assignedObject = nodeMapper.assigneeToAssignedObjectOrUndefined(node); + if (isSdsAbstractResult(assignedObject)) { + return assignedObject.name; + } else { + return undefined; + } + }; + }); +}); diff --git a/tests/language/helpers/safe-ds-node-mapper/callToCallableOrUndefined.test.ts b/tests/language/helpers/safe-ds-node-mapper/callToCallableOrUndefined.test.ts index b973f8fd5..48da24a42 100644 --- a/tests/language/helpers/safe-ds-node-mapper/callToCallableOrUndefined.test.ts +++ b/tests/language/helpers/safe-ds-node-mapper/callToCallableOrUndefined.test.ts @@ -30,7 +30,7 @@ describe('SafeDsNodeMapper', () => { `; const call = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(call)).toBeUndefined(); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBeUndefined(); }); it('should return the called annotation', async () => { @@ -59,7 +59,7 @@ describe('SafeDsNodeMapper', () => { `; const call = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(call)).toBeUndefined(); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBeUndefined(); }); it('should return undefined if receiver is not callable', async () => { @@ -72,7 +72,7 @@ describe('SafeDsNodeMapper', () => { `; const call = await getNodeOfType(services, code, isSdsAbstractCall); - expect(nodeMapper.callToCallableOrUndefined(call)).toBeUndefined(); + expect(nodeMapper.callToCallableOrUndefined(call)?.$type).toBeUndefined(); }); it('should return the called annotation', async () => { diff --git a/tests/language/helpers/safe-ds-node-mapper/typeArgumentToTypeParameterOrUndefined.test.ts b/tests/language/helpers/safe-ds-node-mapper/typeArgumentToTypeParameterOrUndefined.test.ts index 36a7eb39f..a5032f248 100644 --- a/tests/language/helpers/safe-ds-node-mapper/typeArgumentToTypeParameterOrUndefined.test.ts +++ b/tests/language/helpers/safe-ds-node-mapper/typeArgumentToTypeParameterOrUndefined.test.ts @@ -16,7 +16,7 @@ describe('SafeDsNodeMapper', () => { describe('typeArgumentToTypeParameterOrUndefined', () => { it('should return undefined if passed undefined', () => { - expect(nodeMapper.typeArgumentToTypeParameterOrUndefined(undefined)).toBeUndefined(); + expect(nodeMapper.typeArgumentToTypeParameterOrUndefined(undefined)?.$type).toBeUndefined(); }); describe('named type argument', () => { diff --git a/tests/resources/scoping/member accesses/skip-main.sdstest b/tests/resources/scoping/member accesses/skip-main.sdstest index d48b0881b..eaadc197c 100644 --- a/tests/resources/scoping/member accesses/skip-main.sdstest +++ b/tests/resources/scoping/member accesses/skip-main.sdstest @@ -428,195 +428,6 @@ inner class Reference { references.shouldHaveSize(1) references[0].declaration.shouldNotBeResolved() } - - @Test - fun `should resolve result of callable type with one result without matching member`() = - withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToCallableTypeResults") - val singleResult = step.findUniqueDeclarationOrFail("singleResult") - - val references = step.descendants().toList() - references.shouldHaveSize(8) - - val declaration = references[1].declaration - declaration.shouldBeResolved() - declaration.shouldBe(singleResult) - } - - @Test - fun `should resolve attribute for callable type with one result with matching class attribute`() = - withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToCallableTypeResults") - val classForResultMemberAccess = findUniqueDeclarationOrFail("ClassForResultMemberAccess") - val result = classForResultMemberAccess.findUniqueDeclarationOrFail("result") - - val references = step.descendants().toList() - references.shouldHaveSize(8) - - val declaration = references[3].declaration - declaration.shouldBeResolved() - declaration.shouldBe(result) - } - - @Test - fun `should resolve result for callable type with one result with matching enum variant`() = - withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToCallableTypeResults") - val callableWithOneResultWithIdenticalEnumVariant = - step.findUniqueDeclarationOrFail("callableWithOneResultWithIdenticalEnumVariant") - val result = - callableWithOneResultWithIdenticalEnumVariant.findUniqueDeclarationOrFail("result") - - val references = step.descendants().toList() - references.shouldHaveSize(8) - - val declaration = references[5].declaration - declaration.shouldBeResolved() - declaration.shouldBe(result) - } - - @Test - fun `should resolve result of callable type with multiple results`() = withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToCallableTypeResults") - val result1 = step.findUniqueDeclarationOrFail("result1") - - val references = step.descendants().toList() - references.shouldHaveSize(8) - - val declaration = references[7].declaration - declaration.shouldBeResolved() - declaration.shouldBe(result1) - } - - @Test - fun `should resolve result of function with one result without matching member`() = - withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToFunctionResults") - val globalFunctionResultInSameFile = - findUniqueDeclarationOrFail("globalFunctionResultInSameFile") - - val references = step.descendants().toList() - references.shouldHaveSize(6) - - val declaration = references[1].declaration - declaration.shouldBeResolved() - declaration.shouldBe(globalFunctionResultInSameFile) - } - - @Test - fun `should resolve member for function with one result with matching member`() = - withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToFunctionResults") - val classForResultMemberAccess = findUniqueDeclarationOrFail("ClassForResultMemberAccess") - val result = classForResultMemberAccess.findUniqueDeclarationOrFail("result") - - val references = step.descendants().toList() - references.shouldHaveSize(6) - - val declaration = references[3].declaration - declaration.shouldBeResolved() - declaration.shouldBe(result) - } - - @Test - fun `should resolve result of function with multiple results`() = withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToFunctionResults") - val globalFunctionWithTwoResults = - findUniqueDeclarationOrFail("globalFunctionWithTwoResults") - val result1 = globalFunctionWithTwoResults.findUniqueDeclarationOrFail("result1") - - val references = step.descendants().toList() - references.shouldHaveSize(6) - - val declaration = references[5].declaration - declaration.shouldBeResolved() - declaration.shouldBe(result1) - } - - @Test - fun `should resolve result of lambda with one result without matching member`() = withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToLambdaResults") - val singleResult = step.findUniqueDeclarationOrFail("singleResult") - - val references = step.descendants().toList() - references.shouldHaveSize(7) - - val declaration = references[2].declaration - declaration.shouldBeResolved() - declaration.shouldBe(singleResult) - } - - @Test - fun `should resolve member for lambda with one result with matching member`() = - withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToLambdaResults") - val classForResultMemberAccess = findUniqueDeclarationOrFail("ClassForResultMemberAccess") - val result = classForResultMemberAccess.findUniqueDeclarationOrFail("result") - - val references = step.descendants().toList() - references.shouldHaveSize(7) - - val declaration = references[4].declaration - declaration.shouldBeResolved() - declaration.shouldBe(result) - } - - @Test - fun `should resolve result of lambda with multiple results`() = withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToLambdaResults") - val result1 = step.findUniqueDeclarationOrFail("result1") - - val references = step.descendants().toList() - references.shouldHaveSize(7) - - val declaration = references[6].declaration - declaration.shouldBeResolved() - declaration.shouldBe(result1) - } - - @Test - fun `should resolve result of step with one result without matching member`() = - withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToStepResults") - val stepResultInSameFile = findUniqueDeclarationOrFail("stepResultInSameFile") - - val references = step.descendants().toList() - references.shouldHaveSize(6) - - val declaration = references[1].declaration - declaration.shouldBeResolved() - declaration.shouldBe(stepResultInSameFile) - } - - @Test - fun `should resolve member for step with one result with matching member`() = - withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToStepResults") - val classForResultMemberAccess = findUniqueDeclarationOrFail("ClassForResultMemberAccess") - val result = classForResultMemberAccess.findUniqueDeclarationOrFail("result") - - val references = step.descendants().toList() - references.shouldHaveSize(6) - - val declaration = references[3].declaration - declaration.shouldBeResolved() - declaration.shouldBe(result) - } - - @Test - fun `should resolve result of step with multiple results`() = withResource(REFERENCE) { - val step = findUniqueDeclarationOrFail("referencesToStepResults") - val stepInSameFileWithTwoResults = - findUniqueDeclarationOrFail("stepWithTwoResults") - val result1 = stepInSameFileWithTwoResults.findUniqueDeclarationOrFail("result1") - - val references = step.descendants().toList() - references.shouldHaveSize(6) - - val declaration = references[5].declaration - declaration.shouldBeResolved() - declaration.shouldBe(result1) - } } } */ @@ -781,47 +592,3 @@ step referencesToInstanceClassMembersFromClass() { ClassInSameFile.superClassInstanceAttribute; ClassInSameFile.superClassInstanceMethod(); } - - -// Access to results of callable ----------------------------------------------- - -step referencesToCallableTypeResults( - callableWithOneResult: () -> (singleResult: Int), - callableWithOneResultWithIdenticalClassAttribute: () -> (result: ClassForResultMemberAccess), - callableWithOneResultWithIdenticalEnumVariant: () -> (result: EnumForResultMemberAccess), - callableWithTwoResults: () -> (result1: Int, result2: Int) -) { - callableWithOneResult().singleResult; - callableWithOneResultWithIdenticalClassAttribute().result; - callableWithOneResultWithIdenticalEnumVariant().result; - callableWithTwoResults().result1; -} - -step referencesToFunctionResults() { - globalFunctionInSameFile(1).globalFunctionResultInSameFile; - globalFunctionWithOneResultWithIdenticalMember().result; - globalFunctionWithTwoResults().result1; -} - -step referencesToLambdaResults() { - val lambdaWithOneResult = () { - yield singleResult = 1; - }; - val lambdaWithOneResultWithIdenticalMember = () { - yield result = ClassForResultMemberAccess(); - }; - val lambdaWithTwoResults = () { - yield result1 = 1; - yield result2 = 1; - }; - - lambdaWithOneResult().singleResult; - lambdaWithOneResultWithIdenticalMember().result; - lambdaWithTwoResults().result1; -} - -step referencesToStepResults() { - stepInSameFile(1).stepResultInSameFile; - stepWithOneResultWithIdenticalMember().result; - stepWithTwoResults().result1; -} diff --git a/tests/resources/scoping/member accesses/to results/of block lambdas/main.sdstest b/tests/resources/scoping/member accesses/to results/of block lambdas/main.sdstest new file mode 100644 index 000000000..208ee9b3d --- /dev/null +++ b/tests/resources/scoping/member accesses/to results/of block lambdas/main.sdstest @@ -0,0 +1,20 @@ +package tests.scoping.memberAccesses.toResults.ofBlockLambdas + +pipeline myPipeline { + val lambdaWithOneResult = () { + // $TEST$ target lambdaWithOneResult_singleResult + yield »singleResult« = 1; + }; + val lambdaWithTwoResults = () { + // $TEST$ target lambdaWithTwoResults_result1 + yield »result1« = 1; + yield result1 = 1; + yield result2 = 1; + }; + + // $TEST$ references lambdaWithOneResult_singleResult + lambdaWithOneResult().»singleResult«; + + // $TEST$ references lambdaWithTwoResults_result1 + lambdaWithTwoResults().»result1«; +} diff --git a/tests/resources/scoping/member accesses/to results/of callable types/main.sdstest b/tests/resources/scoping/member accesses/to results/of callable types/main.sdstest new file mode 100644 index 000000000..1e4d3c66f --- /dev/null +++ b/tests/resources/scoping/member accesses/to results/of callable types/main.sdstest @@ -0,0 +1,15 @@ +package tests.scoping.memberAccesses.toResults.ofCallableTypes + +segment mySegment( + // $TEST$ target callableWithOneResult_singleResult + callableWithOneResult: () -> »singleResult«: Int, + + // $TEST$ target callableWithTwoResults_result1 + callableWithTwoResults: () -> (»result1«: Int, result2: Int), +) { + // $TEST$ references callableWithOneResult_singleResult + callableWithOneResult().»singleResult«; + + // $TEST$ references callableWithTwoResults_result1 + callableWithTwoResults().»result1«; +} diff --git a/tests/resources/scoping/member accesses/to results/of functions/main.sdstest b/tests/resources/scoping/member accesses/to results/of functions/main.sdstest new file mode 100644 index 000000000..eeba6f3f9 --- /dev/null +++ b/tests/resources/scoping/member accesses/to results/of functions/main.sdstest @@ -0,0 +1,15 @@ +package tests.scoping.memberAccesses.toResults.ofFunctions + +// $TEST$ target functionWithOneResult_singleResult +fun functionWithOneResult() -> »singleResult«: Int + +// $TEST$ target functionWithTwoResults_result1 +fun functionWithTwoResults() -> (»result1«: Int, result2: Int) + +pipeline myPipeline { + // $TEST$ references functionWithOneResult_singleResult + functionWithOneResult().»singleResult«; + + // $TEST$ references functionWithTwoResults_result1 + functionWithTwoResults().»result1«; +} diff --git a/tests/resources/scoping/member accesses/to results/of segments/main.sdstest b/tests/resources/scoping/member accesses/to results/of segments/main.sdstest new file mode 100644 index 000000000..ca1dbcaa5 --- /dev/null +++ b/tests/resources/scoping/member accesses/to results/of segments/main.sdstest @@ -0,0 +1,15 @@ +package tests.scoping.memberAccesses.toResults.ofSegments + +// $TEST$ target segmentWithOneResult_singleResult +segment segmentWithOneResult() -> »singleResult«: Int {} + +// $TEST$ target segmentWithTwoResults_result1 +segment segmentWithTwoResults() -> (»result1«: Int, result2: Int) {} + +pipeline myPipeline { + // $TEST$ references segmentWithOneResult_singleResult + segmentWithOneResult().»singleResult«; + + // $TEST$ references segmentWithTwoResults_result1 + segmentWithTwoResults().»result1«; +} diff --git a/tests/resources/scoping/member accesses/to results/skip-of block lambdas (matching member)/main.sdstest b/tests/resources/scoping/member accesses/to results/skip-of block lambdas (matching member)/main.sdstest new file mode 100644 index 000000000..fb53b4374 --- /dev/null +++ b/tests/resources/scoping/member accesses/to results/skip-of block lambdas (matching member)/main.sdstest @@ -0,0 +1,15 @@ +package tests.scoping.memberAccesses.toResults.ofBlockLambdas.matchingMember + +class MyClass() { + // $TEST$ target MyClass_result + attr »result«: Int +} + +pipeline myPipeline { + val lambdaWithOneResultWithIdenticalMember = () { + yield result = MyClass(); + }; + + // $TEST$ references MyClass_result + lambdaWithOneResultWithIdenticalMember().»result«; +} diff --git a/tests/resources/scoping/member accesses/to results/skip-of callable types (matching member)/main.sdstest b/tests/resources/scoping/member accesses/to results/skip-of callable types (matching member)/main.sdstest new file mode 100644 index 000000000..3d14a472b --- /dev/null +++ b/tests/resources/scoping/member accesses/to results/skip-of callable types (matching member)/main.sdstest @@ -0,0 +1,13 @@ +package tests.scoping.memberAccesses.toResults.ofCallableTypes.matchingMember + +class MyClass() { + // $TEST$ target MyClass_result + attr »result«: Int +} + +segment mySegment( + callableWithIdenticalMember: () -> result: MyClass +) { + // $TEST$ references MyClass_result + callableWithIdenticalMember().»result«; +} diff --git a/tests/resources/scoping/member accesses/to results/skip-of functions (matching member)/main.sdstest b/tests/resources/scoping/member accesses/to results/skip-of functions (matching member)/main.sdstest new file mode 100644 index 000000000..6ac04e80e --- /dev/null +++ b/tests/resources/scoping/member accesses/to results/skip-of functions (matching member)/main.sdstest @@ -0,0 +1,13 @@ +package tests.scoping.memberAccesses.toResults.ofFunctions.matchingMember + +class MyClass() { + // $TEST$ target MyClass_result + attr »result«: Int +} + +fun functionWithOneResultWithIdenticalMember() -> result: MyClass + +pipeline myPipeline { + // $TEST$ references MyClass_result + functionWithOneResultWithIdenticalMember().»result«; +} diff --git a/tests/resources/scoping/member accesses/to results/skip-of segments (matching member)/main.sdstest b/tests/resources/scoping/member accesses/to results/skip-of segments (matching member)/main.sdstest new file mode 100644 index 000000000..c90dae517 --- /dev/null +++ b/tests/resources/scoping/member accesses/to results/skip-of segments (matching member)/main.sdstest @@ -0,0 +1,13 @@ +package tests.scoping.memberAccesses.toResults.ofSegments.matchingMember + +class MyClass() { + // $TEST$ target MyClass_result + attr »result«: Int +} + +segment segmentWithOneResultWithIdenticalMember() -> result: MyClass {} + +pipeline myPipeline { + // $TEST$ references MyClass_result + segmentWithOneResultWithIdenticalMember().»result«; +} diff --git a/tests/resources/validation/builtins/annotations/deprecated/assigned result/main.sdstest b/tests/resources/validation/builtins/annotations/deprecated/assigned result/main.sdstest new file mode 100644 index 000000000..f4cc1bd1d --- /dev/null +++ b/tests/resources/validation/builtins/annotations/deprecated/assigned result/main.sdstest @@ -0,0 +1,30 @@ +package tests.validation.builtins.deprecated.assignedResult + +fun myFunction() -> ( + @Deprecated deprecatedResult: Int, + validResult: Int +) + +segment mySegment() -> (result1: Int, result2: Int, result3: Int) { + // $TEST$ warning "The assigned result 'deprecatedResult' is deprecated." + // $TEST$ no warning r"The assigned result '\w*' is deprecated\." + // $TEST$ no warning r"The assigned result '\w*' is deprecated\." + »val a«, »val b«, »val c« = myFunction(); + + // $TEST$ warning "The assigned result 'deprecatedResult' is deprecated." + // $TEST$ no warning r"The assigned result '\w*' is deprecated\." + // $TEST$ no warning r"The assigned result '\w*' is deprecated\." + »yield result1«, »yield result2«, »yield result3« = myFunction(); + + // $TEST$ no warning r"The assigned result '\w*' is deprecated\." + // $TEST$ no warning r"The assigned result '\w*' is deprecated\." + // $TEST$ no warning r"The assigned result '\w*' is deprecated\." + »_«, »_«, »_« = myFunction(); + + // $TEST$ no warning r"The assigned result '\w*' is deprecated\." + »val d« = a; + // $TEST$ no warning r"The assigned result '\w*' is deprecated\." + »val e« = b; + // $TEST$ no warning r"The assigned result '\w*' is deprecated\." + »val f« = 1; +} diff --git a/tests/resources/validation/builtins/annotations/deprecated/called annotation/main.sdstest b/tests/resources/validation/builtins/annotations/deprecated/called annotation/main.sdstest new file mode 100644 index 000000000..c7ac37d19 --- /dev/null +++ b/tests/resources/validation/builtins/annotations/deprecated/called annotation/main.sdstest @@ -0,0 +1,13 @@ +package tests.validation.builtins.deprecated.calledAnnotation + +@Deprecated +annotation DeprecatedAnnotation +annotation ValidAnnotation + +// $TEST$ warning "The called annotation 'DeprecatedAnnotation' is deprecated." +@»DeprecatedAnnotation« +// $TEST$ no warning r"The called annotation '\w*' is deprecated\." +@»ValidAnnotation« +// $TEST$ no warning r"The called annotation '\w*' is deprecated\." +@»Unresolved« +pipeline myPipeline {} diff --git a/tests/resources/validation/builtins/annotations/deprecated/corresponding parameter/main.sdstest b/tests/resources/validation/builtins/annotations/deprecated/corresponding parameter/main.sdstest new file mode 100644 index 000000000..752ebb63b --- /dev/null +++ b/tests/resources/validation/builtins/annotations/deprecated/corresponding parameter/main.sdstest @@ -0,0 +1,26 @@ +package tests.validation.builtins.deprecated.correspondingParameter + +fun f( + @Deprecated deprecatedParameter: Int = 1, + validParameter: Int = 1 +) + +pipeline myPipeline { + f( + // $TEST$ warning "The corresponding parameter 'deprecatedParameter' is deprecated." + »deprecatedParameter = 1«, + // $TEST$ no warning r"The corresponding parameter '\w*' is deprecated\." + »validParameter = 1«, + // $TEST$ no warning r"The corresponding parameter '\w*' is deprecated\." + »unresolved = 1«, + ); + + f( + // $TEST$ warning "The corresponding parameter 'deprecatedParameter' is deprecated." + »1«, + // $TEST$ no warning r"The corresponding parameter '\w*' is deprecated\." + »1«, + // $TEST$ no warning r"The corresponding parameter '\w*' is deprecated\." + »1«, + ); +} diff --git a/tests/resources/validation/builtins/annotations/deprecated/referenced declaration/from named type/main.sdstest b/tests/resources/validation/builtins/annotations/deprecated/referenced declaration/from named type/main.sdstest new file mode 100644 index 000000000..8de795830 --- /dev/null +++ b/tests/resources/validation/builtins/annotations/deprecated/referenced declaration/from named type/main.sdstest @@ -0,0 +1,32 @@ +package tests.validation.builtins.deprecated.referencedDeclaration.fromNamedType + +@Deprecated +class DeprecatedClass +class ValidClass + +@Deprecated +enum DeprecatedEnum +enum ValidEnum { + @Deprecated DeprecatedEnumVariant + ValidEnumVariant +} + +segment mySegment( + // $TEST$ warning "The referenced declaration 'DeprecatedClass' is deprecated." + p1: »DeprecatedClass«, + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + p2: »ValidClass«, + + // $TEST$ warning "The referenced declaration 'DeprecatedEnum' is deprecated." + p3: »DeprecatedEnum«, + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + p4: »ValidEnum«, + + // $TEST$ warning "The referenced declaration 'DeprecatedEnumVariant' is deprecated." + p5: ValidEnum.»DeprecatedEnumVariant«, + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + p6: ValidEnum.»ValidEnumVariant«, + + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + p7: »Unresolved« +) {} diff --git a/tests/resources/validation/builtins/annotations/deprecated/referenced declaration/from reference/main.sdstest b/tests/resources/validation/builtins/annotations/deprecated/referenced declaration/from reference/main.sdstest new file mode 100644 index 000000000..71a502031 --- /dev/null +++ b/tests/resources/validation/builtins/annotations/deprecated/referenced declaration/from reference/main.sdstest @@ -0,0 +1,96 @@ +package tests.validation.builtins.deprecated.referencedDeclaration.fromReference + +@Deprecated +class DeprecatedClass +class ValidClass { + + @Deprecated + static attr deprecatedAttribute: Int + static attr validAttribute: Int +} + +@Deprecated +enum DeprecatedEnum +enum ValidEnum { + @Deprecated DeprecatedEnumVariant + + ValidEnumVariant +} + +@Deprecated +fun deprecatedFunction() +fun validFunction() -> ( + @Deprecated + deprecatedResult: Int, + validResult: Int +) + +@Deprecated +segment deprecatedSegment() {} +segment validSegment() {} + +segment mySegment( + @Deprecated deprecatedParameter: ValidClass, + validParameter: ValidClass, +) +-> ( + @Deprecated deprecatedResult: Int, + validResult: Int +) { + /* + * Deprecation is only relevant for callers + */ + + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + »deprecatedParameter«; + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + »validParameter«; + + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + yield »deprecatedResult« = 1; + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + yield »validResult« = 1; + + + // $TEST$ warning "The referenced declaration 'DeprecatedClass' is deprecated." + »DeprecatedClass«; + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + »ValidClass«; + + // $TEST$ warning "The referenced declaration 'deprecatedAttribute' is deprecated." + ValidClass.»deprecatedAttribute«; + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + ValidClass.»validAttribute«; + + + // $TEST$ warning "The referenced declaration 'DeprecatedEnum' is deprecated." + »DeprecatedEnum«; + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + »ValidEnum«; + + // $TEST$ warning "The referenced declaration 'DeprecatedEnumVariant' is deprecated." + ValidEnum.»DeprecatedEnumVariant«; + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + ValidEnum.»ValidEnumVariant«; + + + // $TEST$ warning "The referenced declaration 'deprecatedFunction' is deprecated." + »deprecatedFunction«(); + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + »validFunction«(); + + // $TEST$ warning "The referenced declaration 'deprecatedResult' is deprecated." + validFunction().»deprecatedResult«; + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + validFunction().»validResult«; + + + // $TEST$ warning "The referenced declaration 'deprecatedSegment' is deprecated." + »deprecatedSegment«(); + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + »validSegment«(); + + + // $TEST$ no warning r"The referenced declaration '\w*' is deprecated\." + »unresolved«; +} diff --git a/tests/resources/validation/builtins/annotations/experimental/assigned result/main.sdstest b/tests/resources/validation/builtins/annotations/experimental/assigned result/main.sdstest new file mode 100644 index 000000000..97c2171c6 --- /dev/null +++ b/tests/resources/validation/builtins/annotations/experimental/assigned result/main.sdstest @@ -0,0 +1,30 @@ +package tests.validation.builtins.experimental.assignedResult + +fun myFunction() -> ( + @Experimental experimentalResult: Int, + validResult: Int +) + +segment mySegment() -> (result1: Int, result2: Int, result3: Int) { + // $TEST$ warning "The assigned result 'experimentalResult' is experimental." + // $TEST$ no warning r"The assigned result '\w*' is experimental\." + // $TEST$ no warning r"The assigned result '\w*' is experimental\." + »val a«, »val b«, »val c« = myFunction(); + + // $TEST$ warning "The assigned result 'experimentalResult' is experimental." + // $TEST$ no warning r"The assigned result '\w*' is experimental\." + // $TEST$ no warning r"The assigned result '\w*' is experimental\." + »yield result1«, »yield result2«, »yield result3« = myFunction(); + + // $TEST$ no warning r"The assigned result '\w*' is experimental\." + // $TEST$ no warning r"The assigned result '\w*' is experimental\." + // $TEST$ no warning r"The assigned result '\w*' is experimental\." + »_«, »_«, »_« = myFunction(); + + // $TEST$ no warning r"The assigned result '\w*' is experimental\." + »val d« = a; + // $TEST$ no warning r"The assigned result '\w*' is experimental\." + »val e« = b; + // $TEST$ no warning r"The assigned result '\w*' is experimental\." + »val f« = 1; +} diff --git a/tests/resources/validation/builtins/annotations/experimental/called annotation/main.sdstest b/tests/resources/validation/builtins/annotations/experimental/called annotation/main.sdstest new file mode 100644 index 000000000..3792aadae --- /dev/null +++ b/tests/resources/validation/builtins/annotations/experimental/called annotation/main.sdstest @@ -0,0 +1,13 @@ +package tests.validation.builtins.experimental.calledAnnotation + +@Experimental +annotation ExperimentalAnnotation +annotation ValidAnnotation + +// $TEST$ warning "The called annotation 'ExperimentalAnnotation' is experimental." +@»ExperimentalAnnotation« +// $TEST$ no warning r"The called annotation '\w*' is experimental\." +@»ValidAnnotation« +// $TEST$ no warning r"The called annotation '\w*' is experimental\." +@»Unresolved« +pipeline myPipeline {} diff --git a/tests/resources/validation/builtins/annotations/experimental/corresponding parameter/main.sdstest b/tests/resources/validation/builtins/annotations/experimental/corresponding parameter/main.sdstest new file mode 100644 index 000000000..ba5934cf6 --- /dev/null +++ b/tests/resources/validation/builtins/annotations/experimental/corresponding parameter/main.sdstest @@ -0,0 +1,26 @@ +package tests.validation.builtins.experimental.correspondingParameter + +fun f( + @Experimental experimentalParameter: Int = 1, + validParameter: Int = 1 +) + +pipeline myPipeline { + f( + // $TEST$ warning "The corresponding parameter 'experimentalParameter' is experimental." + »experimentalParameter = 1«, + // $TEST$ no warning r"The corresponding parameter '\w*' is experimental\." + »validParameter = 1«, + // $TEST$ no warning r"The corresponding parameter '\w*' is experimental\." + »unresolved = 1«, + ); + + f( + // $TEST$ warning "The corresponding parameter 'experimentalParameter' is experimental." + »1«, + // $TEST$ no warning r"The corresponding parameter '\w*' is experimental\." + »1«, + // $TEST$ no warning r"The corresponding parameter '\w*' is experimental\." + »1«, + ); +} diff --git a/tests/resources/validation/builtins/annotations/experimental/referenced declaration/from named type/main.sdstest b/tests/resources/validation/builtins/annotations/experimental/referenced declaration/from named type/main.sdstest new file mode 100644 index 000000000..b2be9f453 --- /dev/null +++ b/tests/resources/validation/builtins/annotations/experimental/referenced declaration/from named type/main.sdstest @@ -0,0 +1,32 @@ +package tests.validation.builtins.experimental.referencedDeclaration.fromNamedType + +@Experimental +class ExperimentalClass +class ValidClass + +@Experimental +enum ExperimentalEnum +enum ValidEnum { + @Experimental ExperimentalEnumVariant + ValidEnumVariant +} + +segment mySegment( + // $TEST$ warning "The referenced declaration 'ExperimentalClass' is experimental." + p1: »ExperimentalClass«, + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + p2: »ValidClass«, + + // $TEST$ warning "The referenced declaration 'ExperimentalEnum' is experimental." + p3: »ExperimentalEnum«, + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + p4: »ValidEnum«, + + // $TEST$ warning "The referenced declaration 'ExperimentalEnumVariant' is experimental." + p5: ValidEnum.»ExperimentalEnumVariant«, + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + p6: ValidEnum.»ValidEnumVariant«, + + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + p7: »Unresolved« +) {} diff --git a/tests/resources/validation/builtins/annotations/experimental/referenced declaration/from reference/main.sdstest b/tests/resources/validation/builtins/annotations/experimental/referenced declaration/from reference/main.sdstest new file mode 100644 index 000000000..10745a354 --- /dev/null +++ b/tests/resources/validation/builtins/annotations/experimental/referenced declaration/from reference/main.sdstest @@ -0,0 +1,96 @@ +package tests.validation.builtins.experimental.referencedDeclaration.fromReference + +@Experimental +class ExperimentalClass +class ValidClass { + + @Experimental + static attr experimentalAttribute: Int + static attr validAttribute: Int +} + +@Experimental +enum ExperimentalEnum +enum ValidEnum { + @Experimental ExperimentalEnumVariant + + ValidEnumVariant +} + +@Experimental +fun experimentalFunction() +fun validFunction() -> ( + @Experimental + experimentalResult: Int, + validResult: Int +) + +@Experimental +segment experimentalSegment() {} +segment validSegment() {} + +segment mySegment( + @Experimental experimentalParameter: ValidClass, + validParameter: ValidClass, +) +-> ( + @Experimental experimentalResult: Int, + validResult: Int +) { + /* + * Deprecation is only relevant for callers + */ + + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + »experimentalParameter«; + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + »validParameter«; + + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + yield »experimentalResult« = 1; + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + yield »validResult« = 1; + + + // $TEST$ warning "The referenced declaration 'ExperimentalClass' is experimental." + »ExperimentalClass«; + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + »ValidClass«; + + // $TEST$ warning "The referenced declaration 'experimentalAttribute' is experimental." + ValidClass.»experimentalAttribute«; + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + ValidClass.»validAttribute«; + + + // $TEST$ warning "The referenced declaration 'ExperimentalEnum' is experimental." + »ExperimentalEnum«; + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + »ValidEnum«; + + // $TEST$ warning "The referenced declaration 'ExperimentalEnumVariant' is experimental." + ValidEnum.»ExperimentalEnumVariant«; + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + ValidEnum.»ValidEnumVariant«; + + + // $TEST$ warning "The referenced declaration 'experimentalFunction' is experimental." + »experimentalFunction«(); + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + »validFunction«(); + + // $TEST$ warning "The referenced declaration 'experimentalResult' is experimental." + validFunction().»experimentalResult«; + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + validFunction().»validResult«; + + + // $TEST$ warning "The referenced declaration 'experimentalSegment' is experimental." + »experimentalSegment«(); + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + »validSegment«(); + + + // $TEST$ no warning r"The referenced declaration '\w*' is experimental\." + »unresolved«; +}