1
1
import { getContainerOfType } from 'langium' ;
2
+ import type { SafeDsClasses } from '../builtins/safe-ds-classes.js' ;
2
3
import { isSdsEnum , SdsDeclaration } from '../generated/ast.js' ;
3
4
import {
4
5
BooleanConstant ,
@@ -19,16 +20,18 @@ import {
19
20
StaticType ,
20
21
Type ,
21
22
UnionType ,
23
+ UnknownType ,
22
24
} from './model.js' ;
23
25
import { SafeDsClassHierarchy } from './safe-ds-class-hierarchy.js' ;
24
26
import { SafeDsCoreTypes } from './safe-ds-core-types.js' ;
25
27
26
- /* c8 ignore start */
27
28
export class SafeDsTypeChecker {
29
+ private readonly builtinClasses : SafeDsClasses ;
28
30
private readonly classHierarchy : SafeDsClassHierarchy ;
29
31
private readonly coreTypes : SafeDsCoreTypes ;
30
32
31
33
constructor ( services : SafeDsServices ) {
34
+ this . builtinClasses = services . builtins . Classes ;
32
35
this . classHierarchy = services . types . ClassHierarchy ;
33
36
this . coreTypes = services . types . CoreTypes ;
34
37
}
@@ -37,6 +40,12 @@ export class SafeDsTypeChecker {
37
40
* Checks whether {@link type} is assignable {@link other}.
38
41
*/
39
42
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
+
40
49
if ( type instanceof CallableType ) {
41
50
return this . callableTypeIsAssignableTo ( type , other ) ;
42
51
} else if ( type instanceof ClassType ) {
@@ -53,48 +62,66 @@ export class SafeDsTypeChecker {
53
62
return this . staticTypeIsAssignableTo ( type , other ) ;
54
63
} else if ( type instanceof UnionType ) {
55
64
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 */
59
68
}
60
69
61
70
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
+ }
96
78
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
+ }
98
125
}
99
126
100
127
private classTypeIsAssignableTo ( type : ClassType , other : Type ) : boolean {
@@ -104,8 +131,6 @@ export class SafeDsTypeChecker {
104
131
105
132
if ( other instanceof ClassType ) {
106
133
return this . classHierarchy . isEqualToOrSubclassOf ( type . declaration , other . declaration ) ;
107
- } else if ( other instanceof UnionType ) {
108
- return other . possibleTypes . some ( ( it ) => this . isAssignableTo ( type , it ) ) ;
109
134
} else {
110
135
return false ;
111
136
}
@@ -116,45 +141,30 @@ export class SafeDsTypeChecker {
116
141
return false ;
117
142
}
118
143
119
- if ( other instanceof EnumType ) {
144
+ if ( other instanceof ClassType ) {
145
+ return other . declaration === this . builtinClasses . Any ;
146
+ } else if ( other instanceof EnumType ) {
120
147
return type . declaration === other . declaration ;
148
+ } else {
149
+ return false ;
121
150
}
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 ) ;
135
151
}
136
152
137
153
private enumVariantTypeIsAssignableTo ( type : EnumVariantType , other : Type ) : boolean {
138
154
if ( type . isNullable && ! other . isNullable ) {
139
155
return false ;
140
156
}
141
157
142
- if ( other instanceof EnumType ) {
158
+ if ( other instanceof ClassType ) {
159
+ return other . declaration === this . builtinClasses . Any ;
160
+ } else if ( other instanceof EnumType ) {
143
161
const containingEnum = getContainerOfType ( type . declaration , isSdsEnum ) ;
144
162
return containingEnum === other . declaration ;
145
163
} else if ( other instanceof EnumVariantType ) {
146
164
return type . declaration === other . declaration ;
165
+ } else {
166
+ return false ;
147
167
}
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 ) ;
158
168
}
159
169
160
170
private literalTypeIsAssignableTo ( type : LiteralType , other : Type ) : boolean {
@@ -173,7 +183,6 @@ export class SafeDsTypeChecker {
173
183
other . constants . some ( ( otherConstant ) => constant . equals ( otherConstant ) ) ,
174
184
) ;
175
185
} else {
176
- // TODO: union type
177
186
return false ;
178
187
}
179
188
}
@@ -197,18 +206,26 @@ export class SafeDsTypeChecker {
197
206
return this . isAssignableTo ( classType , other ) ;
198
207
}
199
208
200
- /* c8 ignore start */
201
209
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
+ }
203
222
}
204
223
205
224
private staticTypeIsAssignableTo ( type : Type , other : Type ) : boolean {
206
225
return type . equals ( other ) ;
207
226
}
208
227
209
228
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 ) ) ;
212
230
}
213
231
}
214
- /* c8 ignore stop */
0 commit comments