Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: error if class or enum are statically referenced #643

Merged
merged 3 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}