Skip to content

Commit 8b076e7

Browse files
feat: error if class or enum are statically referenced (#643)
Closes partially #543 ### Summary of Changes Show an error if a class or enum is statically referenced. They must only be referenced to access one of their members/variants or to call them¹. ----- ¹ If they are not callable, we already show another error. --------- Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com>
1 parent f5ee1bd commit 8b076e7

File tree

6 files changed

+154
-15
lines changed

6 files changed

+154
-15
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,8 @@ export class SafeDsTypeComputer {
419419
}
420420
}
421421

422-
return memberType.copyWithNullability(node.isNullSafe || memberType.isNullable);
422+
const receiverType = this.computeType(node.receiver);
423+
return memberType.copyWithNullability((receiverType.isNullable && node.isNullSafe) || memberType.isNullable);
423424
}
424425

425426
private computeTypeOfArithmeticPrefixOperation(node: SdsPrefixOperation): Type {

src/language/validation/other/expressions/references.ts

+47-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {
22
isSdsAnnotation,
33
isSdsCall,
4+
isSdsClass,
5+
isSdsEnum,
46
isSdsFunction,
57
isSdsMemberAccess,
68
isSdsPipeline,
@@ -11,20 +13,23 @@ import {
1113
import { AstNode, ValidationAcceptor } from 'langium';
1214

1315
export const CODE_REFERENCE_FUNCTION_POINTER = 'reference/function-pointer';
16+
export const CODE_REFERENCE_STATIC_CLASS_REFERENCE = 'reference/static-class-reference';
17+
export const CODE_REFERENCE_STATIC_ENUM_REFERENCE = 'reference/static-enum-reference';
1418
export const CODE_REFERENCE_TARGET = 'reference/target';
1519

1620
export const referenceMustNotBeFunctionPointer = (node: SdsReference, accept: ValidationAcceptor): void => {
17-
const target = node.target?.ref;
21+
const target = node.target.ref;
1822
if (!isSdsFunction(target) && !isSdsSegment(target)) {
1923
return;
2024
}
2125

22-
let container: AstNode | undefined = node.$container;
23-
if (isSdsMemberAccess(container) && node.$containerProperty === 'member') {
24-
container = container.$container;
26+
// Get the containing member access if the node is on its right side
27+
let nodeOrContainer: AstNode | undefined = node;
28+
if (isSdsMemberAccess(node.$container) && node.$containerProperty === 'member') {
29+
nodeOrContainer = nodeOrContainer.$container;
2530
}
2631

27-
if (!isSdsCall(container)) {
32+
if (!isSdsCall(nodeOrContainer?.$container)) {
2833
accept(
2934
'error',
3035
'Function pointers are not allowed to provide a cleaner graphical view. Use a lambda instead.',
@@ -36,11 +41,47 @@ export const referenceMustNotBeFunctionPointer = (node: SdsReference, accept: Va
3641
}
3742
};
3843

44+
export const referenceMustNotBeStaticClassOrEnumReference = (node: SdsReference, accept: ValidationAcceptor) => {
45+
const target = node.target.ref;
46+
if (!isSdsClass(target) && !isSdsEnum(target)) {
47+
return;
48+
}
49+
50+
// Get the containing member access if the node is on its right side
51+
let nodeOrContainer: AstNode | undefined = node;
52+
if (isSdsMemberAccess(node.$container) && node.$containerProperty === 'member') {
53+
nodeOrContainer = nodeOrContainer.$container;
54+
}
55+
56+
// Access to a member of the class or enum
57+
if (isSdsMemberAccess(nodeOrContainer?.$container) && nodeOrContainer?.$containerProperty === 'receiver') {
58+
return;
59+
}
60+
61+
// Call of the class or enum
62+
if (isSdsCall(nodeOrContainer?.$container)) {
63+
return;
64+
}
65+
66+
// Static reference to the class or enum
67+
if (isSdsClass(target)) {
68+
accept('error', 'A class must not be statically referenced.', {
69+
node,
70+
code: CODE_REFERENCE_STATIC_CLASS_REFERENCE,
71+
});
72+
} else if (isSdsEnum(target)) {
73+
accept('error', 'An enum must not be statically referenced.', {
74+
node,
75+
code: CODE_REFERENCE_STATIC_ENUM_REFERENCE,
76+
});
77+
}
78+
};
79+
3980
export const referenceTargetMustNotBeAnnotationPipelineOrSchema = (
4081
node: SdsReference,
4182
accept: ValidationAcceptor,
4283
): void => {
43-
const target = node.target?.ref;
84+
const target = node.target.ref;
4485

4586
if (isSdsAnnotation(target)) {
4687
accept('error', 'An annotation must not be the target of a reference.', {

src/language/validation/safe-ds-validator.ts

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
import { argumentListMustNotHavePositionalArgumentsAfterNamedArguments } from './other/argumentLists.js';
6161
import {
6262
referenceMustNotBeFunctionPointer,
63+
referenceMustNotBeStaticClassOrEnumReference,
6364
referenceTargetMustNotBeAnnotationPipelineOrSchema,
6465
} from './other/expressions/references.js';
6566
import {
@@ -197,6 +198,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
197198
SdsPlaceholder: [placeholdersMustNotBeAnAlias, placeholderShouldBeUsed(services)],
198199
SdsReference: [
199200
referenceMustNotBeFunctionPointer,
201+
referenceMustNotBeStaticClassOrEnumReference,
200202
referenceTargetMustNotBeAnnotationPipelineOrSchema,
201203
referenceTargetShouldNotBeDeprecated(services),
202204
referenceTargetShouldNotExperimental(services),
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
11
package tests.typing.expressions.memberAccesses.toOther
22

3-
class C {
3+
class C() {
44
// $TEST$ equivalence_class nonNullableMember
5-
static attr »nonNullableMember«: Int
5+
attr »nonNullableMember«: Int
66

77
// $TEST$ equivalence_class nullableMember
8-
static attr »nullableMember«: Any?
8+
attr »nullableMember«: Any?
99
}
1010

11+
fun nullableC() -> result: C?
12+
1113
pipeline myPipeline {
1214
// $TEST$ equivalence_class nonNullableMember
13-
»C.nonNullableMember«;
15+
»C().nonNullableMember«;
1416
// $TEST$ equivalence_class nullableMember
15-
»C.nullableMember«;
17+
»C().nullableMember«;
18+
19+
// $TEST$ equivalence_class nonNullableMember
20+
»C()?.nonNullableMember«;
21+
// $TEST$ equivalence_class nullableMember
22+
»C()?.nullableMember«;
23+
24+
25+
// $TEST$ equivalence_class nonNullableMember
26+
»nullableC().nonNullableMember«;
27+
// $TEST$ equivalence_class nullableMember
28+
»nullableC().nullableMember«;
1629

1730
// $TEST$ serialization Int?
18-
»C?.nonNullableMember«;
19-
// $TEST$ serialization Any?
20-
»C?.nullableMember«;
31+
»nullableC()?.nonNullableMember«;
32+
// $TEST$ equivalence_class nullableMember
33+
»nullableC()?.nullableMember«;
2134
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package tests.validation.other.expressions.references.staticClassReference
2+
3+
class ClassWithConstructor()
4+
5+
class ClassWithoutConstructor
6+
7+
class ClassWithStaticMembers {
8+
static attr myAttribute: Int
9+
10+
class InnerClassWithConstructor() {
11+
static attr myAttribute: Int
12+
}
13+
14+
class InnerClassWithoutConstructor
15+
}
16+
17+
pipeline test {
18+
// $TEST$ no error "A class must not be statically referenced."
19+
»Unresolved«;
20+
// $TEST$ error "A class must not be statically referenced."
21+
»ClassWithConstructor«;
22+
// $TEST$ error "A class must not be statically referenced."
23+
»ClassWithoutConstructor«;
24+
// $TEST$ no error "A class must not be statically referenced."
25+
»ClassWithoutConstructor«();
26+
// $TEST$ no error "A class must not be statically referenced."
27+
»ClassWithConstructor«();
28+
// $TEST$ no error "A class must not be statically referenced."
29+
»ClassWithStaticMembers«.myAttribute;
30+
// $TEST$ no error "A class must not be statically referenced."
31+
»ClassWithStaticMembers«.unresolved;
32+
// $TEST$ no error "A class must not be statically referenced."
33+
// $TEST$ error "A class must not be statically referenced."
34+
»ClassWithStaticMembers«.»InnerClassWithConstructor«;
35+
// $TEST$ no error "A class must not be statically referenced."
36+
// $TEST$ error "A class must not be statically referenced."
37+
»ClassWithStaticMembers«.»InnerClassWithoutConstructor«;
38+
// $TEST$ no error "A class must not be statically referenced."
39+
// $TEST$ no error "A class must not be statically referenced."
40+
»ClassWithStaticMembers«.»InnerClassWithConstructor«();
41+
// $TEST$ no error "A class must not be statically referenced."
42+
// $TEST$ no error "A class must not be statically referenced."
43+
»ClassWithStaticMembers«.»InnerClassWithConstructor«.myAttribute;
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package tests.validation.other.expressions.references.staticEnumReference
2+
3+
enum Enum {
4+
Variant
5+
}
6+
7+
class ClassWithEnum {
8+
enum Enum {
9+
Variant
10+
}
11+
12+
class ClassWithEnum {
13+
enum Enum {
14+
Variant
15+
}
16+
}
17+
}
18+
19+
pipeline test {
20+
// $TEST$ no error "An enum must not be statically referenced."
21+
»Unresolved«;
22+
// $TEST$ error "An enum must not be statically referenced."
23+
»Enum«;
24+
// $TEST$ no error "An enum must not be statically referenced."
25+
»Enum«();
26+
// $TEST$ no error "An enum must not be statically referenced."
27+
»Enum«.Variant;
28+
// $TEST$ no error "An enum must not be statically referenced."
29+
»Enum«.unresolved;
30+
// $TEST$ error "An enum must not be statically referenced."
31+
ClassWithEnum.»Enum«;
32+
// $TEST$ no error "An enum must not be statically referenced."
33+
ClassWithEnum.»Enum«.Variant;
34+
// $TEST$ error "An enum must not be statically referenced."
35+
ClassWithEnum.ClassWithEnum.»Enum«;
36+
// $TEST$ no error "An enum must not be statically referenced."
37+
ClassWithEnum.ClassWithEnum.»Enum«.Variant;
38+
}

0 commit comments

Comments
 (0)