diff --git a/src/language/safe-ds-module.ts b/src/language/safe-ds-module.ts index 3c16c02db..d88f6d850 100644 --- a/src/language/safe-ds-module.ts +++ b/src/language/safe-ds-module.ts @@ -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 new SafeDsClassHierarchy(services), + CoreTypes: (services) => new SafeDsCoreTypes(services), + TypeChecker: (services) => new SafeDsTypeChecker(services), TypeComputer: (services) => new SafeDsTypeComputer(services), }, workspace: { diff --git a/src/language/typing/model.ts b/src/language/typing/model.ts index 7ec804e6a..00bcb3702 100644 --- a/src/language/typing/model.ts +++ b/src/language/typing/model.ts @@ -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 { diff --git a/src/language/typing/safe-ds-class-hierarchy.ts b/src/language/typing/safe-ds-class-hierarchy.ts index 36b482d33..6b37e5f63 100644 --- a/src/language/typing/safe-ds-class-hierarchy.ts +++ b/src/language/typing/safe-ds-class-hierarchy.ts @@ -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 { 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() ?: return null +// return containingClassOrInterface.superClassMembers() +// .filterIsInstance() +// .firstOrNull { it.name == name } +// } +// +// fun SdsClass?.inheritedNonStaticMembersOrEmpty(): Set { +// return this?.parentClassesOrEmpty() +// ?.flatMap { it.classMembersOrEmpty() } +// ?.filter { it is SdsAttribute && !it.isStatic || it is SdsFunction && !it.isStatic } +// ?.toSet() +// .orEmpty() +// } diff --git a/src/language/typing/safe-ds-core-types.ts b/src/language/typing/safe-ds-core-types.ts new file mode 100644 index 000000000..09b3d167e --- /dev/null +++ b/src/language/typing/safe-ds-core-types.ts @@ -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; + + 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)); + } +} diff --git a/src/language/typing/safe-ds-type-checker.ts b/src/language/typing/safe-ds-type-checker.ts new file mode 100644 index 000000000..6b859e6d4 --- /dev/null +++ b/src/language/typing/safe-ds-type-checker.ts @@ -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, 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 */ diff --git a/src/language/typing/safe-ds-type-computer.ts b/src/language/typing/safe-ds-type-computer.ts index f52b9fc33..90b432b81 100644 --- a/src/language/typing/safe-ds-type-computer.ts +++ b/src/language/typing/safe-ds-type-computer.ts @@ -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,7 +42,6 @@ import { isSdsMemberType, isSdsNamedType, isSdsNamedTypeDeclaration, - isSdsNull, isSdsParameter, isSdsParenthesizedExpression, isSdsPipeline, @@ -53,7 +49,6 @@ import { 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; private readonly nodeTypeCache: WorkspaceCache; 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)); - } } diff --git a/src/language/validation/experimentalLanguageFeatures.ts b/src/language/validation/experimentalLanguageFeatures.ts index c74d9cf6b..a55157f4f 100644 --- a/src/language/validation/experimentalLanguageFeatures.ts +++ b/src/language/validation/experimentalLanguageFeatures.ts @@ -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, diff --git a/src/language/validation/other/expressions/infixOperations.ts b/src/language/validation/other/expressions/infixOperations.ts index 66ae0ea0e..7ddfd902f 100644 --- a/src/language/validation/other/expressions/infixOperations.ts +++ b/src/language/validation/other/expressions/infixOperations.ts @@ -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; } diff --git a/src/language/validation/other/types/literalTypes.ts b/src/language/validation/other/types/literalTypes.ts new file mode 100644 index 000000000..a30e400e4 --- /dev/null +++ b/src/language/validation/other/types/literalTypes.ts @@ -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, + }); + } + } +}; diff --git a/src/language/validation/other/types/unionTypes.ts b/src/language/validation/other/types/unionTypes.ts index 29ff578c0..7c2c3bd73 100644 --- a/src/language/validation/other/types/unionTypes.ts +++ b/src/language/validation/other/types/unionTypes.ts @@ -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, diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index 07eb67342..c752e28bb 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -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), diff --git a/tests/language/typing/safe-ds-class-hierarchy.test.ts b/tests/language/typing/safe-ds-class-hierarchy.test.ts index b607d6ba0..1724cef6d 100644 --- a/tests/language/typing/safe-ds-class-hierarchy.test.ts +++ b/tests/language/typing/safe-ds-class-hierarchy.test.ts @@ -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 diff --git a/tests/resources/formatting/expressions/literals/string/empty template expression.sdstest b/tests/resources/formatting/expressions/template strings/empty template expression.sdstest similarity index 100% rename from tests/resources/formatting/expressions/literals/string/empty template expression.sdstest rename to tests/resources/formatting/expressions/template strings/empty template expression.sdstest diff --git a/tests/resources/formatting/expressions/literals/string/template string with basic expression.sdstest b/tests/resources/formatting/expressions/template strings/template string with basic expression.sdstest similarity index 100% rename from tests/resources/formatting/expressions/literals/string/template string with basic expression.sdstest rename to tests/resources/formatting/expressions/template strings/template string with basic expression.sdstest diff --git a/tests/resources/formatting/expressions/literals/string/template string with two expressions.sdstest b/tests/resources/formatting/expressions/template strings/template string with two expressions.sdstest similarity index 100% rename from tests/resources/formatting/expressions/literals/string/template string with two expressions.sdstest rename to tests/resources/formatting/expressions/template strings/template string with two expressions.sdstest diff --git a/tests/resources/grammar/expressions/literals/string/bad-template string with invalid expression.sdstest b/tests/resources/grammar/expressions/template strings/bad-template string with invalid expression.sdstest similarity index 100% rename from tests/resources/grammar/expressions/literals/string/bad-template string with invalid expression.sdstest rename to tests/resources/grammar/expressions/template strings/bad-template string with invalid expression.sdstest diff --git a/tests/resources/grammar/expressions/literals/string/bad-unclosed template expression.sdstest b/tests/resources/grammar/expressions/template strings/bad-unclosed template expression.sdstest similarity index 100% rename from tests/resources/grammar/expressions/literals/string/bad-unclosed template expression.sdstest rename to tests/resources/grammar/expressions/template strings/bad-unclosed template expression.sdstest diff --git a/tests/resources/grammar/expressions/literals/string/good-empty template expression.sdstest b/tests/resources/grammar/expressions/template strings/good-empty template expression.sdstest similarity index 100% rename from tests/resources/grammar/expressions/literals/string/good-empty template expression.sdstest rename to tests/resources/grammar/expressions/template strings/good-empty template expression.sdstest diff --git a/tests/resources/grammar/expressions/literals/string/good-template string with basic expression.sdstest b/tests/resources/grammar/expressions/template strings/good-template string with basic expression.sdstest similarity index 100% rename from tests/resources/grammar/expressions/literals/string/good-template string with basic expression.sdstest rename to tests/resources/grammar/expressions/template strings/good-template string with basic expression.sdstest diff --git a/tests/resources/grammar/types/literal types/bad-no literal list.sdstest b/tests/resources/grammar/types/literal types/bad-no literal list.sdstest new file mode 100644 index 000000000..7c5a3834e --- /dev/null +++ b/tests/resources/grammar/types/literal types/bad-no literal list.sdstest @@ -0,0 +1,5 @@ +// $TEST$ syntax_error + +segment mySegment( + x: literal +) {} diff --git a/tests/resources/grammar/types/literal types/good-with list literal.sdstest b/tests/resources/grammar/types/literal types/good-with list literal.sdstest new file mode 100644 index 000000000..02435b25a --- /dev/null +++ b/tests/resources/grammar/types/literal types/good-with list literal.sdstest @@ -0,0 +1,5 @@ +// $TEST$ no_syntax_error + +segment mySegment( + x: literal<[]> +) {} diff --git a/tests/resources/grammar/types/literal types/good-with map literal.sdstest b/tests/resources/grammar/types/literal types/good-with map literal.sdstest new file mode 100644 index 000000000..d19c39304 --- /dev/null +++ b/tests/resources/grammar/types/literal types/good-with map literal.sdstest @@ -0,0 +1,5 @@ +// $TEST$ no_syntax_error + +segment mySegment( + x: literal<{}> +) {} diff --git a/tests/resources/typing/assignees/block lambda results/main.sdstest b/tests/resources/typing/assignees/block lambda results/main.sdstest index a8ace56be..33592645e 100644 --- a/tests/resources/typing/assignees/block lambda results/main.sdstest +++ b/tests/resources/typing/assignees/block lambda results/main.sdstest @@ -10,7 +10,7 @@ segment mySegment() -> (r: Int) { }; () { - // $TEST$ serialization Int + // $TEST$ serialization literal<1> // $TEST$ serialization $Unknown yield »r«, yield »s« = 1; }; diff --git a/tests/resources/typing/assignees/placeholders/main.sdstest b/tests/resources/typing/assignees/placeholders/main.sdstest index f091e8e32..687534249 100644 --- a/tests/resources/typing/assignees/placeholders/main.sdstest +++ b/tests/resources/typing/assignees/placeholders/main.sdstest @@ -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; } diff --git a/tests/resources/typing/assignees/yields/main.sdstest b/tests/resources/typing/assignees/yields/main.sdstest index 21f442a3e..7569c7e32 100644 --- a/tests/resources/typing/assignees/yields/main.sdstest +++ b/tests/resources/typing/assignees/yields/main.sdstest @@ -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; } diff --git a/tests/resources/typing/expressions/block lambdas/that are isolated/main.sdstest b/tests/resources/typing/expressions/block lambdas/that are isolated/main.sdstest index fc6e9d746..34a938fd6 100644 --- a/tests/resources/typing/expressions/block lambdas/that are isolated/main.sdstest +++ b/tests/resources/typing/expressions/block lambdas/that are isolated/main.sdstest @@ -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(); }«; } diff --git a/tests/resources/typing/expressions/block lambdas/that are passed as arguments/main.sdstest b/tests/resources/typing/expressions/block lambdas/that are passed as arguments/main.sdstest index a3cb6ad35..9106d5425 100644 --- a/tests/resources/typing/expressions/block lambdas/that are passed as arguments/main.sdstest +++ b/tests/resources/typing/expressions/block lambdas/that are passed as arguments/main.sdstest @@ -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; }«); diff --git a/tests/resources/typing/expressions/block lambdas/that are yielded/main.sdstest b/tests/resources/typing/expressions/block lambdas/that are yielded/main.sdstest index 1488b40b4..47c55a95e 100644 --- a/tests/resources/typing/expressions/block lambdas/that are yielded/main.sdstest +++ b/tests/resources/typing/expressions/block lambdas/that are yielded/main.sdstest @@ -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; }«; diff --git a/tests/resources/typing/expressions/block lambdas/with manifest types/main.sdstest b/tests/resources/typing/expressions/block lambdas/with manifest types/main.sdstest index 226f243a7..a1dadf861 100644 --- a/tests/resources/typing/expressions/block lambdas/with manifest types/main.sdstest +++ b/tests/resources/typing/expressions/block lambdas/with manifest types/main.sdstest @@ -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; }«; diff --git a/tests/resources/typing/expressions/calls/of block lambdas/main.sdstest b/tests/resources/typing/expressions/calls/of block lambdas/main.sdstest index eacbaeceb..dbc74d2cb 100644 --- a/tests/resources/typing/expressions/calls/of block lambdas/main.sdstest +++ b/tests/resources/typing/expressions/calls/of block lambdas/main.sdstest @@ -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; diff --git a/tests/resources/typing/expressions/calls/of expression lambdas/main.sdstest b/tests/resources/typing/expressions/calls/of expression lambdas/main.sdstest index d634db366..a00b2df24 100644 --- a/tests/resources/typing/expressions/calls/of expression lambdas/main.sdstest +++ b/tests/resources/typing/expressions/calls/of expression lambdas/main.sdstest @@ -1,6 +1,6 @@ package tests.typing.expressions.calls.ofExpressionLambdas pipeline myPipeline { - // $TEST$ serialization Int + // $TEST$ serialization literal<1> »(() -> 1)()«; } diff --git a/tests/resources/typing/expressions/expression lambdas/that are isolated/main.sdstest b/tests/resources/typing/expressions/expression lambdas/that are isolated/main.sdstest index a9517c8b6..b182a388d 100644 --- a/tests/resources/typing/expressions/expression lambdas/that are isolated/main.sdstest +++ b/tests/resources/typing/expressions/expression lambdas/that are isolated/main.sdstest @@ -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«; } diff --git a/tests/resources/typing/expressions/expression lambdas/that are passed as arguments/main.sdstest b/tests/resources/typing/expressions/expression lambdas/that are passed as arguments/main.sdstest index 8e9eff78f..a1f1313e7 100644 --- a/tests/resources/typing/expressions/expression lambdas/that are passed as arguments/main.sdstest +++ b/tests/resources/typing/expressions/expression lambdas/that are passed as arguments/main.sdstest @@ -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«); } diff --git a/tests/resources/typing/expressions/expression lambdas/that are yielded/main.sdstest b/tests/resources/typing/expressions/expression lambdas/that are yielded/main.sdstest index 7371f90b0..853e2bd66 100644 --- a/tests/resources/typing/expressions/expression lambdas/that are yielded/main.sdstest +++ b/tests/resources/typing/expressions/expression lambdas/that are yielded/main.sdstest @@ -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«; } diff --git a/tests/resources/typing/expressions/expression lambdas/with manifest types/main.sdstest b/tests/resources/typing/expressions/expression lambdas/with manifest types/main.sdstest index 8052f703c..5ad3d0843 100644 --- a/tests/resources/typing/expressions/expression lambdas/with manifest types/main.sdstest +++ b/tests/resources/typing/expressions/expression lambdas/with manifest types/main.sdstest @@ -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«; } diff --git a/tests/resources/typing/expressions/literals/main.sdstest b/tests/resources/typing/expressions/literals/main.sdstest index ff55399de..65d9e97e8 100644 --- a/tests/resources/typing/expressions/literals/main.sdstest +++ b/tests/resources/typing/expressions/literals/main.sdstest @@ -2,18 +2,18 @@ package tests.typing.expressions.literals pipeline myPipeline { - // $TEST$ serialization Boolean + // $TEST$ serialization literal 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 val nullLiteral = »null«; - // $TEST$ serialization String + // $TEST$ serialization literal<"myString"> val stringLiteral = »"myString"«; } diff --git a/tests/resources/typing/expressions/operations/arithmetic/main.sdstest b/tests/resources/typing/expressions/operations/arithmetic/main.sdstest index 99d6c7b8e..6f29d46ad 100644 --- a/tests/resources/typing/expressions/operations/arithmetic/main.sdstest +++ b/tests/resources/typing/expressions/operations/arithmetic/main.sdstest @@ -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()«; } diff --git a/tests/resources/typing/expressions/operations/comparison/main.sdstest b/tests/resources/typing/expressions/operations/comparison/main.sdstest index 50186b978..1aa39738e 100644 --- a/tests/resources/typing/expressions/operations/comparison/main.sdstest +++ b/tests/resources/typing/expressions/operations/comparison/main.sdstest @@ -1,13 +1,13 @@ package tests.typing.operations.comparison pipeline myPipeline { - // $TEST$ serialization Boolean + // $TEST$ serialization literal val lessThan = »1 < 1«; - // $TEST$ serialization Boolean + // $TEST$ serialization literal val lessThanOrEquals = »1 <= 1«; - // $TEST$ serialization Boolean + // $TEST$ serialization literal val greaterThanOrEquals = »1 >= 1«; - // $TEST$ serialization Boolean + // $TEST$ serialization literal val greaterThan = »1 > 1«; // $TEST$ serialization Boolean diff --git a/tests/resources/typing/expressions/operations/equality/main.sdstest b/tests/resources/typing/expressions/operations/equality/main.sdstest index 5635da94f..6d7ffa536 100644 --- a/tests/resources/typing/expressions/operations/equality/main.sdstest +++ b/tests/resources/typing/expressions/operations/equality/main.sdstest @@ -1,13 +1,22 @@ package tests.typing.operations.equality pipeline myPipeline { - // $TEST$ serialization Boolean + // $TEST$ serialization literal val equals = (»1 == 1«); - // $TEST$ serialization Boolean + // $TEST$ serialization literal val notEquals = (»1 != 1«); + // $TEST$ serialization literal + val identicalTo = (»1 === 1«); + // $TEST$ serialization literal + 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«); } diff --git a/tests/resources/typing/expressions/operations/logical/main.sdstest b/tests/resources/typing/expressions/operations/logical/main.sdstest index 146f7e0c9..2428e6b44 100644 --- a/tests/resources/typing/expressions/operations/logical/main.sdstest +++ b/tests/resources/typing/expressions/operations/logical/main.sdstest @@ -1,11 +1,11 @@ package tests.typing.operations.logical pipeline myPipeline { - // $TEST$ serialization Boolean + // $TEST$ serialization literal val conjunction = »true and true«; - // $TEST$ serialization Boolean + // $TEST$ serialization literal val disjunction = »true or true«; - // $TEST$ serialization Boolean + // $TEST$ serialization literal val negation = »not true«; // $TEST$ serialization Boolean diff --git a/tests/resources/typing/expressions/template strings/main.sdstest b/tests/resources/typing/expressions/template strings/main.sdstest index 291e1f68c..bf878f09f 100644 --- a/tests/resources/typing/expressions/template strings/main.sdstest +++ b/tests/resources/typing/expressions/template strings/main.sdstest @@ -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 }}"«; } diff --git a/tests/resources/typing/types/literal types/main.sdstest b/tests/resources/typing/types/literal types/main.sdstest new file mode 100644 index 000000000..aaee8a7be --- /dev/null +++ b/tests/resources/typing/types/literal types/main.sdstest @@ -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, "">«) diff --git a/tests/resources/validation/experimental language feature/literal types/main.sdstest b/tests/resources/validation/experimental language feature/literal types/main.sdstest new file mode 100644 index 000000000..56b12ff46 --- /dev/null +++ b/tests/resources/validation/experimental language feature/literal types/main.sdstest @@ -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<>« +) diff --git a/tests/resources/validation/other/types/literal types/must have literals/main.sdstest b/tests/resources/validation/other/types/literal types/must have literals/main.sdstest new file mode 100644 index 000000000..e93a113ff --- /dev/null +++ b/tests/resources/validation/other/types/literal types/must have literals/main.sdstest @@ -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, "">« +) {} diff --git a/tests/resources/validation/other/types/literal types/must not contain list literals/main.sdstest b/tests/resources/validation/other/types/literal types/must not contain list literals/main.sdstest new file mode 100644 index 000000000..d0e6cae74 --- /dev/null +++ b/tests/resources/validation/other/types/literal types/must not contain list literals/main.sdstest @@ -0,0 +1,6 @@ +package tests.validation.other.types.literalTypes.mustNotContanListLiterals + +// $TEST$ error "Literal types must not contain list literals." +segment mySegment( + x: literal<»[]«> +) {} diff --git a/tests/resources/validation/other/types/literal types/must not contain map literals/main.sdstest b/tests/resources/validation/other/types/literal types/must not contain map literals/main.sdstest new file mode 100644 index 000000000..08bdf73f3 --- /dev/null +++ b/tests/resources/validation/other/types/literal types/must not contain map literals/main.sdstest @@ -0,0 +1,6 @@ +package tests.validation.other.types.literalsTypes.mustNotContainMapLiterals + +// $TEST$ error "Literal types must not contain map literals." +segment mySegment( + x: literal<»{}«> +) {} diff --git a/tests/resources/validation/other/types/union types/must have type arguments/main.sdstest b/tests/resources/validation/other/types/union types/must have type arguments/main.sdstest index 9568c20ce..17c8df750 100644 --- a/tests/resources/validation/other/types/union types/must have type arguments/main.sdstest +++ b/tests/resources/validation/other/types/union types/must have type arguments/main.sdstest @@ -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»« ) {} -// $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»« ) {}