Skip to content

Commit 9b5287c

Browse files
feat: warn if deprecated/experimental declarations are used (#608)
Closes partially #543 Closes partially #540 ### Summary of Changes Show a warning if deprecated or experimental declarations are used. To implement this, I've also added scoping for member accesses to results. --------- Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com>
1 parent d53bda3 commit 9b5287c

File tree

38 files changed

+1148
-408
lines changed

38 files changed

+1148
-408
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { resolveRelativePathToBuiltinFile } from './fileFinder.js';
2+
import { isSdsAnnotation, SdsAnnotatedObject, SdsAnnotation } from '../generated/ast.js';
3+
import { annotationCallsOrEmpty } from '../helpers/nodeProperties.js';
4+
import { SafeDsModuleMembers } from './safe-ds-module-members.js';
5+
6+
const CORE_ANNOTATIONS_URI = resolveRelativePathToBuiltinFile('safeds/lang/coreAnnotations.sdsstub');
7+
8+
export class SafeDsAnnotations extends SafeDsModuleMembers<SdsAnnotation> {
9+
isDeprecated(node: SdsAnnotatedObject | undefined): boolean {
10+
return annotationCallsOrEmpty(node).some((it) => {
11+
const annotation = it.annotation?.ref;
12+
return annotation === this.Deprecated;
13+
});
14+
}
15+
16+
isExperimental(node: SdsAnnotatedObject | undefined): boolean {
17+
return annotationCallsOrEmpty(node).some((it) => {
18+
const annotation = it.annotation?.ref;
19+
return annotation === this.Experimental;
20+
});
21+
}
22+
23+
private get Deprecated(): SdsAnnotation | undefined {
24+
return this.getAnnotation('Deprecated');
25+
}
26+
27+
private get Experimental(): SdsAnnotation | undefined {
28+
return this.getAnnotation('Experimental');
29+
}
30+
31+
private getAnnotation(name: string): SdsAnnotation | undefined {
32+
return this.getModuleMember(CORE_ANNOTATIONS_URI, name, isSdsAnnotation);
33+
}
34+
}
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { resolveRelativePathToBuiltinFile } from './fileFinder.js';
2+
import { isSdsClass, SdsClass } from '../generated/ast.js';
3+
import { SafeDsModuleMembers } from './safe-ds-module-members.js';
4+
5+
const CORE_CLASSES_URI = resolveRelativePathToBuiltinFile('safeds/lang/coreClasses.sdsstub');
6+
7+
export class SafeDsClasses extends SafeDsModuleMembers<SdsClass> {
8+
/* c8 ignore start */
9+
get Any(): SdsClass | undefined {
10+
return this.getClass('Any');
11+
}
12+
13+
/* c8 ignore stop */
14+
15+
get Boolean(): SdsClass | undefined {
16+
return this.getClass('Boolean');
17+
}
18+
19+
get Float(): SdsClass | undefined {
20+
return this.getClass('Float');
21+
}
22+
23+
get Int(): SdsClass | undefined {
24+
return this.getClass('Int');
25+
}
26+
27+
get Nothing(): SdsClass | undefined {
28+
return this.getClass('Nothing');
29+
}
30+
31+
get String(): SdsClass | undefined {
32+
return this.getClass('String');
33+
}
34+
35+
private getClass(name: string): SdsClass | undefined {
36+
return this.getModuleMember(CORE_CLASSES_URI, name, isSdsClass);
37+
}
38+
}

src/language/builtins/safe-ds-core-classes.ts

-94
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { SafeDsServices } from '../safe-ds-module.js';
2+
import { isSdsModule, SdsModuleMember } from '../generated/ast.js';
3+
import { LangiumDocuments, URI, WorkspaceCache } from 'langium';
4+
import { moduleMembersOrEmpty } from '../helpers/nodeProperties.js';
5+
6+
export abstract class SafeDsModuleMembers<T extends SdsModuleMember> {
7+
private readonly langiumDocuments: LangiumDocuments;
8+
private readonly cache: WorkspaceCache<string, T>;
9+
10+
constructor(services: SafeDsServices) {
11+
this.langiumDocuments = services.shared.workspace.LangiumDocuments;
12+
this.cache = new WorkspaceCache(services.shared);
13+
}
14+
15+
protected getModuleMember(uri: URI, name: string, predicate: (node: unknown) => node is T): T | undefined {
16+
const key = `${uri.toString()}#${name}`;
17+
18+
if (this.cache.has(key)) {
19+
return this.cache.get(key);
20+
}
21+
22+
if (!this.langiumDocuments.hasDocument(uri)) {
23+
/* c8 ignore next 2 */
24+
return undefined;
25+
}
26+
27+
const document = this.langiumDocuments.getOrCreateDocument(uri);
28+
const root = document.parseResult.value;
29+
if (!isSdsModule(root)) {
30+
/* c8 ignore next 2 */
31+
return undefined;
32+
}
33+
34+
const firstMatchingModuleMember = moduleMembersOrEmpty(root).find((m) => m.name === name);
35+
if (!predicate(firstMatchingModuleMember)) {
36+
/* c8 ignore next 2 */
37+
return undefined;
38+
}
39+
40+
this.cache.set(key, firstMatchingModuleMember);
41+
return firstMatchingModuleMember;
42+
}
43+
}

src/language/grammar/safe-ds.langium

+1-1
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ fragment SdsSegmentFragment:
334334
interface SdsAnnotationCallList extends SdsAnnotatedObject {}
335335

336336
interface SdsAnnotationCall extends SdsAbstractCall {
337-
annotation?: @SdsAnnotation
337+
annotation: @SdsAnnotation
338338
}
339339

340340
SdsAnnotationCall returns SdsAnnotationCall:

src/language/helpers/nodeProperties.ts

+21
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import {
22
isSdsAssignment,
33
isSdsAttribute,
4+
isSdsBlockLambda,
45
isSdsBlockLambdaResult,
6+
isSdsCallableType,
57
isSdsClass,
68
isSdsDeclaration,
79
isSdsEnum,
@@ -13,6 +15,7 @@ import {
1315
isSdsSegment,
1416
isSdsTypeParameterList,
1517
SdsAbstractCall,
18+
SdsAbstractResult,
1619
SdsAnnotatedObject,
1720
SdsAnnotationCall,
1821
SdsArgument,
@@ -84,6 +87,24 @@ export const isStatic = (node: SdsClassMember): boolean => {
8487
// Accessors for list elements
8588
// -------------------------------------------------------------------------------------------------
8689

90+
export const abstractResultsOrEmpty = (node: SdsCallable | undefined): SdsAbstractResult[] => {
91+
if (!node) {
92+
return [];
93+
}
94+
95+
if (isSdsBlockLambda(node)) {
96+
return blockLambdaResultsOrEmpty(node);
97+
} else if (isSdsCallableType(node)) {
98+
return resultsOrEmpty(node.resultList);
99+
} else if (isSdsFunction(node)) {
100+
return resultsOrEmpty(node.resultList);
101+
} else if (isSdsSegment(node)) {
102+
return resultsOrEmpty(node.resultList);
103+
} /* c8 ignore start */ else {
104+
return [];
105+
} /* c8 ignore stop */
106+
};
107+
87108
export const annotationCallsOrEmpty = (node: SdsAnnotatedObject | undefined): SdsAnnotationCall[] => {
88109
if (!node) {
89110
/* c8 ignore next 2 */

src/language/helpers/safe-ds-node-mapper.ts

+39
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
33
import {
44
isSdsAbstractCall,
55
isSdsAnnotationCall,
6+
isSdsAssignment,
67
isSdsBlock,
78
isSdsCall,
89
isSdsCallable,
@@ -12,8 +13,11 @@ import {
1213
isSdsType,
1314
isSdsYield,
1415
SdsAbstractCall,
16+
SdsAbstractResult,
1517
SdsArgument,
18+
SdsAssignee,
1619
SdsCallable,
20+
SdsExpression,
1721
SdsParameter,
1822
SdsPlaceholder,
1923
SdsReference,
@@ -25,6 +29,7 @@ import {
2529
import { CallableType, StaticType } from '../typing/model.js';
2630
import { findLocalReferences, getContainerOfType } from 'langium';
2731
import {
32+
abstractResultsOrEmpty,
2833
argumentsOrEmpty,
2934
isNamedArgument,
3035
isNamedTypeArgument,
@@ -81,6 +86,40 @@ export class SafeDsNodeMapper {
8186
return undefined;
8287
}
8388

89+
/**
90+
* Returns the result, block lambda result, or expression that is assigned to the given assignee. If nothing is
91+
* assigned, `undefined` is returned.
92+
*/
93+
assigneeToAssignedObjectOrUndefined(node: SdsAssignee | undefined): SdsAbstractResult | SdsExpression | undefined {
94+
if (!node) {
95+
return undefined;
96+
}
97+
98+
const containingAssignment = getContainerOfType(node, isSdsAssignment);
99+
/* c8 ignore start */
100+
if (!containingAssignment) {
101+
return undefined;
102+
}
103+
/* c8 ignore stop */
104+
105+
const assigneePosition = node.$containerIndex ?? -1;
106+
const expression = containingAssignment.expression;
107+
108+
// If the RHS is not a call, the first assignee gets the entire RHS
109+
if (!isSdsCall(expression)) {
110+
if (assigneePosition === 0) {
111+
return expression;
112+
} else {
113+
return undefined;
114+
}
115+
}
116+
117+
// If the RHS is a call, the assignee gets the corresponding result
118+
const callable = this.callToCallableOrUndefined(expression);
119+
const abstractResults = abstractResultsOrEmpty(callable);
120+
return abstractResults[assigneePosition];
121+
}
122+
84123
/**
85124
* Returns the callable that is called by the given call. If no callable can be found, returns undefined.
86125
*/

src/language/safe-ds-module.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@ import { SafeDsScopeComputation } from './scoping/safe-ds-scope-computation.js';
1717
import { SafeDsScopeProvider } from './scoping/safe-ds-scope-provider.js';
1818
import { SafeDsValueConverter } from './grammar/safe-ds-value-converter.js';
1919
import { SafeDsTypeComputer } from './typing/safe-ds-type-computer.js';
20-
import { SafeDsCoreClasses } from './builtins/safe-ds-core-classes.js';
20+
import { SafeDsClasses } from './builtins/safe-ds-classes.js';
2121
import { SafeDsPackageManager } from './workspace/safe-ds-package-manager.js';
2222
import { SafeDsNodeMapper } from './helpers/safe-ds-node-mapper.js';
23+
import { SafeDsAnnotations } from './builtins/safe-ds-annotations.js';
2324

2425
/**
2526
* Declaration of custom services - add your own service classes here.
2627
*/
2728
export type SafeDsAddedServices = {
2829
builtins: {
29-
CoreClasses: SafeDsCoreClasses;
30+
Annotations: SafeDsAnnotations;
31+
Classes: SafeDsClasses;
3032
};
3133
helpers: {
3234
NodeMapper: SafeDsNodeMapper;
@@ -52,7 +54,8 @@ export type SafeDsServices = LangiumServices & SafeDsAddedServices;
5254
*/
5355
export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeDsAddedServices> = {
5456
builtins: {
55-
CoreClasses: (services) => new SafeDsCoreClasses(services),
57+
Annotations: (services) => new SafeDsAnnotations(services),
58+
Classes: (services) => new SafeDsClasses(services),
5659
},
5760
helpers: {
5861
NodeMapper: (services) => new SafeDsNodeMapper(services),

0 commit comments

Comments
 (0)