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]>
lars-reimann and megalinter-bot authored Oct 21, 2023
1 parent ca47870 commit 1775705
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
@@ -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.
@@ -41,6 +43,8 @@ export type SafeDsAddedServices = {
};
types: {
ClassHierarchy: SafeDsClassHierarchy;
CoreTypes: SafeDsCoreTypes;
TypeChecker: SafeDsTypeChecker;
TypeComputer: SafeDsTypeComputer;
};
workspace: {
@@ -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: {
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 {
@@ -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 {
@@ -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(', ')}>`;
}
}

@@ -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 {
42 changes: 39 additions & 3 deletions src/language/typing/safe-ds-class-hierarchy.ts
Original file line number Diff line number Diff line change
@@ -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) {
@@ -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));
}
}
204 changes: 204 additions & 0 deletions src/language/typing/safe-ds-type-checker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { SafeDsServices } from '../safe-ds-module.js';
import {
CallableType,
ClassType,
EnumType,
EnumVariantType,
LiteralType,
NamedTupleType,
StaticType,
Type,
UnionType,
UnknownType,
} from './model.js';
import { SafeDsClassHierarchy } from './safe-ds-class-hierarchy.js';
import { SdsDeclaration } from '../generated/ast.js';
import {
BooleanConstant,
Constant,
FloatConstant,
IntConstant,
NullConstant,
StringConstant,
} from '../partialEvaluation/model.js';
import { SafeDsCoreTypes } from './safe-ds-core-types.js';

/* c8 ignore start */
export class SafeDsTypeChecker {
private readonly classHierarchy: SafeDsClassHierarchy;
private readonly coreTypes: SafeDsCoreTypes;

constructor(services: SafeDsServices) {
this.classHierarchy = services.types.ClassHierarchy;
this.coreTypes = services.types.CoreTypes;
}

/**
* Checks whether {@link type} is assignable {@link other}.
*/
isAssignableTo(type: Type, other: Type): boolean {
if (type instanceof CallableType) {
return this.callableTypeIsAssignableTo(type, other);
} else if (type instanceof ClassType) {
return this.classTypeIsAssignableTo(type, other);
} else if (type instanceof EnumType) {
return this.enumTypeIsAssignableTo(type, other);
} else if (type instanceof EnumVariantType) {
return this.enumVariantTypeIsAssignableTo(type, other);
} else if (type instanceof LiteralType) {
return this.literalTypeIsAssignableTo(type, other);
} else if (type instanceof NamedTupleType) {
return this.namedTupleTypeIsAssignableTo(type, other);
} else if (type instanceof StaticType) {
return this.staticTypeIsAssignableTo(type, other);
} else if (type instanceof UnionType) {
return this.unionTypeIsAssignableTo(type, other);
} else {
return false;
}
}

private callableTypeIsAssignableTo(type: CallableType, other: Type): boolean {
// return when (val unwrappedOther = unwrapVariadicType(other)) {
// is CallableType -> {
// // TODO: We need to compare names of parameters & results and can allow additional optional parameters
//
// // Sizes must match (too strict requirement -> should be loosened later)
// if (this.parameters.size != unwrappedOther.parameters.size || this.results.size != this.results.size) {
// return false
// }
//
// // Actual parameters must be supertypes of expected parameters (contravariance)
// this.parameters.zip(unwrappedOther.parameters).forEach { (thisParameter, otherParameter) ->
// if (!otherParameter.isSubstitutableFor(thisParameter)) {
// return false
// }
// }
//
// // Expected results must be subtypes of expected results (covariance)
// this.results.zip(unwrappedOther.results).forEach { (thisResult, otherResult) ->
// if (!thisResult.isSubstitutableFor(otherResult)) {
// return false
// }
// }
//
// true
// }
// is ClassType -> {
// unwrappedOther.sdsClass.qualifiedNameOrNull() == StdlibClasses.Any
// }
// is UnionType -> {
// unwrappedOther.possibleTypes.any { this.isSubstitutableFor(it) }
// }
// else -> false
// }
// }

return type.equals(other);
}

private classTypeIsAssignableTo(type: ClassType, other: Type): boolean {
if (type.isNullable && !other.isNullable) {
return false;
}

if (other instanceof ClassType) {
return this.classHierarchy.isEqualToOrSubclassOf(type.declaration, other.declaration);
} else if (other instanceof UnionType) {
return other.possibleTypes.some((it) => this.isAssignableTo(type, it));
} else {
return false;
}
}

private enumTypeIsAssignableTo(type: EnumType, other: Type): boolean {
// return when (val unwrappedOther = unwrapVariadicType(other)) {
// is ClassType -> {
// (!this.isNullable || unwrappedOther.isNullable) &&
// unwrappedOther.sdsClass.qualifiedNameOrNull() == StdlibClasses.Any
// }
// is EnumType -> {
// (!this.isNullable || unwrappedOther.isNullable) && this.sdsEnum == unwrappedOther.sdsEnum
// }
// is UnionType -> {
// unwrappedOther.possibleTypes.any { this.isSubstitutableFor(it) }
// }
// else -> false
// }

return type.equals(other);
}

private enumVariantTypeIsAssignableTo(type: EnumVariantType, other: Type): boolean {
// return when (val unwrappedOther = unwrapVariadicType(other)) {
// is ClassType -> {
// (!this.isNullable || unwrappedOther.isNullable) &&
// unwrappedOther.sdsClass.qualifiedNameOrNull() == StdlibClasses.Any
// }
// is EnumType -> {
// (!this.isNullable || unwrappedOther.isNullable) &&
// this.sdsEnumVariant in unwrappedOther.sdsEnum.variantsOrEmpty()
// }
// is EnumVariantType -> {
// (!this.isNullable || unwrappedOther.isNullable) && this.sdsEnumVariant == unwrappedOther.sdsEnumVariant
// }
// is UnionType -> unwrappedOther.possibleTypes.any { this.isSubstitutableFor(it) }
// else -> false
// }

return type.equals(other);
}

private literalTypeIsAssignableTo(type: LiteralType, other: Type): boolean {
if (type.isNullable && !other.isNullable) {
return false;
}

if (other instanceof ClassType) {
if (other.equals(this.coreTypes.AnyOrNull)) {
return true;
}

return type.constants.every((constant) => {
const constantType = this.constantToType(constant);
return this.isAssignableTo(constantType, other);
});
} else if (other instanceof LiteralType) {
return type.constants.every((constant) =>
other.constants.some((otherConstant) => constant.equals(otherConstant)),
);
} else {
// TODO: union type
return false;
}
}

private constantToType(constant: Constant): Type {
if (constant instanceof BooleanConstant) {
return this.coreTypes.Boolean;
} else if (constant instanceof FloatConstant) {
return this.coreTypes.Float;
} else if (constant instanceof IntConstant) {
return this.coreTypes.Int;
} else if (constant === NullConstant) {
return this.coreTypes.NothingOrNull;
} else if (constant instanceof StringConstant) {
return this.coreTypes.String;
} else {
return UnknownType;
}
}

private namedTupleTypeIsAssignableTo(type: NamedTupleType<SdsDeclaration>, other: Type): boolean {
return type.equals(other);
}

private staticTypeIsAssignableTo(type: Type, other: Type): boolean {
return type.equals(other);
}

private unionTypeIsAssignableTo(type: UnionType, other: Type): boolean {
return type.equals(other);
}
}
/* c8 ignore stop */
128 changes: 42 additions & 86 deletions src/language/typing/safe-ds-type-computer.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { AstNode, AstNodeLocator, getContainerOfType, getDocument, WorkspaceCache } from 'langium';
import { SafeDsServices } from '../safe-ds-module.js';
import { SafeDsClasses } from '../builtins/safe-ds-classes.js';
import {
CallableType,
ClassType,
EnumType,
EnumVariantType,
LiteralType,
NamedTupleEntry,
NamedTupleType,
NamedType,
@@ -22,7 +22,6 @@ import {
isSdsAssignment,
isSdsAttribute,
isSdsBlockLambda,
isSdsBoolean,
isSdsCall,
isSdsCallable,
isSdsCallableType,
@@ -32,11 +31,9 @@ import {
isSdsEnumVariant,
isSdsExpression,
isSdsExpressionLambda,
isSdsFloat,
isSdsFunction,
isSdsIndexedAccess,
isSdsInfixOperation,
isSdsInt,
isSdsLambda,
isSdsList,
isSdsLiteralType,
@@ -45,15 +42,13 @@ import {
isSdsMemberType,
isSdsNamedType,
isSdsNamedTypeDeclaration,
isSdsNull,
isSdsParameter,
isSdsParenthesizedExpression,
isSdsPipeline,
isSdsPrefixOperation,
isSdsReference,
isSdsResult,
isSdsSegment,
isSdsString,
isSdsTemplateString,
isSdsType,
isSdsTypeProjection,
@@ -63,12 +58,12 @@ import {
SdsAssignee,
SdsCall,
SdsCallableType,
SdsClass,
SdsDeclaration,
SdsExpression,
SdsFunction,
SdsIndexedAccess,
SdsInfixOperation,
SdsLiteralType,
SdsMemberAccess,
SdsParameter,
SdsPrefixOperation,
@@ -80,26 +75,30 @@ import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
import {
assigneesOrEmpty,
blockLambdaResultsOrEmpty,
literalsOrEmpty,
parametersOrEmpty,
resultsOrEmpty,
typeArgumentsOrEmpty,
} from '../helpers/nodeProperties.js';
import { isEmpty } from 'radash';
import { SafeDsPartialEvaluator } from '../partialEvaluation/safe-ds-partial-evaluator.js';
import { Constant, isConstant } from '../partialEvaluation/model.js';
import { SafeDsCoreTypes } from './safe-ds-core-types.js';

export class SafeDsTypeComputer {
private readonly astNodeLocator: AstNodeLocator;
private readonly builtinClasses: SafeDsClasses;
private readonly coreTypes: SafeDsCoreTypes;
private readonly nodeMapper: SafeDsNodeMapper;
private readonly partialEvaluator: SafeDsPartialEvaluator;

private readonly coreTypeCache: WorkspaceCache<string, Type>;
private readonly nodeTypeCache: WorkspaceCache<string, Type>;

constructor(services: SafeDsServices) {
this.astNodeLocator = services.workspace.AstNodeLocator;
this.builtinClasses = services.builtins.Classes;
this.coreTypes = services.types.CoreTypes;
this.nodeMapper = services.helpers.NodeMapper;
this.partialEvaluator = services.evaluation.PartialEvaluator;

this.coreTypeCache = new WorkspaceCache(services.shared);
this.nodeTypeCache = new WorkspaceCache(services.shared);
}

@@ -257,23 +256,19 @@ export class SafeDsTypeComputer {
}

private computeTypeOfExpression(node: SdsExpression): Type {
// Partial evaluation (definitely handles SdsBoolean, SdsFloat, SdsInt, SdsNull, and SdsString)
const evaluatedNode = this.partialEvaluator.evaluate(node);
if (evaluatedNode instanceof Constant) {
return new LiteralType([evaluatedNode]);
}

// Terminal cases
if (isSdsBoolean(node)) {
return this.Boolean;
} else if (isSdsFloat(node)) {
return this.Float;
} else if (isSdsInt(node)) {
return this.Int;
} else if (isSdsList(node)) {
return this.List;
if (isSdsList(node)) {
return this.coreTypes.List;
} else if (isSdsMap(node)) {
return this.Map;
} else if (isSdsNull(node)) {
return this.NothingOrNull;
} else if (isSdsString(node)) {
return this.String;
return this.coreTypes.Map;
} else if (isSdsTemplateString(node)) {
return this.String;
return this.coreTypes.String;
}

// Recursive cases
@@ -306,21 +301,21 @@ export class SafeDsTypeComputer {
// Boolean operators
case 'or':
case 'and':
return this.Boolean;
return this.coreTypes.Boolean;

// Equality operators
case '==':
case '!=':
case '===':
case '!==':
return this.Boolean;
return this.coreTypes.Boolean;

// Comparison operators
case '<':
case '<=':
case '>=':
case '>':
return this.Boolean;
return this.coreTypes.Boolean;

// Arithmetic operators
case '+':
@@ -345,7 +340,7 @@ export class SafeDsTypeComputer {
} else if (isSdsPrefixOperation(node)) {
switch (node.operator) {
case 'not':
return this.Boolean;
return this.coreTypes.Boolean;
case '-':
return this.computeTypeOfArithmeticPrefixOperation(node);

@@ -380,9 +375,9 @@ export class SafeDsTypeComputer {

private computeTypeOfIndexedAccess(node: SdsIndexedAccess): Type {
const receiverType = this.computeType(node.receiver);
if (receiverType.equals(this.List) || receiverType.equals(this.Map)) {
if (receiverType.equals(this.coreTypes.List) || receiverType.equals(this.coreTypes.Map)) {
// TODO: access type arguments
return this.AnyOrNull();
return this.coreTypes.AnyOrNull;
} else {
return UnknownType;
}
@@ -392,10 +387,10 @@ export class SafeDsTypeComputer {
const leftOperandType = this.computeType(node.leftOperand);
const rightOperandType = this.computeType(node.rightOperand);

if (leftOperandType.equals(this.Int) && rightOperandType.equals(this.Int)) {
return this.Int;
if (leftOperandType.equals(this.coreTypes.Int) && rightOperandType.equals(this.coreTypes.Int)) {
return this.coreTypes.Int;
} else {
return this.Float;
return this.coreTypes.Float;
}
}

@@ -429,10 +424,10 @@ export class SafeDsTypeComputer {
private computeTypeOfArithmeticPrefixOperation(node: SdsPrefixOperation): Type {
const leftOperandType = this.computeType(node.operand);

if (leftOperandType.equals(this.Int)) {
return this.Int;
if (leftOperandType.equals(this.coreTypes.Int)) {
return this.coreTypes.Int;
} else {
return this.Float;
return this.coreTypes.Float;
}
}

@@ -451,8 +446,7 @@ export class SafeDsTypeComputer {
if (isSdsCallableType(node)) {
return this.computeTypeOfCallableWithManifestTypes(node);
} else if (isSdsLiteralType(node)) {
/* c8 ignore next */
return NotImplementedType;
return this.computeTypeOfLiteralType(node);
} else if (isSdsMemberType(node)) {
return this.computeType(node.member);
} else if (isSdsNamedType(node)) {
@@ -465,6 +459,15 @@ export class SafeDsTypeComputer {
} /* c8 ignore stop */
}

private computeTypeOfLiteralType(node: SdsLiteralType): Type {
const constants = literalsOrEmpty(node).map((it) => this.partialEvaluator.evaluate(it));
if (constants.every(isConstant)) {
return new LiteralType(constants);
} /* c8 ignore start */ else {
return UnknownType;
} /* c8 ignore stop */
}

// -----------------------------------------------------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------------------------------------------------
@@ -527,51 +530,4 @@ export class SafeDsTypeComputer {
//
// return otherTypes.all { it.isSubstitutableFor(candidate) }
// }

// -----------------------------------------------------------------------------------------------------------------
// Builtin types
// -----------------------------------------------------------------------------------------------------------------

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);
}

get NothingOrNull(): Type {
return this.createCoreType(this.builtinClasses.Nothing, true);
}

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.coreTypeCache.get(key, () => new ClassType(coreClass, isNullable));
}
}
9 changes: 8 additions & 1 deletion src/language/validation/experimentalLanguageFeatures.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SdsIndexedAccess, SdsMap } from '../generated/ast.js';
import { SdsIndexedAccess, SdsLiteralType, SdsMap } from '../generated/ast.js';
import { ValidationAcceptor } from 'langium';

export const CODE_EXPERIMENTAL_LANGUAGE_FEATURE = 'experimental/language-feature';
@@ -10,6 +10,13 @@ export const indexedAccessesShouldBeUsedWithCaution = (node: SdsIndexedAccess, a
});
};

export const literalTypesShouldBeUsedWithCaution = (node: SdsLiteralType, accept: ValidationAcceptor): void => {
accept('warning', 'Literal types are experimental and may change without prior notice.', {
node,
code: CODE_EXPERIMENTAL_LANGUAGE_FEATURE,
});
};

export const mapsShouldBeUsedWithCaution = (node: SdsMap, accept: ValidationAcceptor): void => {
accept('warning', 'Map literals are experimental and may change without prior notice.', {
node,
5 changes: 4 additions & 1 deletion src/language/validation/other/expressions/infixOperations.ts
Original file line number Diff line number Diff line change
@@ -7,7 +7,9 @@ import { UnknownType } from '../../../typing/model.js';
export const CODE_INFIX_OPERATION_DIVISION_BY_ZERO = 'infix-operation/division-by-zero';

export const divisionDivisorMustNotBeZero = (services: SafeDsServices) => {
const coreTypes = services.types.CoreTypes;
const partialEvaluator = services.evaluation.PartialEvaluator;
const typeChecker = services.types.TypeChecker;
const typeComputer = services.types.TypeComputer;

const zeroInt = new IntConstant(0n);
@@ -22,7 +24,8 @@ export const divisionDivisorMustNotBeZero = (services: SafeDsServices) => {
const dividendType = typeComputer.computeType(node.leftOperand);
if (
dividendType === UnknownType ||
(!dividendType.equals(typeComputer.Int) && !dividendType.equals(typeComputer.Float))
(!typeChecker.isAssignableTo(dividendType, coreTypes.Float) &&
!typeChecker.isAssignableTo(dividendType, coreTypes.Int))
) {
return;
}
40 changes: 40 additions & 0 deletions src/language/validation/other/types/literalTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { isSdsList, isSdsMap, SdsLiteralType } from '../../../generated/ast.js';
import { ValidationAcceptor } from 'langium';
import { literalsOrEmpty } from '../../../helpers/nodeProperties.js';
import { isEmpty } from 'radash';

export const CODE_UNION_TYPE_MISSING_LITERALS = 'union-type/missing-literals';
export const CODE_LITERAL_TYPE_LIST_LITERAL = 'literal-type/list-literal';
export const CODE_LITERAL_TYPE_MAP_LITERAL = 'literal-type/map-literal';

export const literalTypeMustHaveLiterals = (node: SdsLiteralType, accept: ValidationAcceptor): void => {
if (isEmpty(literalsOrEmpty(node))) {
accept('error', 'A literal type must have at least one literal.', {
node,
property: 'literalList',
code: CODE_UNION_TYPE_MISSING_LITERALS,
});
}
};

export const literalTypeMustNotContainListLiteral = (node: SdsLiteralType, accept: ValidationAcceptor): void => {
for (const literal of literalsOrEmpty(node)) {
if (isSdsList(literal)) {
accept('error', 'Literal types must not contain list literals.', {
node: literal,
code: CODE_LITERAL_TYPE_LIST_LITERAL,
});
}
}
};

export const literalTypeMustNotContainMapLiteral = (node: SdsLiteralType, accept: ValidationAcceptor): void => {
for (const literal of literalsOrEmpty(node)) {
if (isSdsMap(literal)) {
accept('error', 'Literal types must not contain map literals.', {
node: literal,
code: CODE_LITERAL_TYPE_MAP_LITERAL,
});
}
}
};
2 changes: 1 addition & 1 deletion src/language/validation/other/types/unionTypes.ts
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ export const CODE_UNION_TYPE_MISSING_TYPE_ARGUMENTS = 'union-type/missing-type-a

export const unionTypeMustHaveTypeArguments = (node: SdsUnionType, accept: ValidationAcceptor): void => {
if (isEmpty(typeArgumentsOrEmpty(node.typeArgumentList))) {
accept('error', 'A union type must have least one type argument.', {
accept('error', 'A union type must have at least one type argument.', {
node,
property: 'typeArgumentList',
code: CODE_UNION_TYPE_MISSING_TYPE_ARGUMENTS,
17 changes: 16 additions & 1 deletion src/language/validation/safe-ds-validator.ts
Original file line number Diff line number Diff line change
@@ -90,7 +90,11 @@ import {
lambdaMustBeAssignedToTypedParameter,
lambdaParameterMustNotHaveConstModifier,
} from './other/expressions/lambdas.js';
import { indexedAccessesShouldBeUsedWithCaution, mapsShouldBeUsedWithCaution } from './experimentalLanguageFeatures.js';
import {
indexedAccessesShouldBeUsedWithCaution,
literalTypesShouldBeUsedWithCaution,
mapsShouldBeUsedWithCaution,
} from './experimentalLanguageFeatures.js';
import { requiredParameterMustNotBeExpert } from './builtins/expert.js';
import {
annotationCallArgumentsMustBeConstant,
@@ -116,6 +120,11 @@ import { pythonModuleShouldDifferFromSafeDsPackage } from './builtins/pythonModu
import { divisionDivisorMustNotBeZero } from './other/expressions/infixOperations.js';
import { constantParameterMustHaveConstantDefaultValue } from './other/declarations/parameters.js';
import { callArgumentsMustBeConstantIfParameterIsConstant } from './other/expressions/calls.js';
import {
literalTypeMustHaveLiterals,
literalTypeMustNotContainListLiteral,
literalTypeMustNotContainMapLiteral,
} from './other/types/literalTypes.js';

/**
* Register custom validation checks.
@@ -197,6 +206,12 @@ export const registerValidationChecks = function (services: SafeDsServices) {
lambdaParametersMustNotBeAnnotated,
lambdaParameterMustNotHaveConstModifier,
],
SdsLiteralType: [
literalTypeMustHaveLiterals,
literalTypeMustNotContainListLiteral,
literalTypeMustNotContainMapLiteral,
literalTypesShouldBeUsedWithCaution,
],
SdsMap: [mapsShouldBeUsedWithCaution],
SdsMemberAccess: [
memberAccessMustBeNullSafeIfReceiverIsNullable(services),
46 changes: 46 additions & 0 deletions tests/language/typing/safe-ds-class-hierarchy.test.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import { isSdsClass, SdsClass } from '../../../src/language/generated/ast.js';
import { getNodeOfType } from '../../helpers/nodeFinder.js';

const services = createSafeDsServices(NodeFileSystem).SafeDs;
const builtinClasses = services.builtins.Classes;
const classHierarchy = services.types.ClassHierarchy;

describe('SafeDsClassHierarchy', async () => {
@@ -18,6 +19,51 @@ describe('SafeDsClassHierarchy', async () => {
await clearDocuments(services);
});

describe('isEqualToOrSubclassOf', () => {
const testCases = [
{
testName: 'should return false if node is undefined',
node: () => undefined,
other: () => builtinClasses.Any,
expected: false,
},
{
testName: 'should return false if other is undefined',
node: () => builtinClasses.Nothing,
other: () => undefined,
expected: false,
},
{
testName: 'should return false if node and other are undefined',
node: () => undefined,
other: () => undefined,
expected: false,
},
{
testName: 'should return true if node is Nothing',
node: () => builtinClasses.Nothing,
other: () => builtinClasses.Any,
expected: true,
},
{
testName: 'should return true if node and other are equal',
node: () => builtinClasses.Any,
other: () => builtinClasses.Any,
expected: true,
},
{
testName: 'should return true if node is a subclass of other',
node: () => builtinClasses.Int,
other: () => builtinClasses.Any,
expected: true,
},
];

it.each(testCases)('$testName', async ({ node, other, expected }) => {
expect(classHierarchy.isEqualToOrSubclassOf(node(), other())).toStrictEqual(expected);
});
});

describe('streamSuperclasses', () => {
const superclassNames = (node: SdsClass | undefined) =>
classHierarchy
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// $TEST$ syntax_error

segment mySegment(
x: literal
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// $TEST$ no_syntax_error

segment mySegment(
x: literal<[]>
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// $TEST$ no_syntax_error

segment mySegment(
x: literal<{}>
) {}
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ segment mySegment() -> (r: Int) {
};

() {
// $TEST$ serialization Int
// $TEST$ serialization literal<1>
// $TEST$ serialization $Unknown
yield »r«, yield »s« = 1;
};
2 changes: 1 addition & 1 deletion tests/resources/typing/assignees/placeholders/main.sdstest
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ segment mySegment1() -> (r: Int) {
}

segment mySegment2() -> (r: Int, s: String) {
// $TEST$ serialization Int
// $TEST$ serialization literal<1>
// $TEST$ serialization $Unknown
val »r«, val »s« = 1;
}
2 changes: 1 addition & 1 deletion tests/resources/typing/assignees/yields/main.sdstest
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ segment mySegment1() -> (r: Int) {
}

segment mySegment2() -> (r: Int, s: String) {
// $TEST$ serialization Int
// $TEST$ serialization literal<1>
// $TEST$ serialization $Unknown
»yield r«, »yield s« = 1;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package tests.typing.expressions.blockLambdas.thatAreIsolated

fun g() -> r: Int

segment mySegment() {
// $TEST$ serialization (p: $Unknown) -> (r: Int, s: $Unknown)
// $TEST$ serialization (p: $Unknown) -> (r: literal<1>, s: $Unknown)
»(p) {
yield r, yield s = 1;
}«;

// $TEST$ serialization (p: $Unknown) -> (r: Int, s: $Unknown)
val f = »(p) {
yield r, yield s = 1;
yield r, yield s = g();
}«;
}
Original file line number Diff line number Diff line change
@@ -6,40 +6,40 @@ fun normalFunction(param: Int)
fun parameterlessFunction()

segment mySegment() {
// $TEST$ serialization (p: String) -> (r: Int, s: String)
// $TEST$ serialization (p: String) -> (r: literal<1>, s: literal<"">)
higherOrderFunction1(»(p) {
yield r = 1;
yield s = "";
}«);

// $TEST$ serialization (p: String) -> (r: Int, s: $Unknown)
// $TEST$ serialization (p: String) -> (r: literal<1>, s: $Unknown)
higherOrderFunction1(param = »(p) {
yield r, yield s = 1;
}«);

// $TEST$ serialization (p: $Unknown) -> (r: Int, s: String)
// $TEST$ serialization (p: $Unknown) -> (r: literal<1>, s: literal<"">)
higherOrderFunction2(»(p) {
yield r = 1;
yield s = "";
}«);

// $TEST$ serialization (p: $Unknown) -> (r: Int, s: $Unknown)
// $TEST$ serialization (p: $Unknown) -> (r: literal<1>, s: $Unknown)
higherOrderFunction2(param = »(p) {
yield r, yield s = 1;
}«);

// $TEST$ serialization (p: $Unknown) -> (r: Int, s: String)
// $TEST$ serialization (p: $Unknown) -> (r: literal<1>, s: literal<"">)
normalFunction(»(p) {
yield r = 1;
yield s = "";
}«);

// $TEST$ serialization (p: $Unknown) -> (r: Int, s: $Unknown)
// $TEST$ serialization (p: $Unknown) -> (r: literal<1>, s: $Unknown)
normalFunction(param = »(p) {
yield r, yield s = 1;
}«);

// $TEST$ serialization (p: $Unknown) -> (r: Int, s: $Unknown)
// $TEST$ serialization (p: $Unknown) -> (r: literal<1>, s: $Unknown)
parameterlessFunction(»(p) {
yield r, yield s = 1;
}«);
Original file line number Diff line number Diff line change
@@ -5,19 +5,19 @@ segment mySegment() -> (
s: () -> (),
t: Int,
) {
// $TEST$ serialization (p: String) -> (r: Int, s: String)
// $TEST$ serialization (p: String) -> (r: literal<1>, s: literal<"">)
yield r = »(p) {
yield r = 1;
yield s = "";
}«;

// $TEST$ serialization (p: $Unknown) -> (r: Int, s: String)
// $TEST$ serialization (p: $Unknown) -> (r: literal<1>, s: literal<"">)
yield s = »(p) {
yield r = 1;
yield s = "";
}«;

// $TEST$ serialization (p: $Unknown) -> (r: Int, s: $Unknown)
// $TEST$ serialization (p: $Unknown) -> (r: literal<1>, s: $Unknown)
yield t = »(p) {
yield r, yield s = 1;
}«;
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package tests.typing.expressions.blockLambdas.withManifestTypes

segment mySegment() {
// $TEST$ serialization (p: Int) -> (r: Int, s: String)
// $TEST$ serialization (p: Int) -> (r: literal<1>, s: literal<"">)
»(p: Int) {
yield r = 1;
yield s = "";
}«;

// $TEST$ serialization (p: String) -> (r: Int, s: $Unknown)
// $TEST$ serialization (p: String) -> (r: literal<1>, s: $Unknown)
»(p: String) {
yield r, yield s = 1;
}«;
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package tests.typing.expressions.calls.ofBlockLambdas

pipeline myPipeline {
// $TEST$ serialization String
// $TEST$ serialization literal<"">
»(() {
yield r = "";
})()«;

// $TEST$ serialization (r: String, s: Int)
// $TEST$ serialization (r: literal<"">, s: literal<1>)
»(() {
yield r = "";
yield s = 1;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package tests.typing.expressions.calls.ofExpressionLambdas

pipeline myPipeline {
// $TEST$ serialization Int
// $TEST$ serialization literal<1>
»(() -> 1)()«;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package tests.typing.expressions.expressionLambdas.thatAreIsolated

fun g() -> r: Int

segment mySegment() {
// $TEST$ serialization (p: $Unknown) -> (result: Int)
»(p) -> 1«;
»(p) -> g()«;

// $TEST$ serialization (p: $Unknown) -> (result: Int)
// $TEST$ serialization (p: $Unknown) -> (result: literal<1>)
val f = »(p) -> 1«;
}
Original file line number Diff line number Diff line change
@@ -6,24 +6,24 @@ fun normalFunction(param: Int)
fun parameterlessFunction()

segment mySegment() {
// $TEST$ serialization (p: String) -> (result: Int)
// $TEST$ serialization (p: String) -> (result: literal<1>)
higherOrderFunction1(»(p) -> 1«);

// $TEST$ serialization (p: String) -> (result: Int)
// $TEST$ serialization (p: String) -> (result: literal<1>)
higherOrderFunction1(param = »(p) -> 1«);

// $TEST$ serialization (p: $Unknown) -> (result: Int)
// $TEST$ serialization (p: $Unknown) -> (result: literal<1>)
higherOrderFunction2(»(p) -> 1«);

// $TEST$ serialization (p: $Unknown) -> (result: Int)
// $TEST$ serialization (p: $Unknown) -> (result: literal<1>)
higherOrderFunction2(param = »(p) -> 1«);

// $TEST$ serialization (p: $Unknown) -> (result: Int)
// $TEST$ serialization (p: $Unknown) -> (result: literal<1>)
normalFunction(»(p) -> 1«);

// $TEST$ serialization (p: $Unknown) -> (result: Int)
// $TEST$ serialization (p: $Unknown) -> (result: literal<1>)
normalFunction(param = »(p) -> 1«);

// $TEST$ serialization (p: $Unknown) -> (result: Int)
// $TEST$ serialization (p: $Unknown) -> (result: literal<1>)
parameterlessFunction(»(p) -> 1«);
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package tests.typing.expressions.expressionLambdas.thatAreYielded

segment mySegment() -> (
r: (p: String) -> (),
r: (p: String) -> (r: Int),
s: () -> (),
t: Int,
) {
// $TEST$ serialization (p: String) -> (result: Int)
// $TEST$ serialization (p: String) -> (result: literal<1>)
yield r = »(p) -> 1«;

// $TEST$ serialization (p: $Unknown) -> (result: Int)
// $TEST$ serialization (p: $Unknown) -> (result: literal<1>)
yield s = »(p) -> 1«;

// $TEST$ serialization (p: $Unknown) -> (result: Int)
// $TEST$ serialization (p: $Unknown) -> (result: literal<1>)
yield t = »(p) -> 1«;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package tests.typing.expressions.expressionLambdas.withManifestTypes

segment mySegment() {
// $TEST$ serialization (p: Int) -> (result: Int)
// $TEST$ serialization (p: Int) -> (result: literal<1>)
»(p: Int) -> 1«;
}
12 changes: 6 additions & 6 deletions tests/resources/typing/expressions/literals/main.sdstest
Original file line number Diff line number Diff line change
@@ -2,18 +2,18 @@ package tests.typing.expressions.literals

pipeline myPipeline {

// $TEST$ serialization Boolean
// $TEST$ serialization literal<true>
val booleanLiteral = »true«;

// $TEST$ serialization Float
val floatLiteral = »1.0«;
// $TEST$ serialization literal<1.5>
val floatLiteral = »1.5«;

// $TEST$ serialization Int
// $TEST$ serialization literal<1>
val intLiteral = »1«;

// $TEST$ serialization Nothing?
// $TEST$ serialization literal<null>
val nullLiteral = »null«;

// $TEST$ serialization String
// $TEST$ serialization literal<"myString">
val stringLiteral = »"myString"«;
}
100 changes: 84 additions & 16 deletions tests/resources/typing/expressions/operations/arithmetic/main.sdstest
Original file line number Diff line number Diff line change
@@ -1,36 +1,104 @@
package tests.typing.operations.arithmetic

pipeline myPipeline {
// $TEST$ serialization Int
fun anyInt() -> r: Int
fun anyFloat() -> r: Float

pipeline constantOperands {
// $TEST$ serialization literal<2>
val additionIntInt = »1 + 1«;
// $TEST$ serialization Int
// $TEST$ serialization literal<0>
val subtractionIntInt = »1 - 1«;
// $TEST$ serialization Int
// $TEST$ serialization literal<1>
val multiplicationIntInt = »1 * 1«;
// $TEST$ serialization Int
// $TEST$ serialization literal<1>
val divisionIntInt = »1 / 1«;
// $TEST$ serialization Int

// $TEST$ serialization literal<2.5>
val additionIntFloat = »1 + 1.5«;
// $TEST$ serialization literal<-0.5>
val subtractionIntFloat = »1 - 1.5«;
// $TEST$ serialization literal<1.5>
val multiplicationIntFloat = »1 * 1.5«;
// $TEST$ serialization literal<1.6>
val divisionIntFloat = »1 / 0.625«;

// $TEST$ serialization literal<2.5>
val additionFloatInt = »1.5 + 1«;
// $TEST$ serialization literal<0.5>
val subtractionFloatInt = »1.5 - 1«;
// $TEST$ serialization literal<1.5>
val multiplicationFloatInt = »1.5 * 1«;
// $TEST$ serialization literal<1.5>
val divisionFloatInt = »1.5 / 1«;

// $TEST$ serialization literal<2.75>
val additionFloatFloat = »1.5 + 1.25«;
// $TEST$ serialization literal<0.25>
val subtractionFloatFloat = »1.5 - 1.25«;
// $TEST$ serialization literal<1.875>
val multiplicationFloatFloat = »1.5 * 1.25«;
// $TEST$ serialization literal<0.6>
val divisionFloatFloat = »1.5 / 2.5«;

// $TEST$ serialization literal<-1>
val negationInt = »-1«;
// $TEST$ serialization literal<-1.5>
val negationFloat = »-1.5«;
}

pipeline invalidOperands {
// $TEST$ serialization Float
val additionInvalid = »true + true«;
// $TEST$ serialization Float
val subtractionInvalid = »true - true«;
// $TEST$ serialization Float
val multiplicationInvalid = »true * true«;
// $TEST$ serialization Float
val divisionInvalid = »true / true«;

// $TEST$ serialization Float
val additionIntFloat = »1 + 1.0«;
val negationInvalid = »-true«;
}

pipeline nonConstantOperands {
// $TEST$ serialization Int
val additionIntInt = »anyInt() + anyInt()«;
// $TEST$ serialization Int
val subtractionIntInt = »anyInt() - anyInt()«;
// $TEST$ serialization Int
val multiplicationIntInt = »anyInt() * anyInt()«;
// $TEST$ serialization Int
val divisionIntInt = »anyInt() / anyInt()«;

// $TEST$ serialization Float
val subtractionIntFloat = »1 - 1.0«;
val additionIntFloat = »anyInt() + anyFloat()«;
// $TEST$ serialization Float
val multiplicationIntFloat = »1 * 1.0«;
val subtractionIntFloat = »anyInt() - anyFloat()«;
// $TEST$ serialization Float
val divisionIntFloat = »1 / 1.0«;
val multiplicationIntFloat = »anyInt() * anyFloat()«;
// $TEST$ serialization Float
val negationFloat = »-1.0«;
val divisionIntFloat = »anyInt() / anyFloat()«;

// $TEST$ serialization Float
val additionInvalid = »true + true«;
val additionFloatInt = »anyFloat() + anyInt()«;
// $TEST$ serialization Float
val subtractionInvalid = »true - true«;
val subtractionFloatInt = »anyFloat() - anyInt()«;
// $TEST$ serialization Float
val multiplicationInvalid = »true * true«;
val multiplicationFloatInt = »anyFloat() * anyInt()«;
// $TEST$ serialization Float
val divisionInvalid = »true / true«;
val divisionFloatInt = »anyFloat() / anyInt()«;

// $TEST$ serialization Float
val negationInvalid = »-true«;
val additionFloatFloat = »anyFloat() + anyFloat()«;
// $TEST$ serialization Float
val subtractionFloatFloat = »anyFloat() - anyFloat()«;
// $TEST$ serialization Float
val multiplicationFloatFloat = »anyFloat() * anyFloat()«;
// $TEST$ serialization Float
val divisionFloatFloat = »anyFloat() / anyFloat()«;

// $TEST$ serialization Int
val negationInt = »-anyInt()«;
// $TEST$ serialization Float
val negationFloat = »-anyFloat()«;
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package tests.typing.operations.comparison

pipeline myPipeline {
// $TEST$ serialization Boolean
// $TEST$ serialization literal<false>
val lessThan = »1 < 1«;
// $TEST$ serialization Boolean
// $TEST$ serialization literal<true>
val lessThanOrEquals = »1 <= 1«;
// $TEST$ serialization Boolean
// $TEST$ serialization literal<true>
val greaterThanOrEquals = »1 >= 1«;
// $TEST$ serialization Boolean
// $TEST$ serialization literal<false>
val greaterThan = »1 > 1«;

// $TEST$ serialization Boolean
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package tests.typing.operations.equality

pipeline myPipeline {
// $TEST$ serialization Boolean
// $TEST$ serialization literal<true>
val equals = (»1 == 1«);
// $TEST$ serialization Boolean
// $TEST$ serialization literal<false>
val notEquals = (»1 != 1«);

// $TEST$ serialization literal<true>
val identicalTo = (»1 === 1«);
// $TEST$ serialization literal<false>
val notIdenticalTo = (»1 !== 1«);

// $TEST$ serialization Boolean
val nonConstantEquals = (»1 == unresolved«);
// $TEST$ serialization Boolean
val nonConstantNotEquals = (»1 != unresolved«);
// $TEST$ serialization Boolean
val strictlyEquals = (»1 === 1«);
val nonConstantIdenticalTo = (»1 === unresolved«);
// $TEST$ serialization Boolean
val notStrictlyEquals = (»1 !== 1«);
val nonConstantNotIdenticalTo = (»1 !== unresolved«);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package tests.typing.operations.logical

pipeline myPipeline {
// $TEST$ serialization Boolean
// $TEST$ serialization literal<true>
val conjunction = »true and true«;
// $TEST$ serialization Boolean
// $TEST$ serialization literal<true>
val disjunction = »true or true«;
// $TEST$ serialization Boolean
// $TEST$ serialization literal<false>
val negation = »not true«;

// $TEST$ serialization Boolean
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package tests.typing.expressions.templateStrings

pipeline myPipeline {
// $TEST$ serialization literal<"1 + 2 = 3">
val valid = »"1 + 2 = {{ 1 + 2 }}"«;

// $TEST$ serialization String
val templateString = »"1 + 2 = {{ 1 + 2 }}"«;
val invalid = »"1 + 2 = {{ unresolved }}"«;
}
10 changes: 10 additions & 0 deletions tests/resources/typing/types/literal types/main.sdstest
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package tests.typing.types.literalTypes

// $TEST$ serialization literal<>
fun myFunction1(f: »literal<>«)

// $TEST$ serialization literal<1, 2>
fun myFunction2(f: »literal<1, 2>«)

// $TEST$ serialization literal<1, "">
fun myFunction3(f: »literal<1, "">«)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package tests.validation.experimentalLanguageFeature.literalTypes

fun myFunction(
// $TEST$ warning "Literal types are experimental and may change without prior notice."
p: »literal<>«
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package tests.validation.other.types.literalTypes.mustHaveLiterals

// $TEST$ error "A literal type must have at least one literal."
segment mySegment1(
p: literal»<>«
) {}

// $TEST$ no error "A literal type must have at least one literal."
segment mySegment2(
p: literal»<1>«
) {}

// $TEST$ no error "A literal type must have at least one literal."
segment mySegment3(
p: literal»<1, "">«
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package tests.validation.other.types.literalTypes.mustNotContanListLiterals

// $TEST$ error "Literal types must not contain list literals."
segment mySegment(
x: literal<»[]«>
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package tests.validation.other.types.literalsTypes.mustNotContainMapLiterals

// $TEST$ error "Literal types must not contain map literals."
segment mySegment(
x: literal<»{}«>
) {}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package tests.validation.other.types.unionTypes.mustHaveTypeArguments

// $TEST$ error "A union type must have least one type argument."
// $TEST$ error "A union type must have at least one type argument."
segment mySegment1(
p: union»<>«
) {}

// $TEST$ no error "A union type must have least one type argument."
// $TEST$ no error "A union type must have at least one type argument."
segment mySegment2(
p: union»<Int>«
) {}

// $TEST$ no error "A union type must have least one type argument."
// $TEST$ no error "A union type must have at least one type argument."
segment mySegment3(
p: union»<Int, Float>«
) {}

0 comments on commit 1775705

Please sign in to comment.