Skip to content

Commit ea8fe29

Browse files
authored
feat: error if type parameters don't have sufficient context (#687)
### Summary of Changes Show an error if we already know at the declaration-site that we won't be able to infer type parameters in a call. A type parameter must occur in the parameter list of the containing callable. However, it does not suffice if they appear inside a union type or the parameter list of a callable type.
1 parent 09bfb38 commit ea8fe29

File tree

21 files changed

+147
-169
lines changed

21 files changed

+147
-169
lines changed

src/cli/generator.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ const getExternalReferenceNeededImport = function (
528528
if (isSdsQualifiedImport(value)) {
529529
const importedDeclarations = getImportedDeclarations(value);
530530
for (const importedDeclaration of importedDeclarations) {
531-
if (declaration === importedDeclaration.declaration.ref) {
531+
if (declaration === importedDeclaration.declaration?.ref) {
532532
if (importedDeclaration.alias !== undefined) {
533533
return {
534534
importPath: services.builtins.Annotations.getPythonModule(targetModule) || value.package,

src/language/grammar/safe-ds.langium

+3-3
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ SdsImportedDeclarationList returns SdsImportedDeclarationList:
7777
;
7878

7979
interface SdsImportedDeclaration extends SdsObject {
80-
declaration: @SdsModuleMember;
80+
declaration?: @SdsModuleMember;
8181
alias?: SdsImportedDeclarationAlias;
8282
}
8383

@@ -384,7 +384,7 @@ SdsConstraint returns SdsConstraint:
384384
;
385385

386386
interface SdsTypeParameterConstraint extends SdsConstraint {
387-
leftOperand: @SdsTypeParameter
387+
leftOperand?: @SdsTypeParameter
388388
operator: string
389389
rightOperand: SdsType
390390
}
@@ -922,7 +922,7 @@ SdsLiteralList returns SdsLiteralList:
922922
;
923923

924924
interface SdsNamedType extends SdsType {
925-
declaration: @SdsNamedTypeDeclaration
925+
declaration?: @SdsNamedTypeDeclaration
926926
typeArgumentList?: SdsTypeArgumentList
927927
isNullable: boolean
928928
}

src/language/helpers/safe-ds-node-mapper.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ export class SafeDsNodeMapper {
239239
}
240240

241241
// Find type parameter at the same position
242-
const namedTypeDeclaration = containingType.declaration.ref;
242+
const namedTypeDeclaration = containingType.declaration?.ref;
243243
const typeParameters = getTypeParameters(namedTypeDeclaration);
244244
return typeParameters[typeArgumentPosition];
245245
}

src/language/lsp/safe-ds-semantic-token-provider.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export class SafeDsSemanticTokenProvider extends AbstractSemanticTokenProvider {
8787
type: SemanticTokenTypes.namespace,
8888
});
8989
} else if (isSdsImportedDeclaration(node)) {
90-
const info = this.computeSemanticTokenInfoForDeclaration(node.declaration.ref);
90+
const info = this.computeSemanticTokenInfoForDeclaration(node.declaration?.ref);
9191
if (info) {
9292
acceptor({
9393
node,
@@ -96,7 +96,7 @@ export class SafeDsSemanticTokenProvider extends AbstractSemanticTokenProvider {
9696
});
9797
}
9898
} else if (isSdsNamedType(node)) {
99-
const info = this.computeSemanticTokenInfoForDeclaration(node.declaration.ref);
99+
const info = this.computeSemanticTokenInfoForDeclaration(node.declaration?.ref);
100100
if (info) {
101101
acceptor({
102102
node,

src/language/scoping/safe-ds-scope-provider.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,16 @@ import { isContainedIn } from '../helpers/astUtils.js';
5252
import {
5353
getAbstractResults,
5454
getAssignees,
55-
getMatchingClassMembers,
5655
getEnumVariants,
5756
getImportedDeclarations,
5857
getImports,
59-
isStatic,
58+
getMatchingClassMembers,
6059
getPackageName,
6160
getParameters,
6261
getResults,
6362
getStatements,
6463
getTypeParameters,
64+
isStatic,
6565
} from '../helpers/nodeProperties.js';
6666
import { SafeDsServices } from '../safe-ds-module.js';
6767
import { SafeDsTypeComputer } from '../typing/safe-ds-type-computer.js';
@@ -165,7 +165,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
165165
*/
166166
private getUniqueReferencedDeclarationForType(node: SdsType): SdsNamedTypeDeclaration | undefined {
167167
if (isSdsNamedType(node)) {
168-
return node.declaration.ref;
168+
return node.declaration?.ref;
169169
} else if (isSdsMemberType(node)) {
170170
return node.member?.declaration?.ref;
171171
} else {
@@ -335,7 +335,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
335335
return EMPTY_SCOPE;
336336
}
337337

338-
const namedTypeDeclaration = containingNamedType.declaration.ref;
338+
const namedTypeDeclaration = containingNamedType.declaration?.ref;
339339
if (isSdsClass(namedTypeDeclaration)) {
340340
const typeParameters = getTypeParameters(namedTypeDeclaration.typeParameterList);
341341
return this.createScopeForNodes(typeParameters);
@@ -403,7 +403,7 @@ export class SafeDsScopeProvider extends DefaultScopeProvider {
403403
for (const imp of imports) {
404404
if (isSdsQualifiedImport(imp)) {
405405
for (const importedDeclaration of getImportedDeclarations(imp)) {
406-
const description = importedDeclaration.declaration.$nodeDescription;
406+
const description = importedDeclaration.declaration?.$nodeDescription;
407407
if (!description || !this.astReflection.isSubtype(description.type, referenceType)) {
408408
continue;
409409
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ export class SafeDsTypeChecker {
198198
}
199199

200200
private unionTypeIsAssignableTo(type: UnionType, other: Type): boolean {
201+
// return this.possibleTypes.all { it.isSubstitutableFor(other) }
201202
return type.equals(other);
202203
}
203204
}

src/language/typing/safe-ds-type-computer.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,11 @@ import {
7575
import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js';
7676
import {
7777
getAssignees,
78-
streamBlockLambdaResults,
7978
getLiterals,
8079
getParameters,
8180
getResults,
8281
getTypeArguments,
82+
streamBlockLambdaResults,
8383
} from '../helpers/nodeProperties.js';
8484
import { SafeDsPartialEvaluator } from '../partialEvaluation/safe-ds-partial-evaluator.js';
8585
import { Constant, isConstant } from '../partialEvaluation/model.js';
@@ -453,7 +453,7 @@ export class SafeDsTypeComputer {
453453
} else if (isSdsMemberType(node)) {
454454
return this.computeType(node.member);
455455
} else if (isSdsNamedType(node)) {
456-
return this.computeType(node.declaration.ref).copyWithNullability(node.isNullable);
456+
return this.computeType(node.declaration?.ref).copyWithNullability(node.isNullable);
457457
} else if (isSdsUnionType(node)) {
458458
const typeArguments = getTypeArguments(node.typeArgumentList);
459459
return new UnionType(typeArguments.map((typeArgument) => this.computeType(typeArgument.value)));

src/language/typing/typeConformance.ts

-141
This file was deleted.

src/language/validation/builtins/deprecated.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export const argumentCorrespondingParameterShouldNotBeDeprecated =
7676

7777
export const namedTypeDeclarationShouldNotBeDeprecated =
7878
(services: SafeDsServices) => (node: SdsNamedType, accept: ValidationAcceptor) => {
79-
const declaration = node.declaration.ref;
79+
const declaration = node.declaration?.ref;
8080
if (!declaration) {
8181
return;
8282
}

src/language/validation/builtins/experimental.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export const argumentCorrespondingParameterShouldNotBeExperimental =
6868

6969
export const namedTypeDeclarationShouldNotBeExperimental =
7070
(services: SafeDsServices) => (node: SdsNamedType, accept: ValidationAcceptor) => {
71-
const declaration = node.declaration.ref;
71+
const declaration = node.declaration?.ref;
7272
if (!declaration) {
7373
return;
7474
}

src/language/validation/experimentalLanguageFeatures.ts

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const indexedAccessesShouldBeUsedWithCaution = (node: SdsIndexedAccess, a
2525
export const literalTypesShouldBeUsedWithCaution = (node: SdsLiteralType, accept: ValidationAcceptor): void => {
2626
accept('warning', 'Literal types are experimental and may change without prior notice.', {
2727
node,
28+
keyword: 'literal',
2829
code: CODE_EXPERIMENTAL_LANGUAGE_FEATURE,
2930
});
3031
};
@@ -47,6 +48,7 @@ export const unionTypesShouldBeUsedWithCaution = (node: SdsUnionType, accept: Va
4748

4849
accept('warning', 'Union types are experimental and may change without prior notice.', {
4950
node,
51+
keyword: 'union',
5052
code: CODE_EXPERIMENTAL_LANGUAGE_FEATURE,
5153
});
5254
};

src/language/validation/names.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ export const moduleMustContainUniqueNames = (node: SdsModule, accept: Validation
305305
};
306306

307307
const importedDeclarationName = (node: SdsImportedDeclaration | undefined): string | undefined => {
308-
return node?.alias?.alias ?? node?.declaration.ref?.name;
308+
return node?.alias?.alias ?? node?.declaration?.ref?.name;
309309
};
310310

311311
export const pipelineMustContainUniqueNames = (node: SdsPipeline, accept: ValidationAcceptor): void => {

src/language/validation/other/declarations/typeParameterConstraints.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const typeParameterConstraintLeftOperandMustBeOwnTypeParameter = (
77
node: SdsTypeParameterConstraint,
88
accept: ValidationAcceptor,
99
) => {
10-
const typeParameter = node.leftOperand.ref;
10+
const typeParameter = node.leftOperand?.ref;
1111
if (!typeParameter) {
1212
return;
1313
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import {
2+
isSdsCallable,
3+
isSdsClass,
4+
isSdsParameterList,
5+
isSdsUnionType,
6+
SdsTypeParameter,
7+
} from '../../../generated/ast.js';
8+
import { findLocalReferences, getContainerOfType, hasContainerOfType, ValidationAcceptor } from 'langium';
9+
10+
export const CODE_TYPE_PARAMETER_INSUFFICIENT_CONTEXT = 'type-parameter/insufficient-context';
11+
12+
export const typeParameterMustHaveSufficientContext = (node: SdsTypeParameter, accept: ValidationAcceptor) => {
13+
const containingCallable = getContainerOfType(node, isSdsCallable);
14+
/* c8 ignore start */
15+
if (!containingCallable) {
16+
return;
17+
}
18+
/* c8 ignore stop */
19+
20+
// Classes without constructor can only be used as named types, where type arguments are manifest
21+
if (isSdsClass(containingCallable) && !containingCallable.parameterList) {
22+
return;
23+
}
24+
25+
// A type parameter must be referenced in the parameter list of the containing callable...
26+
let typeParameterHasInsufficientContext =
27+
!containingCallable.parameterList ||
28+
findLocalReferences(node, containingCallable.parameterList)
29+
// ...but references in a union type or in the parameter list of a callable type don't count
30+
.filter((reference) => {
31+
const referenceNode = reference.$refNode?.astNode;
32+
const containingParameterList = getContainerOfType(referenceNode, isSdsParameterList);
33+
34+
return (
35+
!hasContainerOfType(referenceNode, isSdsUnionType) &&
36+
containingParameterList === containingCallable.parameterList
37+
);
38+
})
39+
.isEmpty();
40+
41+
if (typeParameterHasInsufficientContext) {
42+
accept('error', 'Insufficient context to infer this type parameter.', {
43+
node,
44+
code: CODE_TYPE_PARAMETER_INSUFFICIENT_CONTEXT,
45+
});
46+
}
47+
};

src/language/validation/other/types/namedTypes.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,12 @@ export const namedTypeTypeArgumentListMustNotHavePositionalArgumentsAfterNamedAr
5151

5252
export const namedTypeMustNotHaveTooManyTypeArguments = (node: SdsNamedType, accept: ValidationAcceptor): void => {
5353
// If the declaration is unresolved, we already show another error
54-
if (!node.declaration.ref) {
54+
const namedTypeDeclaration = node.declaration?.ref;
55+
if (!namedTypeDeclaration) {
5556
return;
5657
}
5758

58-
const typeParameters = getTypeParameters(node.declaration.ref);
59+
const typeParameters = getTypeParameters(namedTypeDeclaration);
5960
const typeArguments = getTypeArguments(node.typeArgumentList);
6061

6162
if (typeArguments.length > typeParameters.length) {

0 commit comments

Comments
 (0)