Skip to content

Commit

Permalink
feat: error if class or enum are statically referenced (#643)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
lars-reimann and megalinter-bot authored Oct 17, 2023
1 parent f5ee1bd commit 8b076e7
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 15 deletions.
3 changes: 2 additions & 1 deletion src/language/typing/safe-ds-type-computer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,8 @@ export class SafeDsTypeComputer {
}
}

return memberType.copyWithNullability(node.isNullSafe || memberType.isNullable);
const receiverType = this.computeType(node.receiver);
return memberType.copyWithNullability((receiverType.isNullable && node.isNullSafe) || memberType.isNullable);
}

private computeTypeOfArithmeticPrefixOperation(node: SdsPrefixOperation): Type {
Expand Down
53 changes: 47 additions & 6 deletions src/language/validation/other/expressions/references.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
isSdsAnnotation,
isSdsCall,
isSdsClass,
isSdsEnum,
isSdsFunction,
isSdsMemberAccess,
isSdsPipeline,
Expand All @@ -11,20 +13,23 @@ import {
import { AstNode, ValidationAcceptor } from 'langium';

export const CODE_REFERENCE_FUNCTION_POINTER = 'reference/function-pointer';
export const CODE_REFERENCE_STATIC_CLASS_REFERENCE = 'reference/static-class-reference';
export const CODE_REFERENCE_STATIC_ENUM_REFERENCE = 'reference/static-enum-reference';
export const CODE_REFERENCE_TARGET = 'reference/target';

export const referenceMustNotBeFunctionPointer = (node: SdsReference, accept: ValidationAcceptor): void => {
const target = node.target?.ref;
const target = node.target.ref;
if (!isSdsFunction(target) && !isSdsSegment(target)) {
return;
}

let container: AstNode | undefined = node.$container;
if (isSdsMemberAccess(container) && node.$containerProperty === 'member') {
container = container.$container;
// Get the containing member access if the node is on its right side
let nodeOrContainer: AstNode | undefined = node;
if (isSdsMemberAccess(node.$container) && node.$containerProperty === 'member') {
nodeOrContainer = nodeOrContainer.$container;
}

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

export const referenceMustNotBeStaticClassOrEnumReference = (node: SdsReference, accept: ValidationAcceptor) => {
const target = node.target.ref;
if (!isSdsClass(target) && !isSdsEnum(target)) {
return;
}

// Get the containing member access if the node is on its right side
let nodeOrContainer: AstNode | undefined = node;
if (isSdsMemberAccess(node.$container) && node.$containerProperty === 'member') {
nodeOrContainer = nodeOrContainer.$container;
}

// Access to a member of the class or enum
if (isSdsMemberAccess(nodeOrContainer?.$container) && nodeOrContainer?.$containerProperty === 'receiver') {
return;
}

// Call of the class or enum
if (isSdsCall(nodeOrContainer?.$container)) {
return;
}

// Static reference to the class or enum
if (isSdsClass(target)) {
accept('error', 'A class must not be statically referenced.', {
node,
code: CODE_REFERENCE_STATIC_CLASS_REFERENCE,
});
} else if (isSdsEnum(target)) {
accept('error', 'An enum must not be statically referenced.', {
node,
code: CODE_REFERENCE_STATIC_ENUM_REFERENCE,
});
}
};

export const referenceTargetMustNotBeAnnotationPipelineOrSchema = (
node: SdsReference,
accept: ValidationAcceptor,
): void => {
const target = node.target?.ref;
const target = node.target.ref;

if (isSdsAnnotation(target)) {
accept('error', 'An annotation must not be the target of a reference.', {
Expand Down
2 changes: 2 additions & 0 deletions src/language/validation/safe-ds-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import {
import { argumentListMustNotHavePositionalArgumentsAfterNamedArguments } from './other/argumentLists.js';
import {
referenceMustNotBeFunctionPointer,
referenceMustNotBeStaticClassOrEnumReference,
referenceTargetMustNotBeAnnotationPipelineOrSchema,
} from './other/expressions/references.js';
import {
Expand Down Expand Up @@ -197,6 +198,7 @@ export const registerValidationChecks = function (services: SafeDsServices) {
SdsPlaceholder: [placeholdersMustNotBeAnAlias, placeholderShouldBeUsed(services)],
SdsReference: [
referenceMustNotBeFunctionPointer,
referenceMustNotBeStaticClassOrEnumReference,
referenceTargetMustNotBeAnnotationPipelineOrSchema,
referenceTargetShouldNotBeDeprecated(services),
referenceTargetShouldNotExperimental(services),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
package tests.typing.expressions.memberAccesses.toOther

class C {
class C() {
// $TEST$ equivalence_class nonNullableMember
static attr »nonNullableMember«: Int
attr »nonNullableMember«: Int

// $TEST$ equivalence_class nullableMember
static attr »nullableMember«: Any?
attr »nullableMember«: Any?
}

fun nullableC() -> result: C?

pipeline myPipeline {
// $TEST$ equivalence_class nonNullableMember
»C.nonNullableMember«;
»C().nonNullableMember«;
// $TEST$ equivalence_class nullableMember
»C.nullableMember«;
»C().nullableMember«;

// $TEST$ equivalence_class nonNullableMember
»C()?.nonNullableMember«;
// $TEST$ equivalence_class nullableMember
»C()?.nullableMember«;


// $TEST$ equivalence_class nonNullableMember
»nullableC().nonNullableMember«;
// $TEST$ equivalence_class nullableMember
»nullableC().nullableMember«;

// $TEST$ serialization Int?
»C?.nonNullableMember«;
// $TEST$ serialization Any?
»C?.nullableMember«;
»nullableC()?.nonNullableMember«;
// $TEST$ equivalence_class nullableMember
»nullableC()?.nullableMember«;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package tests.validation.other.expressions.references.staticClassReference

class ClassWithConstructor()

class ClassWithoutConstructor

class ClassWithStaticMembers {
static attr myAttribute: Int

class InnerClassWithConstructor() {
static attr myAttribute: Int
}

class InnerClassWithoutConstructor
}

pipeline test {
// $TEST$ no error "A class must not be statically referenced."
»Unresolved«;
// $TEST$ error "A class must not be statically referenced."
»ClassWithConstructor«;
// $TEST$ error "A class must not be statically referenced."
»ClassWithoutConstructor«;
// $TEST$ no error "A class must not be statically referenced."
»ClassWithoutConstructor«();
// $TEST$ no error "A class must not be statically referenced."
»ClassWithConstructor«();
// $TEST$ no error "A class must not be statically referenced."
»ClassWithStaticMembers«.myAttribute;
// $TEST$ no error "A class must not be statically referenced."
»ClassWithStaticMembers«.unresolved;
// $TEST$ no error "A class must not be statically referenced."
// $TEST$ error "A class must not be statically referenced."
»ClassWithStaticMembers«.»InnerClassWithConstructor«;
// $TEST$ no error "A class must not be statically referenced."
// $TEST$ error "A class must not be statically referenced."
»ClassWithStaticMembers«.»InnerClassWithoutConstructor«;
// $TEST$ no error "A class must not be statically referenced."
// $TEST$ no error "A class must not be statically referenced."
»ClassWithStaticMembers«.»InnerClassWithConstructor«();
// $TEST$ no error "A class must not be statically referenced."
// $TEST$ no error "A class must not be statically referenced."
»ClassWithStaticMembers«.»InnerClassWithConstructor«.myAttribute;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package tests.validation.other.expressions.references.staticEnumReference

enum Enum {
Variant
}

class ClassWithEnum {
enum Enum {
Variant
}

class ClassWithEnum {
enum Enum {
Variant
}
}
}

pipeline test {
// $TEST$ no error "An enum must not be statically referenced."
»Unresolved«;
// $TEST$ error "An enum must not be statically referenced."
»Enum«;
// $TEST$ no error "An enum must not be statically referenced."
»Enum«();
// $TEST$ no error "An enum must not be statically referenced."
»Enum«.Variant;
// $TEST$ no error "An enum must not be statically referenced."
»Enum«.unresolved;
// $TEST$ error "An enum must not be statically referenced."
ClassWithEnum.»Enum«;
// $TEST$ no error "An enum must not be statically referenced."
ClassWithEnum.»Enum«.Variant;
// $TEST$ error "An enum must not be statically referenced."
ClassWithEnum.ClassWithEnum.»Enum«;
// $TEST$ no error "An enum must not be statically referenced."
ClassWithEnum.ClassWithEnum.»Enum«.Variant;
}

0 comments on commit 8b076e7

Please sign in to comment.