Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: warn if deprecated/experimental declarations are used #608

Merged
merged 13 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/language/builtins/safe-ds-annotations.ts
Original file line number Diff line number Diff line change
@@ -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<SdsAnnotation> {
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);
}
}
38 changes: 38 additions & 0 deletions src/language/builtins/safe-ds-classes.ts
Original file line number Diff line number Diff line change
@@ -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<SdsClass> {
/* 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);
}
}
94 changes: 0 additions & 94 deletions src/language/builtins/safe-ds-core-classes.ts

This file was deleted.

43 changes: 43 additions & 0 deletions src/language/builtins/safe-ds-module-members.ts
Original file line number Diff line number Diff line change
@@ -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<T extends SdsModuleMember> {
private readonly langiumDocuments: LangiumDocuments;
private readonly cache: WorkspaceCache<string, T>;

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;
}
}
2 changes: 1 addition & 1 deletion src/language/grammar/safe-ds.langium
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ fragment SdsSegmentFragment:
interface SdsAnnotationCallList extends SdsAnnotatedObject {}

interface SdsAnnotationCall extends SdsAbstractCall {
annotation?: @SdsAnnotation
annotation: @SdsAnnotation
}

SdsAnnotationCall returns SdsAnnotationCall:
Expand Down
21 changes: 21 additions & 0 deletions src/language/helpers/nodeProperties.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import {
isSdsAssignment,
isSdsAttribute,
isSdsBlockLambda,
isSdsBlockLambdaResult,
isSdsCallableType,
isSdsClass,
isSdsDeclaration,
isSdsEnum,
Expand All @@ -13,6 +15,7 @@ import {
isSdsSegment,
isSdsTypeParameterList,
SdsAbstractCall,
SdsAbstractResult,
SdsAnnotatedObject,
SdsAnnotationCall,
SdsArgument,
Expand Down Expand Up @@ -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 */
Expand Down
39 changes: 39 additions & 0 deletions src/language/helpers/safe-ds-node-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
import {
isSdsAbstractCall,
isSdsAnnotationCall,
isSdsAssignment,
isSdsBlock,
isSdsCall,
isSdsCallable,
Expand All @@ -12,8 +13,11 @@ import {
isSdsType,
isSdsYield,
SdsAbstractCall,
SdsAbstractResult,
SdsArgument,
SdsAssignee,
SdsCallable,
SdsExpression,
SdsParameter,
SdsPlaceholder,
SdsReference,
Expand All @@ -25,6 +29,7 @@ import {
import { CallableType, StaticType } from '../typing/model.js';
import { findLocalReferences, getContainerOfType } from 'langium';
import {
abstractResultsOrEmpty,
argumentsOrEmpty,
isNamedArgument,
isNamedTypeArgument,
Expand Down Expand Up @@ -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.
*/
Expand Down
9 changes: 6 additions & 3 deletions src/language/safe-ds-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -52,7 +54,8 @@ export type SafeDsServices = LangiumServices & SafeDsAddedServices;
*/
export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeDsAddedServices> = {
builtins: {
CoreClasses: (services) => new SafeDsCoreClasses(services),
Annotations: (services) => new SafeDsAnnotations(services),
Classes: (services) => new SafeDsClasses(services),
},
helpers: {
NodeMapper: (services) => new SafeDsNodeMapper(services),
Expand Down
Loading