Skip to content

Commit

Permalink
feat: various features related to literal types (#657)
Browse files Browse the repository at this point in the history
Closes partially #80

### Summary of Changes

* Show an error if literal types have no literals
* Show an error if lists or maps are used in a literal types
* Mark literal types as experimental (show a warning when they are used)
* Compute type of manifest literal types
* Evaluate literals to a literal type instead of the corresponding
class. For example, the literal `1` now gets the type `literal<1>`
instead of `Int`).

---------

Co-authored-by: megalinter-bot <[email protected]>
  • Loading branch information
lars-reimann and megalinter-bot authored Oct 21, 2023
1 parent ca47870 commit 1775705
Show file tree
Hide file tree
Showing 47 changed files with 692 additions and 171 deletions.
6 changes: 6 additions & 0 deletions src/language/safe-ds-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { SafeDsAnnotations } from './builtins/safe-ds-annotations.js';
import { SafeDsClassHierarchy } from './typing/safe-ds-class-hierarchy.js';
import { SafeDsPartialEvaluator } from './partialEvaluation/safe-ds-partial-evaluator.js';
import { SafeDsSemanticTokenProvider } from './lsp/safe-ds-semantic-token-provider.js';
import { SafeDsTypeChecker } from './typing/safe-ds-type-checker.js';
import { SafeDsCoreTypes } from './typing/safe-ds-core-types.js';

/**
* Declaration of custom services - add your own service classes here.
Expand All @@ -41,6 +43,8 @@ export type SafeDsAddedServices = {
};
types: {
ClassHierarchy: SafeDsClassHierarchy;
CoreTypes: SafeDsCoreTypes;
TypeChecker: SafeDsTypeChecker;
TypeComputer: SafeDsTypeComputer;
};
workspace: {
Expand Down Expand Up @@ -83,6 +87,8 @@ export const SafeDsModule: Module<SafeDsServices, PartialLangiumServices & SafeD
},
types: {
ClassHierarchy: (services) => new SafeDsClassHierarchy(services),
CoreTypes: (services) => new SafeDsCoreTypes(services),
TypeChecker: (services) => new SafeDsTypeChecker(services),
TypeComputer: (services) => new SafeDsTypeComputer(services),
},
workspace: {
Expand Down
19 changes: 12 additions & 7 deletions src/language/typing/model.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import {
isSdsNull,
SdsAbstractResult,
SdsCallable,
SdsClass,
SdsDeclaration,
SdsEnum,
SdsEnumVariant,
SdsLiteral,
SdsParameter,
} from '../generated/ast.js';
import { Constant, NullConstant } from '../partialEvaluation/model.js';

/* c8 ignore start */
export abstract class Type {
Expand Down Expand Up @@ -68,10 +67,10 @@ export class CallableType extends Type {
export class LiteralType extends Type {
override readonly isNullable: boolean;

constructor(readonly values: SdsLiteral[]) {
constructor(readonly constants: Constant[]) {
super();

this.isNullable = values.some(isSdsNull);
this.isNullable = constants.some((it) => it === NullConstant);
}

override copyWithNullability(isNullable: boolean): LiteralType {
Expand All @@ -93,11 +92,15 @@ export class LiteralType extends Type {
return false;
}

throw Error('Not implemented');
if (other.constants.length !== this.constants.length) {
return false;
}

return other.constants.every((otherValue) => this.constants.some((value) => value.equals(otherValue)));
}

override toString(): string {
throw Error('Not implemented');
return `literal<${this.constants.join(', ')}>`;
}
}

Expand Down Expand Up @@ -300,10 +303,12 @@ export class StaticType extends Type {
}

export class UnionType extends Type {
override readonly isNullable = false;
override readonly isNullable: boolean;

constructor(readonly possibleTypes: Type[]) {
super();

this.isNullable = possibleTypes.some((it) => it.isNullable);
}

override copyWithNullability(_isNullable: boolean): UnionType {
Expand Down
42 changes: 39 additions & 3 deletions src/language/typing/safe-ds-class-hierarchy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,26 @@ export class SafeDsClassHierarchy {
}

/**
* Returns a stream of all superclasses of the given class. The class itself is not included in the stream unless
* there is a cycle in the inheritance hierarchy. Direct ancestors are returned first, followed by their ancestors
* and so on.
* Returns `true` if the given node is equal to or a subclass of the given other node. If one of the nodes is
* undefined, `false` is returned.
*/
isEqualToOrSubclassOf(node: SdsClass | undefined, other: SdsClass | undefined): boolean {
if (!node || !other) {
return false;
}

// Nothing is a subclass of everything
if (node === this.builtinClasses.Nothing) {
return true;
}

return node === other || this.streamSuperclasses(node).includes(other);
}

/**
* Returns a stream of all superclasses of the given class. Direct ancestors are returned first, followed by their
* ancestors and so on. The class itself is not included in the stream unless there is a cycle in the inheritance
* hierarchy.
*/
streamSuperclasses(node: SdsClass | undefined): Stream<SdsClass> {
if (!node) {
Expand Down Expand Up @@ -58,3 +75,22 @@ export class SafeDsClassHierarchy {
return undefined;
}
}

// fun SdsClass.superClassMembers() =
// this.superClasses().flatMap { it.classMembersOrEmpty().asSequence() }
//
// // TODO only static methods can be hidden
// fun SdsFunction.hiddenFunction(): SdsFunction? {
// val containingClassOrInterface = closestAncestorOrNull<SdsClass>() ?: return null
// return containingClassOrInterface.superClassMembers()
// .filterIsInstance<SdsFunction>()
// .firstOrNull { it.name == name }
// }
//
// fun SdsClass?.inheritedNonStaticMembersOrEmpty(): Set<SdsAbstractDeclaration> {
// return this?.parentClassesOrEmpty()
// ?.flatMap { it.classMembersOrEmpty() }
// ?.filter { it is SdsAttribute && !it.isStatic || it is SdsFunction && !it.isStatic }
// ?.toSet()
// .orEmpty()
// }
60 changes: 60 additions & 0 deletions src/language/typing/safe-ds-core-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { WorkspaceCache } from 'langium';
import { SafeDsServices } from '../safe-ds-module.js';
import { SafeDsClasses } from '../builtins/safe-ds-classes.js';
import { ClassType, Type, UnknownType } from './model.js';
import { SdsClass } from '../generated/ast.js';

export class SafeDsCoreTypes {
private readonly builtinClasses: SafeDsClasses;
private readonly cache: WorkspaceCache<string, Type>;

constructor(services: SafeDsServices) {
this.builtinClasses = services.builtins.Classes;
this.cache = new WorkspaceCache(services.shared);
}

get AnyOrNull(): Type {
return this.createCoreType(this.builtinClasses.Any, true);
}

get Boolean(): Type {
return this.createCoreType(this.builtinClasses.Boolean);
}

get Float(): Type {
return this.createCoreType(this.builtinClasses.Float);
}

get Int(): Type {
return this.createCoreType(this.builtinClasses.Int);
}

get List(): Type {
return this.createCoreType(this.builtinClasses.List);
}

get Map(): Type {
return this.createCoreType(this.builtinClasses.Map);
}

/* c8 ignore start */
get NothingOrNull(): Type {
return this.createCoreType(this.builtinClasses.Nothing, true);
}
/* c8 ignore stop */

get String(): Type {
return this.createCoreType(this.builtinClasses.String);
}

private createCoreType(coreClass: SdsClass | undefined, isNullable: boolean = false): Type {
/* c8 ignore start */
if (!coreClass) {
return UnknownType;
}
/* c8 ignore stop */

const key = `${coreClass.name}~${isNullable}`;
return this.cache.get(key, () => new ClassType(coreClass, isNullable));
}
}
Loading

0 comments on commit 1775705

Please sign in to comment.