Skip to content

Commit daad5c4

Browse files
authored
feat: type checker service (#722)
Closes partially #666 ### Summary of Changes Implement & test type checker services. A later PR will use the service to implement validation for types.
1 parent 3e71cad commit daad5c4

File tree

4 files changed

+695
-75
lines changed

4 files changed

+695
-75
lines changed

packages/safe-ds-lang/src/language/helpers/nodeProperties.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AstNode, getContainerOfType, Stream, stream } from 'langium';
12
import {
23
isSdsAnnotation,
34
isSdsArgumentList,
@@ -57,7 +58,6 @@ import {
5758
SdsTypeParameter,
5859
SdsTypeParameterList,
5960
} from '../generated/ast.js';
60-
import { AstNode, getContainerOfType, Stream, stream } from 'langium';
6161

6262
// -------------------------------------------------------------------------------------------------
6363
// Checks

packages/safe-ds-lang/src/language/typing/model.ts

-2
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,9 @@ export class NamedTupleType<T extends SdsDeclaration> extends Type {
149149
/**
150150
* The length of this tuple.
151151
*/
152-
/* c8 ignore start */
153152
get length(): number {
154153
return this.entries.length;
155154
}
156-
/* c8 ignore stop */
157155

158156
/**
159157
* Returns the type of the entry at the given index. If the index is out of bounds, returns `undefined`.

packages/safe-ds-lang/src/language/typing/safe-ds-type-checker.ts

+89-72
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { getContainerOfType } from 'langium';
2+
import type { SafeDsClasses } from '../builtins/safe-ds-classes.js';
23
import { isSdsEnum, SdsDeclaration } from '../generated/ast.js';
34
import {
45
BooleanConstant,
@@ -19,16 +20,18 @@ import {
1920
StaticType,
2021
Type,
2122
UnionType,
23+
UnknownType,
2224
} from './model.js';
2325
import { SafeDsClassHierarchy } from './safe-ds-class-hierarchy.js';
2426
import { SafeDsCoreTypes } from './safe-ds-core-types.js';
2527

26-
/* c8 ignore start */
2728
export class SafeDsTypeChecker {
29+
private readonly builtinClasses: SafeDsClasses;
2830
private readonly classHierarchy: SafeDsClassHierarchy;
2931
private readonly coreTypes: SafeDsCoreTypes;
3032

3133
constructor(services: SafeDsServices) {
34+
this.builtinClasses = services.builtins.Classes;
3235
this.classHierarchy = services.types.ClassHierarchy;
3336
this.coreTypes = services.types.CoreTypes;
3437
}
@@ -37,6 +40,12 @@ export class SafeDsTypeChecker {
3740
* Checks whether {@link type} is assignable {@link other}.
3841
*/
3942
isAssignableTo(type: Type, other: Type): boolean {
43+
if (type === UnknownType || other === UnknownType) {
44+
return false;
45+
} else if (other instanceof UnionType) {
46+
return other.possibleTypes.some((it) => this.isAssignableTo(type, it));
47+
}
48+
4049
if (type instanceof CallableType) {
4150
return this.callableTypeIsAssignableTo(type, other);
4251
} else if (type instanceof ClassType) {
@@ -53,48 +62,66 @@ export class SafeDsTypeChecker {
5362
return this.staticTypeIsAssignableTo(type, other);
5463
} else if (type instanceof UnionType) {
5564
return this.unionTypeIsAssignableTo(type, other);
56-
} else {
57-
return false;
58-
}
65+
} /* c8 ignore start */ else {
66+
throw new Error(`Unexpected type: ${type.constructor.name}`);
67+
} /* c8 ignore stop */
5968
}
6069

6170
private callableTypeIsAssignableTo(type: CallableType, other: Type): boolean {
62-
// return when (val unwrappedOther = unwrapVariadicType(other)) {
63-
// is CallableType -> {
64-
// // TODO: We need to compare names of parameters & results and can allow additional optional parameters
65-
//
66-
// // Sizes must match (too strict requirement -> should be loosened later)
67-
// if (this.parameters.size != unwrappedOther.parameters.size || this.results.size != this.results.size) {
68-
// return false
69-
// }
70-
//
71-
// // Actual parameters must be supertypes of expected parameters (contravariance)
72-
// this.parameters.zip(unwrappedOther.parameters).forEach { (thisParameter, otherParameter) ->
73-
// if (!otherParameter.isSubstitutableFor(thisParameter)) {
74-
// return false
75-
// }
76-
// }
77-
//
78-
// // Expected results must be subtypes of expected results (covariance)
79-
// this.results.zip(unwrappedOther.results).forEach { (thisResult, otherResult) ->
80-
// if (!thisResult.isSubstitutableFor(otherResult)) {
81-
// return false
82-
// }
83-
// }
84-
//
85-
// true
86-
// }
87-
// is ClassType -> {
88-
// unwrappedOther.sdsClass.qualifiedNameOrNull() == StdlibClasses.Any
89-
// }
90-
// is UnionType -> {
91-
// unwrappedOther.possibleTypes.any { this.isSubstitutableFor(it) }
92-
// }
93-
// else -> false
94-
// }
95-
// }
71+
if (other instanceof ClassType) {
72+
return other.declaration === this.builtinClasses.Any;
73+
} else if (other instanceof CallableType) {
74+
// Must accept at least as many parameters and produce at least as many results
75+
if (type.inputType.length < other.inputType.length || type.outputType.length < other.outputType.length) {
76+
return false;
77+
}
9678

97-
return type.equals(other);
79+
// Check expected parameters
80+
for (let i = 0; i < other.inputType.length; i++) {
81+
const typeEntry = type.inputType.entries[i];
82+
const otherEntry = other.inputType.entries[i];
83+
84+
// Names must match
85+
if (typeEntry.name !== otherEntry.name) {
86+
return false;
87+
}
88+
89+
// Types must be contravariant
90+
if (!this.isAssignableTo(otherEntry.type, typeEntry.type)) {
91+
return false;
92+
}
93+
}
94+
95+
// Additional parameters must be optional
96+
for (let i = other.inputType.length; i < type.inputType.length; i++) {
97+
const typeEntry = type.inputType.entries[i];
98+
if (!typeEntry.declaration?.defaultValue) {
99+
return false;
100+
}
101+
}
102+
103+
// Check expected results
104+
for (let i = 0; i < other.outputType.length; i++) {
105+
const typeEntry = type.outputType.entries[i];
106+
const otherEntry = other.outputType.entries[i];
107+
108+
// Names must match
109+
if (typeEntry.name !== otherEntry.name) {
110+
return false;
111+
}
112+
113+
// Types must be covariant
114+
if (!this.isAssignableTo(typeEntry.type, otherEntry.type)) {
115+
return false;
116+
}
117+
}
118+
119+
// Additional results are OK
120+
121+
return true;
122+
} else {
123+
return false;
124+
}
98125
}
99126

100127
private classTypeIsAssignableTo(type: ClassType, other: Type): boolean {
@@ -104,8 +131,6 @@ export class SafeDsTypeChecker {
104131

105132
if (other instanceof ClassType) {
106133
return this.classHierarchy.isEqualToOrSubclassOf(type.declaration, other.declaration);
107-
} else if (other instanceof UnionType) {
108-
return other.possibleTypes.some((it) => this.isAssignableTo(type, it));
109134
} else {
110135
return false;
111136
}
@@ -116,45 +141,30 @@ export class SafeDsTypeChecker {
116141
return false;
117142
}
118143

119-
if (other instanceof EnumType) {
144+
if (other instanceof ClassType) {
145+
return other.declaration === this.builtinClasses.Any;
146+
} else if (other instanceof EnumType) {
120147
return type.declaration === other.declaration;
148+
} else {
149+
return false;
121150
}
122-
123-
// return when (val unwrappedOther = unwrapVariadicType(other)) {
124-
// is ClassType -> {
125-
// (!this.isNullable || unwrappedOther.isNullable) &&
126-
// unwrappedOther.sdsClass.qualifiedNameOrNull() == StdlibClasses.Any
127-
// }
128-
// is UnionType -> {
129-
// unwrappedOther.possibleTypes.any { this.isSubstitutableFor(it) }
130-
// }
131-
// else -> false
132-
// }
133-
134-
return type.equals(other);
135151
}
136152

137153
private enumVariantTypeIsAssignableTo(type: EnumVariantType, other: Type): boolean {
138154
if (type.isNullable && !other.isNullable) {
139155
return false;
140156
}
141157

142-
if (other instanceof EnumType) {
158+
if (other instanceof ClassType) {
159+
return other.declaration === this.builtinClasses.Any;
160+
} else if (other instanceof EnumType) {
143161
const containingEnum = getContainerOfType(type.declaration, isSdsEnum);
144162
return containingEnum === other.declaration;
145163
} else if (other instanceof EnumVariantType) {
146164
return type.declaration === other.declaration;
165+
} else {
166+
return false;
147167
}
148-
// return when (val unwrappedOther = unwrapVariadicType(other)) {
149-
// is ClassType -> {
150-
// (!this.isNullable || unwrappedOther.isNullable) &&
151-
// unwrappedOther.sdsClass.qualifiedNameOrNull() == StdlibClasses.Any
152-
// }
153-
// is UnionType -> unwrappedOther.possibleTypes.any { this.isSubstitutableFor(it) }
154-
// else -> false
155-
// }
156-
157-
return type.equals(other);
158168
}
159169

160170
private literalTypeIsAssignableTo(type: LiteralType, other: Type): boolean {
@@ -173,7 +183,6 @@ export class SafeDsTypeChecker {
173183
other.constants.some((otherConstant) => constant.equals(otherConstant)),
174184
);
175185
} else {
176-
// TODO: union type
177186
return false;
178187
}
179188
}
@@ -197,18 +206,26 @@ export class SafeDsTypeChecker {
197206
return this.isAssignableTo(classType, other);
198207
}
199208

200-
/* c8 ignore start */
201209
private namedTupleTypeIsAssignableTo(type: NamedTupleType<SdsDeclaration>, other: Type): boolean {
202-
return type.equals(other);
210+
if (other instanceof NamedTupleType) {
211+
return (
212+
type.length === other.length &&
213+
type.entries.every((typeEntry, index) => {
214+
const otherEntry = other.entries[index];
215+
// We deliberately ignore the declarations here
216+
return typeEntry.name === otherEntry.name && this.isAssignableTo(typeEntry.type, otherEntry.type);
217+
})
218+
);
219+
} else {
220+
return false;
221+
}
203222
}
204223

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

209228
private unionTypeIsAssignableTo(type: UnionType, other: Type): boolean {
210-
// return this.possibleTypes.all { it.isSubstitutableFor(other) }
211-
return type.equals(other);
229+
return type.possibleTypes.every((it) => this.isAssignableTo(it, other));
212230
}
213231
}
214-
/* c8 ignore stop */

0 commit comments

Comments
 (0)