diff --git a/src/language/builtins/fileFinder.ts b/src/language/builtins/fileFinder.ts new file mode 100644 index 000000000..6993fd4ba --- /dev/null +++ b/src/language/builtins/fileFinder.ts @@ -0,0 +1,37 @@ +import path from 'path'; +import { URI } from 'langium'; +import { SAFE_DS_FILE_EXTENSIONS } from '../helpers/fileExtensions.js'; +import { globSync } from 'glob'; + +let builtinsPath: string; +if (__filename.endsWith('.ts')) { + // Before running ESBuild + builtinsPath = path.join(__dirname, '..', '..', 'resources', 'builtins'); +} /* c8 ignore start */ else { + // After running ESBuild + builtinsPath = path.join(__dirname, '..', 'resources', 'builtins'); +} /* c8 ignore stop */ + +/** + * Lists all Safe-DS files in `src/resources/builtins`. + * + * @return URIs of all discovered files. + */ +export const listBuiltinsFiles = (): URI[] => { + const pattern = `**/*.{${SAFE_DS_FILE_EXTENSIONS.join(',')}}`; + const relativePaths = globSync(pattern, { cwd: builtinsPath, nodir: true }); + return relativePaths.map((relativePath) => { + const absolutePath = path.join(builtinsPath, relativePath); + return URI.file(absolutePath); + }); +}; + +/** + * Resolves a relative path to a builtin file. + * + * @param relativePath + */ +export const resolveRelativePathToBuiltinFile = (relativePath: string): URI => { + const absolutePath = path.join(builtinsPath, relativePath); + return URI.file(absolutePath); +}; diff --git a/src/language/builtins/safe-ds-core-classes.ts b/src/language/builtins/safe-ds-core-classes.ts new file mode 100644 index 000000000..ea12a5601 --- /dev/null +++ b/src/language/builtins/safe-ds-core-classes.ts @@ -0,0 +1,93 @@ +import { SafeDsServices } from '../safe-ds-module.js'; +import { resolveRelativePathToBuiltinFile } from './fileFinder.js'; +import { isSdsClass, isSdsModule, SdsClass } from '../generated/ast.js'; +import { LangiumDocuments } from 'langium'; +import { moduleMembersOrEmpty } from '../helpers/shortcuts.js'; + +const CORE_CLASSES_URI = resolveRelativePathToBuiltinFile('safeds/lang/coreClasses.sdsstub'); + +export class SafeDsCoreClasses { + private readonly langiumDocuments: LangiumDocuments; + + constructor(services: SafeDsServices) { + this.langiumDocuments = services.shared.workspace.LangiumDocuments; + } + + private cachedAny: SdsClass | undefined; + + /* c8 ignore start */ + get Any(): SdsClass | undefined { + if (!this.cachedAny) { + this.cachedAny = this.getClass('Any'); + } + return this.cachedAny; + } + + private cachedBoolean: SdsClass | undefined; + /* c8 ignore stop */ + + get Boolean(): SdsClass | undefined { + if (!this.cachedBoolean) { + this.cachedBoolean = this.getClass('Boolean'); + } + return this.cachedBoolean; + } + + private cachedFloat: SdsClass | undefined; + + get Float(): SdsClass | undefined { + if (!this.cachedFloat) { + this.cachedFloat = this.getClass('Float'); + } + return this.cachedFloat; + } + + private cachedInt: SdsClass | undefined; + + get Int(): SdsClass | undefined { + if (!this.cachedInt) { + this.cachedInt = this.getClass('Int'); + } + return this.cachedInt; + } + + private cachedNothing: SdsClass | undefined; + + get Nothing(): SdsClass | undefined { + if (!this.cachedNothing) { + this.cachedNothing = this.getClass('Nothing'); + } + return this.cachedNothing; + } + + private cachedString: SdsClass | undefined; + + get String(): SdsClass | undefined { + if (!this.cachedString) { + this.cachedString = this.getClass('String'); + } + return this.cachedString; + } + + private getClass(name: string): SdsClass | undefined { + if (!this.langiumDocuments.hasDocument(CORE_CLASSES_URI)) { + /* c8 ignore next 2 */ + return undefined; + } + + const document = this.langiumDocuments.getOrCreateDocument(CORE_CLASSES_URI); + const root = document.parseResult.value; + if (!isSdsModule(root)) { + /* c8 ignore next 2 */ + return undefined; + } + + const firstMatchingModuleMember = moduleMembersOrEmpty(root).find((m) => m.name === name); + if (!isSdsClass(firstMatchingModuleMember)) { + /* c8 ignore next 2 */ + return undefined; + } + + return firstMatchingModuleMember; + } +} diff --git a/src/language/builtins/safe-ds-workspace-manager.ts b/src/language/builtins/safe-ds-workspace-manager.ts index 7826867cd..dbefa04fe 100644 --- a/src/language/builtins/safe-ds-workspace-manager.ts +++ b/src/language/builtins/safe-ds-workspace-manager.ts @@ -1,8 +1,6 @@ -import { DefaultWorkspaceManager, LangiumDocument, LangiumDocumentFactory, LangiumSharedServices, URI } from 'langium'; +import { DefaultWorkspaceManager, LangiumDocument, LangiumDocumentFactory, LangiumSharedServices } from 'langium'; import { WorkspaceFolder } from 'vscode-languageserver'; -import { SAFE_DS_FILE_EXTENSIONS } from '../helpers/fileExtensions.js'; -import { globSync } from 'glob'; -import path from 'path'; +import { listBuiltinsFiles } from './fileFinder.js'; export class SafeDsWorkspaceManager extends DefaultWorkspaceManager { private documentFactory: LangiumDocumentFactory; @@ -18,31 +16,9 @@ export class SafeDsWorkspaceManager extends DefaultWorkspaceManager { ): Promise { await super.loadAdditionalDocuments(folders, collector); + // Load builtin files for (const uri of listBuiltinsFiles()) { collector(this.documentFactory.create(uri)); } } } - -let builtinsPath: string; -if (__filename.endsWith('.ts')) { - // Before running ESBuild - builtinsPath = path.join(__dirname, '..', '..', 'resources', 'builtins'); -} /* c8 ignore start */ else { - // After running ESBuild - builtinsPath = path.join(__dirname, '..', 'resources', 'builtins'); -} /* c8 ignore stop */ - -/** - * Lists all Safe-DS files in `src/resources/builtins`. - * - * @return URIs of all discovered files. - */ -export const listBuiltinsFiles = (): URI[] => { - const pattern = `**/*.{${SAFE_DS_FILE_EXTENSIONS.join(',')}}`; - const relativePaths = globSync(pattern, { cwd: builtinsPath, nodir: true }); - return relativePaths.map((relativePath) => { - const absolutePath = path.join(builtinsPath, relativePath); - return URI.file(absolutePath); - }); -}; diff --git a/src/language/helpers/shortcuts.ts b/src/language/helpers/shortcuts.ts index 69340bc13..ceaa3e243 100644 --- a/src/language/helpers/shortcuts.ts +++ b/src/language/helpers/shortcuts.ts @@ -3,6 +3,7 @@ import { isSdsBlockLambdaResult, isSdsDeclaration, isSdsModule, + isSdsModuleMember, isSdsPlaceholder, SdsAnnotatedObject, SdsAnnotationCall, @@ -19,6 +20,7 @@ import { SdsLiteral, SdsLiteralType, SdsModule, + SdsModuleMember, SdsParameter, SdsParameterList, SdsPlaceholder, @@ -77,6 +79,10 @@ export const importsOrEmpty = function (node: SdsModule | undefined): SdsImport[ return node?.imports ?? []; }; +export const moduleMembersOrEmpty = function (node: SdsModule | undefined): SdsModuleMember[] { + return node?.members?.filter(isSdsModuleMember) ?? []; +}; + export const packageNameOrNull = function (node: AstNode | undefined): string | null { return getContainerOfType(node, isSdsModule)?.name ?? null; }; diff --git a/src/language/safe-ds-module.ts b/src/language/safe-ds-module.ts index 4a2c5257a..438a999f4 100644 --- a/src/language/safe-ds-module.ts +++ b/src/language/safe-ds-module.ts @@ -10,19 +10,24 @@ import { PartialLangiumServices, } from 'langium'; import { SafeDsGeneratedModule, SafeDsGeneratedSharedModule } from './generated/module.js'; -import { registerValidationChecks, SafeDsValidator } from './validation/safe-ds-validator.js'; +import { registerValidationChecks } from './validation/safe-ds-validator.js'; import { SafeDsFormatter } from './formatting/safe-ds-formatter.js'; import { SafeDsWorkspaceManager } from './builtins/safe-ds-workspace-manager.js'; import { SafeDsScopeComputation } from './scoping/safe-ds-scope-computation.js'; import { SafeDsScopeProvider } from './scoping/safe-ds-scope-provider.js'; import { SafeDsValueConverter } from './grammar/safe-ds-value-converter.js'; +import { SafeDsTypeComputer } from './typing/safe-ds-type-computer.js'; +import { SafeDsCoreClasses } from './builtins/safe-ds-core-classes.js'; /** * Declaration of custom services - add your own service classes here. */ export type SafeDsAddedServices = { - validation: { - SafeDsValidator: SafeDsValidator; + builtins: { + CoreClasses: SafeDsCoreClasses; + }; + types: { + TypeComputer: SafeDsTypeComputer; }; }; @@ -38,6 +43,9 @@ export type SafeDsServices = LangiumServices & SafeDsAddedServices; * selected services, while the custom services must be fully specified. */ export const SafeDsModule: Module = { + builtins: { + CoreClasses: (services) => new SafeDsCoreClasses(services), + }, lsp: { Formatter: () => new SafeDsFormatter(), }, @@ -48,8 +56,8 @@ export const SafeDsModule: Module new SafeDsScopeComputation(services), ScopeProvider: (services) => new SafeDsScopeProvider(services), }, - validation: { - SafeDsValidator: () => new SafeDsValidator(), + types: { + TypeComputer: (services) => new SafeDsTypeComputer(services), }, }; diff --git a/src/language/scoping/safe-ds-scope-provider.ts b/src/language/scoping/safe-ds-scope-provider.ts index ace5369bb..1dd5c302b 100644 --- a/src/language/scoping/safe-ds-scope-provider.ts +++ b/src/language/scoping/safe-ds-scope-provider.ts @@ -1,14 +1,12 @@ import { AstNode, AstNodeDescription, - AstNodeDescriptionProvider, AstNodeLocator, DefaultScopeProvider, EMPTY_SCOPE, getContainerOfType, getDocument, LangiumDocuments, - LangiumServices, MultiMap, ReferenceInfo, Scope, @@ -59,18 +57,20 @@ import { } from '../helpers/shortcuts.js'; import { isContainedIn } from '../helpers/ast.js'; import { isStatic, isWildcardImport } from '../helpers/checks.js'; +import { SafeDsServices } from '../safe-ds-module.js'; +import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js'; export class SafeDsScopeProvider extends DefaultScopeProvider { - readonly documents: LangiumDocuments; - readonly astNodeDescriptionProvider: AstNodeDescriptionProvider; - readonly astNodeLocator: AstNodeLocator; + private readonly astNodeLocator: AstNodeLocator; + private readonly langiumDocuments: LangiumDocuments; + private readonly typeComputer: SafeDsTypeComputer; - constructor(services: LangiumServices) { + constructor(services: SafeDsServices) { super(services); - this.documents = services.shared.workspace.LangiumDocuments; - this.astNodeDescriptionProvider = services.workspace.AstNodeDescriptionProvider; this.astNodeLocator = services.workspace.AstNodeLocator; + this.langiumDocuments = services.shared.workspace.LangiumDocuments; + this.typeComputer = services.types.TypeComputer; } override getScope(context: ReferenceInfo): Scope { @@ -388,7 +388,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider { /* c8 ignore next 2 */ return nodeDescription.node; } - const document = this.documents.getOrCreateDocument(nodeDescription.documentUri); + const document = this.langiumDocuments.getOrCreateDocument(nodeDescription.documentUri); return this.astNodeLocator.getAstNode(document.parseResult.value, nodeDescription.path); } diff --git a/src/language/typing/model.ts b/src/language/typing/model.ts new file mode 100644 index 000000000..61b092432 --- /dev/null +++ b/src/language/typing/model.ts @@ -0,0 +1,239 @@ +// package com.larsreimann.safeds.staticAnalysis.typing +// +// import com.larsreimann.safeds.emf.containingEnumOrNull +// import com.larsreimann.safeds.naming.qualifiedNameOrNull +// import com.larsreimann.safeds.safeDS.SdsAbstractDeclaration +// import com.larsreimann.safeds.safeDS.SdsClass +// import com.larsreimann.safeds.safeDS.SdsEnum +// import com.larsreimann.safeds.safeDS.SdsEnumVariant +// import org.eclipse.xtext.naming.QualifiedName + +import { SdsClass, SdsDeclaration, SdsEnum, SdsEnumVariant } from '../generated/ast.js'; + +/* c8 ignore start */ +export abstract class Type { + abstract isNullable: boolean; + + abstract copyWithNullability(isNullable: boolean): Type; + + abstract equals(other: Type): boolean; + + abstract toString(): string; +} + +export abstract class NamedType extends Type { + protected constructor(private readonly sdsDeclaration: SdsDeclaration) { + super(); + } + + override toString(): string { + if (this.isNullable) { + return `${this.sdsDeclaration.name}?`; + } else { + return this.sdsDeclaration.name; + } + } + + // TODO: toQualifiedString(): string that uses qualified names instead of simple names +} + +export class ClassType extends NamedType { + constructor( + readonly sdsClass: SdsClass, + override readonly isNullable: boolean, + ) { + super(sdsClass); + } + + override copyWithNullability(isNullable: boolean): Type { + return new ClassType(this.sdsClass, isNullable); + } + + override equals(other: Type): boolean { + if (other === this) { + return true; + } + + if (!(other instanceof ClassType)) { + return false; + } + + return other.sdsClass === this.sdsClass && other.isNullable === this.isNullable; + } +} + +export class EnumType extends NamedType { + constructor( + readonly sdsEnum: SdsEnum, + override readonly isNullable: boolean, + ) { + super(sdsEnum); + } + + override copyWithNullability(isNullable: boolean): Type { + return new EnumType(this.sdsEnum, isNullable); + } + + override equals(other: Type): boolean { + if (other === this) { + return true; + } + + if (!(other instanceof EnumType)) { + return false; + } + + return other.sdsEnum === this.sdsEnum && other.isNullable === this.isNullable; + } +} + +export class EnumVariantType extends NamedType { + constructor( + readonly sdsEnumVariant: SdsEnumVariant, + override readonly isNullable: boolean, + ) { + super(sdsEnumVariant); + } + + override copyWithNullability(isNullable: boolean): Type { + return new EnumVariantType(this.sdsEnumVariant, isNullable); + } + + override equals(other: Type): boolean { + if (other === this) { + return true; + } + + if (!(other instanceof EnumVariantType)) { + return false; + } + + return other.sdsEnumVariant === this.sdsEnumVariant && other.isNullable === this.isNullable; + } + + // override fun toSimpleString() = buildString { + // sdsEnumVariant.containingEnumOrNull()?.let { append("${it.name}.") } + // append(sdsEnumVariant.name) + // // nullability + // } +} + +// class RecordType(resultToType: List>) : Type() { +// private val resultToType = resultToType.toMap() +// +// override val isNullable = false +// override fun setIsNullableOnCopy(isNullable: boolean) = this +// +// override fun toString(): String { +// val types = resultToType.entries.joinToString { (name, type) -> "$name: $type" } +// return "($types)" +// } +// +// override fun toSimpleString(): String { +// val types = resultToType.entries.joinToString { (name, type) -> "$name: ${type.toSimpleString()}" } +// return "($types)" +// } +// +// override fun equals(other: Any?): boolean { +// if (this === other) return true +// if (javaClass != other?.javaClass) return false +// +// other as RecordType +// +// if (resultToType != other.resultToType) return false +// if (isNullable != other.isNullable) return false +// +// return true +// } +// +// override fun hashCode(): Int { +// var result = resultToType.hashCode() +// result = 31 * result + isNullable.hashCode() +// return result +// } +// } +// +// data class CallableType(val parameters: List, val results: List) : Type() { +// override val isNullable = false +// override fun setIsNullableOnCopy(isNullable: boolean) = this +// +// override fun toString(): String { +// val parameters = parameters.joinToString() +// val results = results.joinToString() +// +// return "($parameters) -> ($results)" +// } +// +// override fun toSimpleString(): String { +// val parameters = parameters.joinToString { it.toSimpleString() } +// val results = results.joinToString { it.toSimpleString() } +// +// return "($parameters) -> ($results)" +// } +// } +// +// +// LiteralType +// +// } +// +// data class UnionType(val possibleTypes: Set) : Type() { +// override val isNullable = false +// override fun setIsNullableOnCopy(isNullable: boolean) = this +// +// override fun toString(): String { +// return "union<${possibleTypes.joinToString()}>" +// } +// +// override fun toSimpleString(): String { +// return "union<${possibleTypes.joinToString { it.toSimpleString() }}>" +// } +// } +// +// data class VariadicType(val elementType: Type) : Type() { +// override val isNullable = false +// override fun setIsNullableOnCopy(isNullable: boolean) = this +// +// override fun toString(): String { +// return "vararg<$elementType>" +// } +// +// override fun toSimpleString(): String { +// return "vararg<${elementType.toSimpleString()}>" +// } +// } +// +// data class ParameterisedType( +// val sdsAbstractNamedTypeDeclaration: SdsAbstractDeclaration, +// val kind: String, +// ) : Type() { +// override val isNullable = false +// override fun setIsNullableOnCopy(isNullable: boolean) = this +// +// override fun toString(): String { +// return "::$kind" +// } +// +// override fun toSimpleString(): String { +// return "::$kind" +// } +// } + +class UnresolvedTypeClass extends Type { + readonly isNullable = false; + + copyWithNullability(_isNullable: boolean): Type { + return this; + } + + override equals(other: Type): boolean { + return other instanceof UnresolvedTypeClass; + } + + toString(): string { + return '$Unresolved'; + } +} + +export const UnresolvedType = new UnresolvedTypeClass(); +/* c8 ignore stop */ diff --git a/src/language/typing/safe-ds-type-computer.ts b/src/language/typing/safe-ds-type-computer.ts new file mode 100644 index 000000000..1d1f22b80 --- /dev/null +++ b/src/language/typing/safe-ds-type-computer.ts @@ -0,0 +1,465 @@ +import { AstNode, AstNodeLocator, getDocument, WorkspaceCache } from 'langium'; +import { SafeDsServices } from '../safe-ds-module.js'; +import { SafeDsCoreClasses } from '../builtins/safe-ds-core-classes.js'; +import { ClassType, Type, UnresolvedType } from './model.js'; +import { + isSdsAssignee, + isSdsBoolean, + isSdsDeclaration, + isSdsExpression, + isSdsFloat, + isSdsInt, + isSdsNull, + isSdsString, + isSdsTemplateString, + isSdsType, + isSdsTypeArgument, + isSdsTypeProjection, + SdsAssignee, + SdsClass, + SdsDeclaration, + SdsExpression, + SdsType, +} from '../generated/ast.js'; + +/* c8 ignore start */ +export class SafeDsTypeComputer { + readonly astNodeLocator: AstNodeLocator; + readonly coreClasses: SafeDsCoreClasses; + + readonly typeCache: WorkspaceCache; + + constructor(readonly services: SafeDsServices) { + this.astNodeLocator = services.workspace.AstNodeLocator; + this.coreClasses = services.builtins.CoreClasses; + + this.typeCache = new WorkspaceCache(services.shared); + } + + computeType(node: AstNode | undefined): Type { + if (!node) { + return UnresolvedType; + } + + const documentUri = getDocument(node).uri.toString(); + const nodePath = this.astNodeLocator.getAstNodePath(node); + const key = `${documentUri}~${nodePath}`; + return this.typeCache.get(key, () => this.doComputeType(node)); + } + + private doComputeType(node: AstNode): Type { + if (isSdsAssignee(node)) { + return this.computeTypeOfAssignee(node); + } else if (isSdsDeclaration(node)) { + return this.computeTypeOfDeclaration(node); + } else if (isSdsExpression(node)) { + return this.computeTypeOfExpression(node); + } else if (isSdsType(node)) { + return this.computeTypeOfType(node); + } else if (isSdsTypeArgument(node)) { + return UnresolvedType; + // return this.computeType(node.value); + } else if (isSdsTypeProjection(node)) { + return UnresolvedType; + // return this.computeTypeOfType(node.type); + } else { + return this.Any(); + } + } + + private computeTypeOfAssignee(_node: SdsAssignee): Type { + return UnresolvedType; + } + + private computeTypeOfDeclaration(_node: SdsDeclaration): Type { + return UnresolvedType; + } + + private computeTypeOfExpression(node: SdsExpression): Type { + // Terminal cases + if (isSdsBoolean(node)) { + return this.Boolean(); + } else if (isSdsFloat(node)) { + return this.Float(); + } else if (isSdsInt(node)) { + return this.Int(); + } else if (isSdsNull(node)) { + return this.Nothing(true); + } else if (isSdsString(node)) { + return this.String(); + } else if (isSdsTemplateString(node)) { + return this.String(); + } + + return UnresolvedType; + } + + private computeTypeOfType(_node: SdsType): Type { + return UnresolvedType; + } + + private cachedAny: Type = UnresolvedType; + + private Any(): Type { + if (this.cachedAny === UnresolvedType) { + this.cachedAny = this.createCoreType(this.coreClasses.Any); + } + return this.cachedAny; + } + + private cachedBoolean: Type = UnresolvedType; + + private Boolean(): Type { + if (this.cachedBoolean === UnresolvedType) { + this.cachedBoolean = this.createCoreType(this.coreClasses.Boolean); + } + return this.cachedBoolean; + } + + private cachedFloat: Type = UnresolvedType; + + private Float(): Type { + if (this.cachedFloat === UnresolvedType) { + this.cachedFloat = this.createCoreType(this.coreClasses.Float); + } + return this.cachedFloat; + } + + private cachedInt: Type = UnresolvedType; + + private Int(): Type { + if (this.cachedInt === UnresolvedType) { + this.cachedInt = this.createCoreType(this.coreClasses.Int); + } + return this.cachedInt; + } + + private cachedNullableNothing: Type = UnresolvedType; + private cachedNothing: Type = UnresolvedType; + + private Nothing(isNullable: boolean): Type { + if (isNullable) { + if (this.cachedNullableNothing === UnresolvedType) { + this.cachedNullableNothing = this.createCoreType(this.coreClasses.Nothing, true); + } + return this.cachedNullableNothing; + } else { + if (this.cachedNothing === UnresolvedType) { + this.cachedNothing = this.createCoreType(this.coreClasses.Nothing); + } + return this.cachedNothing; + } + } + + private cachedString: Type = UnresolvedType; + + private String(): Type { + if (this.cachedString === UnresolvedType) { + this.cachedString = this.createCoreType(this.coreClasses.String); + } + return this.cachedString; + } + + private createCoreType(coreClass: SdsClass | undefined, isNullable: boolean = false): Type { + if (coreClass) { + return new ClassType(coreClass, isNullable); + } else { + return UnresolvedType; + } + } +} + +/* c8 ignore stop */ + +/* +fun SdsAbstractObject.hasPrimitiveType(): Boolean { + val type = type() + if (type !is ClassType) { + return false + } + + val qualifiedName = type.sdsClass.qualifiedNameOrNull() + return qualifiedName in setOf( + StdlibClasses.Boolean, + StdlibClasses.Float, + StdlibClasses.Int, + StdlibClasses.String, + ) +} + +private fun SdsAbstractAssignee.inferTypeForAssignee(context: EObject): Type { + return when { + this.eIsProxy() -> UnresolvedType + this is SdsBlockLambdaResult || this is SdsPlaceholder || this is SdsYield -> { + val assigned = assignedOrNull() ?: return Nothing(context) + assigned.inferType(context) + } + else -> Any(context) + } +} + +@OptIn(ExperimentalSdsApi::class) +private fun SdsAbstractDeclaration.inferTypeForDeclaration(context: EObject): Type { + return when { + this.eIsProxy() -> UnresolvedType + this is SdsAttribute -> type.inferTypeForType(context) + this is SdsClass -> { + val typeParametersTypes = this.typeParametersOrEmpty() + .map { it.inferTypeForDeclaration(context) } + .filterIsInstance() + + ClassType(this, typeParametersTypes, isNullable = false) + } + this is SdsEnum -> EnumType(this, isNullable = false) + this is SdsEnumVariant -> EnumVariantType(this, isNullable = false) + this is SdsFunction -> CallableType( + parametersOrEmpty().map { it.inferTypeForDeclaration(context) }, + resultsOrEmpty().map { it.inferTypeForDeclaration(context) }, + ) + this is SdsParameter -> { + // Declared parameter type + if (this.type != null) { + val declaredParameterType = this.type.inferTypeForType(context) + return when { + this.isVariadic -> VariadicType(declaredParameterType) + else -> declaredParameterType + } + } + + // Inferred lambda parameter type + val callable = this.closestAncestorOrNull() + val thisIndex = callable.parametersOrEmpty().indexOf(this) + if (callable is SdsAbstractLambda) { + val containerType = when (val container = callable.eContainer()) { + is SdsArgument -> container.parameterOrNull()?.inferType(context) + is SdsAssignment -> + container + .yieldsOrEmpty() + .find { it.assignedOrNull() == callable } + ?.result + ?.inferType(context) + else -> null + } + + return when (containerType) { + is CallableType -> containerType.parameters.getOrElse(thisIndex) { Any(context) } + else -> Any(context) + } + } + + // We don't know better + return Any(context) + } + this is SdsResult -> type.inferTypeForType(context) + // For now all Schema placeholders are of type 'ParameterisedType(::$SchemaType)' + this is SdsSchemaPlaceholder -> ParameterisedType(this, SdsKind.SchemaKind.toString()) + this is SdsStep -> CallableType( + parametersOrEmpty().map { it.inferTypeForDeclaration(context) }, + resultsOrEmpty().map { it.inferTypeForDeclaration(context) }, + ) + // Todo: resolve TypeParameter for "non kind" TypeParameter too + this is SdsTypeParameter && this.kind != null -> ParameterisedType(this, kind) + else -> Any(context) + } +} + +private fun SdsAbstractExpression.inferTypeExpression(context: EObject): Type { + return when { + // Terminal cases + this.eIsProxy() -> UnresolvedType + this is SdsBoolean -> Boolean(context) + this is SdsFloat -> Float(context) + this is SdsInt -> Int(context) + this is SdsNull -> Nothing(context, isNullable = true) + this is SdsString -> String(context) + this is SdsTemplateString -> String(context) + + // Recursive cases + this is SdsArgument -> this.value.inferTypeExpression(context) + this is SdsBlockLambda -> CallableType( + this.parametersOrEmpty().map { it.inferTypeForDeclaration(context) }, + blockLambdaResultsOrEmpty().map { it.inferTypeForAssignee(context) }, + ) + this is SdsCall -> when (val callable = callableOrNull()) { + is SdsClass -> { + val typeParametersTypes = callable.typeParametersOrEmpty() + .map { it.inferTypeForDeclaration(context) } + .filterIsInstance() + + ClassType(callable, typeParametersTypes, isNullable = false) + } + is SdsCallableType -> { + val results = callable.resultsOrEmpty() + when (results.size) { + 1 -> results.first().inferTypeForDeclaration(context) + else -> RecordType(results.map { it.name to it.inferTypeForDeclaration(context) }) + } + } + is SdsFunction -> { + val results = callable.resultsOrEmpty() + when (results.size) { + 1 -> results.first().inferTypeForDeclaration(context) + else -> RecordType(results.map { it.name to it.inferTypeForDeclaration(context) }) + } + } + is SdsBlockLambda -> { + val results = callable.blockLambdaResultsOrEmpty() + when (results.size) { + 1 -> results.first().inferTypeForAssignee(context) + else -> RecordType(results.map { it.name to it.inferTypeForAssignee(context) }) + } + } + is SdsEnumVariant -> { + EnumVariantType(callable, isNullable = false) + } + is SdsExpressionLambda -> { + callable.result.inferTypeExpression(context) + } + is SdsStep -> { + val results = callable.resultsOrEmpty() + when (results.size) { + 1 -> results.first().inferTypeForDeclaration(context) + else -> RecordType(results.map { it.name to it.inferTypeForDeclaration(context) }) + } + } + else -> Any(context) + } + this is SdsExpressionLambda -> CallableType( + this.parametersOrEmpty().map { it.inferTypeForDeclaration(context) }, + listOf(result.inferTypeExpression(context)), + ) + this is SdsIndexedAccess -> { + when (val receiverType = this.receiver.inferTypeExpression(context)) { + is UnresolvedType -> UnresolvedType + is VariadicType -> receiverType.elementType + else -> Nothing(context) + } + } + this is SdsInfixOperation -> when (operator) { + "<", "<=", ">=", ">" -> Boolean(context) + "==", "!=" -> Boolean(context) + "===", "!==" -> Boolean(context) + "or", "and" -> Boolean(context) + "+", "-", "*", "/" -> when { + this.leftOperand.inferTypeExpression(context) == Int(context) && + this.rightOperand.inferTypeExpression(context) == Int(context) -> Int(context) + else -> Float(context) + } + "?:" -> { + val leftOperandType = this.leftOperand.inferTypeExpression(context) + if (leftOperandType.isNullable) { + lowestCommonSupertype( + context, + listOf( + leftOperandType.setIsNullableOnCopy(isNullable = false), + this.rightOperand.inferTypeExpression(context), + ), + ) + } else { + leftOperandType + } + } + else -> Nothing(context) + } + this is SdsMemberAccess -> { + val memberType = this.member.inferTypeExpression(context) + memberType.setIsNullableOnCopy(this.isNullSafe || memberType.isNullable) + } + this is SdsParenthesizedExpression -> this.expression.inferTypeExpression(context) + this is SdsPrefixOperation -> when (operator) { + "not" -> Boolean(context) + "-" -> when (this.operand.inferTypeExpression(context)) { + Int(context) -> Int(context) + else -> Float(context) + } + else -> Nothing(context) + } + this is SdsReference -> this.declaration.inferType(context) + this is SdsSchemaReference -> this.type.inferTypeForType(context) + else -> Any(context) + } +} + +private fun SdsAbstractType.inferTypeForType(context: EObject): Type { + return when { + this.eIsProxy() -> UnresolvedType + this is SdsCallableType -> CallableType( + this.parametersOrEmpty().map { it.inferTypeForDeclaration(context) }, + this.resultsOrEmpty().map { it.inferTypeForDeclaration(context) }, + ) + this is SdsMemberType -> { + this.member.inferTypeForType(context) + } + this is SdsNamedType -> { + this.declaration.inferTypeForDeclaration(context).setIsNullableOnCopy(this.isNullable) + } + this is SdsParenthesizedType -> { + this.type.inferTypeForType(context) + } + this is SdsSchemaType -> { + this.declaration.inferTypeForDeclaration(context) + } + this is SdsUnionType -> { + UnionType(this.typeArgumentsOrEmpty().map { it.value.inferType(context) }.toSet()) + } + else -> Any(context) + } +} + +private fun lowestCommonSupertype(context: EObject, types: List): Type { + if (types.isEmpty()) { + return Nothing(context) + } + + val unwrappedTypes = unwrapUnionTypes(types) + val isNullable = unwrappedTypes.any { it.isNullable } + var candidate = unwrappedTypes.first().setIsNullableOnCopy(isNullable) + + while (!isLowestCommonSupertype(candidate, unwrappedTypes)) { + candidate = when (candidate) { + is CallableType -> Any(context, candidate.isNullable) + is ClassType -> { + val superClass = candidate.sdsClass.superClasses().firstOrNull() + ?: return Any(context, candidate.isNullable) + + val typeParametersTypes = superClass.typeParametersOrEmpty() + .map { it.inferTypeForDeclaration(context) } + .filterIsInstance() + + ClassType(superClass, typeParametersTypes, candidate.isNullable) + } + is EnumType -> Any(context, candidate.isNullable) + is EnumVariantType -> { + val containingEnum = candidate.sdsEnumVariant.containingEnumOrNull() + ?: return Any(context, candidate.isNullable) + EnumType(containingEnum, candidate.isNullable) + } + is RecordType -> Any(context, candidate.isNullable) + // TODO: Correct ? + is ParameterisedType -> Any(context, candidate.isNullable) + is UnionType -> throw AssertionError("Union types should have been unwrapped.") + UnresolvedType -> Any(context, candidate.isNullable) + is VariadicType -> Any(context, candidate.isNullable) + } + } + + return candidate +} + +private fun unwrapUnionTypes(types: List): List { + return types.flatMap { + when (it) { + is UnionType -> it.possibleTypes + else -> listOf(it) + } + } +} + +private fun isLowestCommonSupertype(candidate: Type, otherTypes: List): Boolean { + if (candidate is ClassType && candidate.sdsClass.qualifiedNameOrNull() == StdlibClasses.Any) { + return true + } + + return otherTypes.all { it.isSubstitutableFor(candidate) } +} + */ diff --git a/src/language/typing/typeComputer.ts b/src/language/typing/typeComputer.ts deleted file mode 100644 index 2c39cdc61..000000000 --- a/src/language/typing/typeComputer.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { AstNode } from 'langium'; - -export const computeType = (_node: AstNode): Type => { - return { - equals(_other: Type): boolean { - return true; - }, - - toString(): string { - return 'test'; - }, - }; -}; - -interface Type { - equals(other: Type): boolean; - - toString(): string; -} diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index 3297075e2..edaf0fcf0 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -50,7 +50,6 @@ import { referenceTargetMustNotBeAnnotationPipelineOrSchema } from './other/expr */ export const registerValidationChecks = function (services: SafeDsServices) { const registry = services.validation.ValidationRegistry; - const validator = services.validation.SafeDsValidator; const checks: ValidationChecks = { SdsAssignment: [assignmentShouldHaveMoreThanWildcardsAsAssignees], SdsAnnotation: [annotationMustContainUniqueNames, annotationParameterListShouldNotBeEmpty], @@ -86,10 +85,5 @@ export const registerValidationChecks = function (services: SafeDsServices) { SdsUnionType: [unionTypeMustHaveTypeArguments, unionTypeShouldNotHaveASingularTypeArgument], SdsYield: [yieldMustNotBeUsedInPipeline], }; - registry.register(checks, validator); + registry.register(checks); }; - -/** - * Implementation of custom validations. - */ -export class SafeDsValidator {} diff --git a/tests/language/builtins/fileFinder.test.ts b/tests/language/builtins/fileFinder.test.ts new file mode 100644 index 000000000..6205f0439 --- /dev/null +++ b/tests/language/builtins/fileFinder.test.ts @@ -0,0 +1,8 @@ +import { describe, expect, it } from 'vitest'; +import { listBuiltinsFiles } from '../../../src/language/builtins/fileFinder.js'; + +describe('listBuiltinsFiles', () => { + it('should not return an empty list', () => { + expect(listBuiltinsFiles().length).toBeGreaterThan(0); + }); +}); diff --git a/tests/language/builtins/safe-ds-workspace-manager.test.ts b/tests/language/builtins/safe-ds-workspace-manager.test.ts index 37e9a3fc7..e2bc9d02b 100644 --- a/tests/language/builtins/safe-ds-workspace-manager.test.ts +++ b/tests/language/builtins/safe-ds-workspace-manager.test.ts @@ -1,5 +1,4 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { listBuiltinsFiles } from '../../../src/language/builtins/safe-ds-workspace-manager.js'; import { createSafeDsServices } from '../../../src/language/safe-ds-module.js'; import { getLinkingErrors } from '../../helpers/diagnostics.js'; import { NodeFileSystem } from 'langium/node'; @@ -35,9 +34,3 @@ class C { ); }); }); - -describe('listBuiltinsFiles', () => { - it('should not return an empty list', () => { - expect(listBuiltinsFiles().length).toBeGreaterThan(0); - }); -}); diff --git a/tests/language/scoping/testScoping.test.ts b/tests/language/scoping/testScoping.test.ts index c1ae38cf7..b63107ca1 100644 --- a/tests/language/scoping/testScoping.test.ts +++ b/tests/language/scoping/testScoping.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, it } from 'vitest'; +import { afterEach, beforeEach, describe, it } from 'vitest'; import { createSafeDsServices } from '../../../src/language/safe-ds-module.js'; import { URI } from 'vscode-uri'; import { NodeFileSystem } from 'langium/node'; @@ -12,6 +12,11 @@ import { Location } from 'vscode-languageserver'; const services = createSafeDsServices(NodeFileSystem).SafeDs; describe('scoping', async () => { + beforeEach(async () => { + // Load the builtin library + await services.shared.workspace.WorkspaceManager.initializeWorkspace([]); + }); + afterEach(async () => { await clearDocuments(services); }); diff --git a/tests/language/typing/testTyping.test.ts b/tests/language/typing/testTyping.test.ts index 8ed1e159d..97495e31b 100644 --- a/tests/language/typing/testTyping.test.ts +++ b/tests/language/typing/testTyping.test.ts @@ -6,9 +6,9 @@ import { clearDocuments } from 'langium/test'; import { AssertionError } from 'assert'; import { getNodeByLocation, locationToString } from '../../helpers/location.js'; import { createTypingTests } from './creator.js'; -import { computeType } from '../../../src/language/typing/typeComputer.js'; const services = createSafeDsServices(NodeFileSystem).SafeDs; +const typeComputer = services.types.TypeComputer; describe('typing', async () => { beforeEach(async () => { @@ -37,11 +37,11 @@ describe('typing', async () => { if (equivalenceClassAssertion.locations.length > 1) { const firstLocation = equivalenceClassAssertion.locations[0]; const firstNode = getNodeByLocation(services, firstLocation); - const firstType = computeType(firstNode); + const firstType = typeComputer.computeType(firstNode); for (const currentLocation of equivalenceClassAssertion.locations.slice(1)) { const currentNode = getNodeByLocation(services, currentLocation); - const currentType = computeType(currentNode); + const currentType = typeComputer.computeType(currentNode); if (!currentType.equals(firstType)) { throw new AssertionError({ @@ -59,7 +59,7 @@ describe('typing', async () => { // Ensure the serialized type of the node matches the expected type for (const serializationAssertion of test.serializationAssertions) { const node = getNodeByLocation(services, serializationAssertion.location); - const actualType = computeType(node); + const actualType = typeComputer.computeType(node); if (actualType.toString() !== serializationAssertion.expectedType) { throw new AssertionError({ diff --git a/tests/resources/typing/expressions/literals/main.sdstest b/tests/resources/typing/expressions/literals/main.sdstest new file mode 100644 index 000000000..ff55399de --- /dev/null +++ b/tests/resources/typing/expressions/literals/main.sdstest @@ -0,0 +1,19 @@ +package tests.typing.expressions.literals + +pipeline myPipeline { + + // $TEST$ serialization Boolean + val booleanLiteral = »true«; + + // $TEST$ serialization Float + val floatLiteral = »1.0«; + + // $TEST$ serialization Int + val intLiteral = »1«; + + // $TEST$ serialization Nothing? + val nullLiteral = »null«; + + // $TEST$ serialization String + val stringLiteral = »"myString"«; +} diff --git a/tests/resources/typing/expressions/template strings/main.sdstest b/tests/resources/typing/expressions/template strings/main.sdstest new file mode 100644 index 000000000..291e1f68c --- /dev/null +++ b/tests/resources/typing/expressions/template strings/main.sdstest @@ -0,0 +1,6 @@ +package tests.typing.expressions.templateStrings + +pipeline myPipeline { + // $TEST$ serialization String + val templateString = »"1 + 2 = {{ 1 + 2 }}"«; +} diff --git a/tests/resources/typing/skip-assignees/blockLambdaResults.sdstest b/tests/resources/typing/skip-assignees/blockLambdaResults.sdstest new file mode 100644 index 000000000..06803536f --- /dev/null +++ b/tests/resources/typing/skip-assignees/blockLambdaResults.sdstest @@ -0,0 +1,10 @@ +package tests.typingassignees.blockLambdaResults + +fun f(p: Any) + +pipeline myPipeline { + f(() { + yield r = 1; + yield s = ""; + }); +} diff --git a/tests/resources/typing/skip-assignees/placeholders.sdstest b/tests/resources/typing/skip-assignees/placeholders.sdstest new file mode 100644 index 000000000..52a255f6e --- /dev/null +++ b/tests/resources/typing/skip-assignees/placeholders.sdstest @@ -0,0 +1,8 @@ +package tests.typingassignees.placeholders + +fun f(p: Any) + +pipeline myPipeline { + val a = 1; + val b = ""; +} diff --git a/tests/resources/typing/skip-assignees/yields.sdstest b/tests/resources/typing/skip-assignees/yields.sdstest new file mode 100644 index 000000000..ca40b830b --- /dev/null +++ b/tests/resources/typing/skip-assignees/yields.sdstest @@ -0,0 +1,8 @@ +package tests.typingassignees.yields + +fun f(p: Any) + +step myStep() -> (r: Int, s: String) { + yield r = 1; + yield s = ""; +} diff --git a/tests/resources/typing/skip-declarations/attributes.sdstest b/tests/resources/typing/skip-declarations/attributes.sdstest new file mode 100644 index 000000000..ac6208b01 --- /dev/null +++ b/tests/resources/typing/skip-declarations/attributes.sdstest @@ -0,0 +1,5 @@ +package tests.typingdeclarations.attributes + +class C { + attr a: Int +} diff --git a/tests/resources/typing/skip-declarations/classes.sdstest b/tests/resources/typing/skip-declarations/classes.sdstest new file mode 100644 index 000000000..97d8dab9f --- /dev/null +++ b/tests/resources/typing/skip-declarations/classes.sdstest @@ -0,0 +1,3 @@ +package tests.typingdeclarations.classes + +class C diff --git a/tests/resources/typing/skip-declarations/enumVariants.sdstest b/tests/resources/typing/skip-declarations/enumVariants.sdstest new file mode 100644 index 000000000..782f9c0f0 --- /dev/null +++ b/tests/resources/typing/skip-declarations/enumVariants.sdstest @@ -0,0 +1,7 @@ +package tests.typingdeclarations.enumVariants + +enum E { + V +} + +step myStep(v: E.V) {} diff --git a/tests/resources/typing/skip-declarations/enums.sdstest b/tests/resources/typing/skip-declarations/enums.sdstest new file mode 100644 index 000000000..d8e049653 --- /dev/null +++ b/tests/resources/typing/skip-declarations/enums.sdstest @@ -0,0 +1,5 @@ +package tests.typingdeclarations.enums + +enum E + +step myStep(e: E) {} diff --git a/tests/resources/typing/skip-declarations/functions.sdstest b/tests/resources/typing/skip-declarations/functions.sdstest new file mode 100644 index 000000000..e1440dff3 --- /dev/null +++ b/tests/resources/typing/skip-declarations/functions.sdstest @@ -0,0 +1,3 @@ +package tests.typingdeclarations.functions + +fun f(a: Int, b: String) -> (r: String, s: Int) diff --git a/tests/resources/typing/skip-declarations/parameters.sdstest b/tests/resources/typing/skip-declarations/parameters.sdstest new file mode 100644 index 000000000..4110f271d --- /dev/null +++ b/tests/resources/typing/skip-declarations/parameters.sdstest @@ -0,0 +1,17 @@ +package tests.typingdeclarations.parameters + +fun f(parameter: (a: String) -> r: String) + +step myStepWithNormalParameter(a: Int, b: String) {} +step myStepWithVariadicParameter(vararg param: Int) {} + +step myStepWithLambdas() -> ( + r: (a: String) -> r: String, + s: (a: String) -> r: String +) { + f((a) -> ""); + f((b) { yield r = ""; }); + + yield r = (c) -> ""; + yield s = (d) { yield r = ""; }; +} diff --git a/tests/resources/typing/skip-declarations/results.sdstest b/tests/resources/typing/skip-declarations/results.sdstest new file mode 100644 index 000000000..04efa63f2 --- /dev/null +++ b/tests/resources/typing/skip-declarations/results.sdstest @@ -0,0 +1,6 @@ +package tests.typingdeclarations.results + +step myStep() -> (r: Int, s: String) { + yield r = 1; + yield s = ""; +} diff --git a/tests/resources/typing/skip-declarations/steps.sdstest b/tests/resources/typing/skip-declarations/steps.sdstest new file mode 100644 index 000000000..891a36fd2 --- /dev/null +++ b/tests/resources/typing/skip-declarations/steps.sdstest @@ -0,0 +1,6 @@ +package tests.typingdeclarations.steps + +step s(a: Int, b: String) -> (r: String, s: Int) { + yield r = ""; + yield s = 1; +} diff --git a/tests/resources/typing/skip-expressions/arguments.sdstest b/tests/resources/typing/skip-expressions/arguments.sdstest new file mode 100644 index 000000000..484674139 --- /dev/null +++ b/tests/resources/typing/skip-expressions/arguments.sdstest @@ -0,0 +1,8 @@ +package tests.typingexpressions.parenthesizedExpressions + +fun f(x: Any?) + +pipeline myPipeline { + f(1); + f(x = ""); +} diff --git a/tests/resources/typing/skip-expressions/blockLambdas.sdstest b/tests/resources/typing/skip-expressions/blockLambdas.sdstest new file mode 100644 index 000000000..0b48112d5 --- /dev/null +++ b/tests/resources/typing/skip-expressions/blockLambdas.sdstest @@ -0,0 +1,49 @@ +package tests.typingexpressions.blockLambdas + +fun f( + parameter: (a: String, b: Int) -> (r: String, s: Int) +) + +step lambdasWithExplicitParameterTypes() -> ( + result: (a: String, b: Int) -> (r: String, s: Int) +) { + val myLambda = (a: Int, b: String) { + yield r = 1; + yield s = ""; + }; + yield result = (a: Int, b: String) { + yield r = 1; + yield s = ""; + }; + f( + (a: Int, b: String) { + yield r = 1; + yield s = ""; + } + ); +} + +step lambdasWithExplicitVariadicType() { + val myLambda = (a: Int, vararg b: String) { + yield r = 1; + yield s = ""; + }; +} + +step yieldedLambda() -> ( + result: (a: String, b: Int) -> (r: String, s: Int) +) { + yield result = (a, b) { + yield r = 1; + yield s = ""; + }; +} + +step argumentLambda() { + f( + (a, b) { + yield r = 1; + yield s = ""; + } + ); +} diff --git a/tests/resources/typing/skip-expressions/calls.sdstest b/tests/resources/typing/skip-expressions/calls.sdstest new file mode 100644 index 000000000..2e323bf9d --- /dev/null +++ b/tests/resources/typing/skip-expressions/calls.sdstest @@ -0,0 +1,37 @@ +package tests.typingexpressions.calls + +class C() +enum E { + V(a: Int) +} +fun f1() -> r: String +fun f2() -> (r: String, s: Int) +step s1() -> r: String{ + yield r = ""; +} +step s2() -> (r: String, s: Int) { + yield r = ""; + yield s = 1; +} + +step mySteps( + p1: () -> r: String, + p2: () -> (r: String, s: Int) +) { + val classCall = C(); + val callableTypeCall1 = p1(); + val callableTypeCall2 = p2(); + val enumVariantCall = E.V(1); + val functionCall1 = f1(); + val functionCall2 = f2(); + val blockLambdaCall1 = (() { + yield r = ""; + })(); + val blockLambdaCall2 = (() { + yield r = ""; + yield s = 1; + })(); + val expressionLambdaCall = (() -> 1)(); + val stepCall1 = s1(); + val stepCall2 = s2(); +} diff --git a/tests/resources/typing/skip-expressions/expressionLambdas.sdstest b/tests/resources/typing/skip-expressions/expressionLambdas.sdstest new file mode 100644 index 000000000..158bddc91 --- /dev/null +++ b/tests/resources/typing/skip-expressions/expressionLambdas.sdstest @@ -0,0 +1,27 @@ +package tests.typingexpressions.expressionLambdas + +fun f( + parameter: (a: String, b: Int) -> r: String +) + +step lambdasWithExplicitParameterTypes() -> ( + result: (a: String, b: Int) -> r: String +) { + val myLambda = (a: Int, b: String) -> 1; + yield result = (a: Int, b: String) -> 1; + f((a: Int, b: String) -> 1); +} + +step lambdasWithExplicitVariadicType() { + val myLambda = (a: Int, vararg b: String) -> 1; +} + +step yieldedLambda() -> ( + result: (a: String, b: Int) -> r: String +) { + yield result = (a, b) -> 1; +} + +step argumentLambda() { + f((a, b) -> 1); +} diff --git a/tests/resources/typing/skip-expressions/indexedAccesses.sdstest b/tests/resources/typing/skip-expressions/indexedAccesses.sdstest new file mode 100644 index 000000000..572a7b24d --- /dev/null +++ b/tests/resources/typing/skip-expressions/indexedAccesses.sdstest @@ -0,0 +1,17 @@ +package tests.typingexpressions.indexedAccesses + +step myStep1(vararg params: Int) { + params[0]; +} + +step myStep2(vararg params: String) { + params[0]; +} + +step myStep3(params: String) { + params[0]; +} + +step myStep4() { + unresolved[0]; +} diff --git a/tests/resources/typing/skip-expressions/memberAccesses.sdstest b/tests/resources/typing/skip-expressions/memberAccesses.sdstest new file mode 100644 index 000000000..7d6d1da03 --- /dev/null +++ b/tests/resources/typing/skip-expressions/memberAccesses.sdstest @@ -0,0 +1,19 @@ +package tests.typingexpressions.memberAccesses + +class C { + static attr a: Int + static attr b: String + static attr c: Any? +} + +pipeline myPipeline { + C.a; + C.b; + C.c; + C.unresolved; + + C?.a; + C?.b; + C?.c; + C?.unresolved; +} diff --git a/tests/resources/typing/skip-expressions/operations/arithmetic.sdstest b/tests/resources/typing/skip-expressions/operations/arithmetic.sdstest new file mode 100644 index 000000000..8bc95c317 --- /dev/null +++ b/tests/resources/typing/skip-expressions/operations/arithmetic.sdstest @@ -0,0 +1,21 @@ +package tests.typingoperations.arithmetic + +pipeline myPipeline { + val additionIntInt = (1 + 1); + val subtractionIntInt = (1 - 1); + val multiplicationIntInt = (1 * 1); + val divisionIntInt = (1 / 1); + val negationInt = (-1); + + val additionIntFloat = (1 + 1.0); + val subtractionIntFloat = (1 - 1.0); + val multiplicationIntFloat = (1 * 1.0); + val divisionIntFloat = (1 / 1.0); + val negationFloat = (-1.0); + + val additionInvalid = (true + true); + val subtractionInvalid = (true - true); + val multiplicationInvalid = (true * true); + val divisionInvalid = (true / true); + val negationInvalid = (-true); +} diff --git a/tests/resources/typing/skip-expressions/operations/comparison.sdstest b/tests/resources/typing/skip-expressions/operations/comparison.sdstest new file mode 100644 index 000000000..a4200c411 --- /dev/null +++ b/tests/resources/typing/skip-expressions/operations/comparison.sdstest @@ -0,0 +1,12 @@ +package tests.typingoperations.comparison + +pipeline myPipeline { + val lessThan = (1 < 1); + val lessThanOrEquals = (1 <= 1); + val greaterThanOrEquals = (1 >= 1); + val greaterThan = (1 > 1); + val lessThanInvalid = (true < true); + val lessThanOrEqualsInvalid = (true <= true); + val greaterThanOrEqualsInvalid = (true >= true); + val greaterThanInvalid = (true > true); +} diff --git a/tests/resources/typing/skip-expressions/operations/elvis.sdstest b/tests/resources/typing/skip-expressions/operations/elvis.sdstest new file mode 100644 index 000000000..9616e92fb --- /dev/null +++ b/tests/resources/typing/skip-expressions/operations/elvis.sdstest @@ -0,0 +1,21 @@ +package tests.typingoperations.elvis + +fun intOrNull() -> a: Int? +fun stringOrNull() -> s: String? + +pipeline elvisWithNonNullableLeftOperand { + 1 ?: intOrNull(); + 1 ?: 1; + 1 ?: 1.0; + 1 ?: ""; + 1 ?: null; +} + +pipeline elvisWithNullableLeftOperand { + val intOrNullElseIntOrNull = intOrNull() ?: intOrNull(); + val intOrNullElseNull = intOrNull() ?: null; + val intOrNullElseInt = intOrNull() ?: 1; + val intOrNullElseFloat = intOrNull() ?: 1.0; + val intOrNullElseString = intOrNull() ?: ""; + val intOrNullElseStringOrNull = intOrNull() ?: stringOrNull(); +} diff --git a/tests/resources/typing/skip-expressions/operations/equality.sdstest b/tests/resources/typing/skip-expressions/operations/equality.sdstest new file mode 100644 index 000000000..6278e2b63 --- /dev/null +++ b/tests/resources/typing/skip-expressions/operations/equality.sdstest @@ -0,0 +1,6 @@ +package tests.typingoperations.equality + +pipeline myPipeline { + val equals = (1 == 1); + val notEquals = (1 != 1); +} diff --git a/tests/resources/typing/skip-expressions/operations/logical.sdstest b/tests/resources/typing/skip-expressions/operations/logical.sdstest new file mode 100644 index 000000000..e789df0d9 --- /dev/null +++ b/tests/resources/typing/skip-expressions/operations/logical.sdstest @@ -0,0 +1,10 @@ +package tests.typingoperations.logical + +pipeline myPipeline { + val conjunction = (true and true); + val disjunction = (true or true); + val negation = (not true); + val conjunctionInvalid = (1 and 1); + val disjunctionInvalid = (1.0 or 1.0); + val negationInvalid = (not "true"); +} diff --git a/tests/resources/typing/skip-expressions/operations/strictEquality.sdstest b/tests/resources/typing/skip-expressions/operations/strictEquality.sdstest new file mode 100644 index 000000000..7663b79cb --- /dev/null +++ b/tests/resources/typing/skip-expressions/operations/strictEquality.sdstest @@ -0,0 +1,6 @@ +package tests.typingoperations.strictEquality + +pipeline myPipeline { + val strictlyEquals = (1 === 1); + val notStrictlyEquals = (1 !== 1); +} diff --git a/tests/resources/typing/skip-expressions/parenthesizedExpressions.sdstest b/tests/resources/typing/skip-expressions/parenthesizedExpressions.sdstest new file mode 100644 index 000000000..435022bff --- /dev/null +++ b/tests/resources/typing/skip-expressions/parenthesizedExpressions.sdstest @@ -0,0 +1,6 @@ +package tests.typingexpressions.parenthesizedExpressions + +pipeline myPipeline { + (1); + (""); +} diff --git a/tests/resources/typing/skip-expressions/references.sdstest b/tests/resources/typing/skip-expressions/references.sdstest new file mode 100644 index 000000000..7f3a1899f --- /dev/null +++ b/tests/resources/typing/skip-expressions/references.sdstest @@ -0,0 +1,10 @@ +package tests.typingexpressions.references + +pipeline myPipeline { + val a = 1; + val b = ""; + + a; + b; + unresolved; +} diff --git a/tests/resources/typing/skip-types/callableTypes.sdstest b/tests/resources/typing/skip-types/callableTypes.sdstest new file mode 100644 index 000000000..46d98e37b --- /dev/null +++ b/tests/resources/typing/skip-types/callableTypes.sdstest @@ -0,0 +1,3 @@ +package tests.typingtypes.callableTypes + +fun myFun(f: (p1: Int, p2: String) -> (r1: Int, r2: String)) diff --git a/tests/resources/typing/skip-types/memberTypes.sdstest b/tests/resources/typing/skip-types/memberTypes.sdstest new file mode 100644 index 000000000..e4b635cd9 --- /dev/null +++ b/tests/resources/typing/skip-types/memberTypes.sdstest @@ -0,0 +1,9 @@ +package tests.typingtypes.memberTypes + +enum MyEnum { + MyVariant1 + MyVariant2 +} + +fun nonNullableMemberTypes(a: MyEnum.MyVariant1, b: MyEnum.MyVariant2) +fun nullableMemberTypes(a: MyEnum.MyVariant1?, b: MyEnum.MyVariant2?) diff --git a/tests/resources/typing/skip-types/namedTypes.sdstest b/tests/resources/typing/skip-types/namedTypes.sdstest new file mode 100644 index 000000000..8f64311b6 --- /dev/null +++ b/tests/resources/typing/skip-types/namedTypes.sdstest @@ -0,0 +1,4 @@ +package tests.typingtypes.namedTypes + +fun nonNullableNamedTypes(a: Int, b: String) +fun nullableNamedTypes(a: Int?, b: String?) diff --git a/tests/resources/typing/skip-types/parenthesizedTypes.sdstest b/tests/resources/typing/skip-types/parenthesizedTypes.sdstest new file mode 100644 index 000000000..c35d55b35 --- /dev/null +++ b/tests/resources/typing/skip-types/parenthesizedTypes.sdstest @@ -0,0 +1,3 @@ +package tests.typingtypes.parenthesizedTypes + +fun myFun(a: (Int), b: (String)) diff --git a/tests/resources/typing/skip-types/unionTypes.sdstest b/tests/resources/typing/skip-types/unionTypes.sdstest new file mode 100644 index 000000000..9850bd075 --- /dev/null +++ b/tests/resources/typing/skip-types/unionTypes.sdstest @@ -0,0 +1,3 @@ +package tests.typingtypes.unionTypes + +fun myFun(a: union) diff --git a/tests/resources/typing/todo/main.sdstest b/tests/resources/typing/todo/main.sdstest deleted file mode 100644 index c6cda62ca..000000000 --- a/tests/resources/typing/todo/main.sdstest +++ /dev/null @@ -1,4 +0,0 @@ -package test - -// $TEST$ equivalence_class test -class »C« diff --git a/tests/resources/typing/todo/resource.sdstest b/tests/resources/typing/todo/resource.sdstest deleted file mode 100644 index 51106954c..000000000 --- a/tests/resources/typing/todo/resource.sdstest +++ /dev/null @@ -1,7 +0,0 @@ -package test - -// $TEST$ equivalence_class test -class »C« - -// $TEST$ serialization test -class »C«