diff --git a/src/language/builtins/safe-ds-annotations.ts b/src/language/builtins/safe-ds-annotations.ts index 6a59ef51a..4229f2315 100644 --- a/src/language/builtins/safe-ds-annotations.ts +++ b/src/language/builtins/safe-ds-annotations.ts @@ -6,7 +6,7 @@ import { URI } from 'langium'; import { SafeDsServices } from '../safe-ds-module.js'; import { SafeDsNodeMapper } from '../helpers/safe-ds-node-mapper.js'; import { toConstantExpressionOrUndefined } from '../partialEvaluation/toConstantExpressionOrUndefined.js'; -import { SdsConstantExpression, SdsConstantString } from '../partialEvaluation/model.js'; +import { ConstantExpression, ConstantString } from '../partialEvaluation/model.js'; const ANNOTATION_USAGE_URI = resourceNameToUri('builtins/safeds/lang/annotationUsage.sdsstub'); const CODE_GENERATION_URI = resourceNameToUri('builtins/safeds/lang/codeGeneration.sdsstub'); @@ -48,7 +48,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { getPythonModule(node: SdsModule | undefined): string | undefined { const value = this.getArgumentValue(node, this.PythonModule, 'qualifiedName'); - if (value instanceof SdsConstantString) { + if (value instanceof ConstantString) { return value.value; } else { return undefined; @@ -61,7 +61,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { getPythonName(node: SdsAnnotatedObject | undefined): string | undefined { const value = this.getArgumentValue(node, this.PythonName, 'name'); - if (value instanceof SdsConstantString) { + if (value instanceof ConstantString) { return value.value; } else { return undefined; @@ -92,7 +92,7 @@ export class SafeDsAnnotations extends SafeDsModuleMembers { node: SdsAnnotatedObject | undefined, annotation: SdsAnnotation | undefined, parameterName: string, - ): SdsConstantExpression | undefined { + ): ConstantExpression | undefined { const annotationCall = findFirstAnnotationCallOf(node, annotation); const expression = argumentsOrEmpty(annotationCall).find( (it) => this.nodeMapper.argumentToParameterOrUndefined(it)?.name === parameterName, diff --git a/src/language/partialEvaluation/model.ts b/src/language/partialEvaluation/model.ts index 0dcee9084..cc3023474 100644 --- a/src/language/partialEvaluation/model.ts +++ b/src/language/partialEvaluation/model.ts @@ -10,23 +10,23 @@ import { } from '../generated/ast.js'; /* c8 ignore start */ -export type ParameterSubstitutions = Map; -export type ResultSubstitutions = Map; +export type ParameterSubstitutions = Map; +export type ResultSubstitutions = Map; -export abstract class SdsSimplifiedExpression { +export abstract class SimplifiedExpression { /** * Removes any unnecessary containers from the expression. */ - unwrap(): SdsSimplifiedExpression { + unwrap(): SimplifiedExpression { return this; } } -export abstract class SdsIntermediateExpression extends SdsSimplifiedExpression {} +export abstract class IntermediateExpression extends SimplifiedExpression {} -export abstract class SdsIntermediateCallable extends SdsIntermediateExpression {} +export abstract class IntermediateCallable extends IntermediateExpression {} -export class SdsIntermediateBlockLambda extends SdsIntermediateCallable { +export class IntermediateBlockLambda extends IntermediateCallable { constructor( readonly parameters: SdsParameter[], readonly results: SdsBlockLambdaResult[], @@ -36,7 +36,7 @@ export class SdsIntermediateBlockLambda extends SdsIntermediateCallable { } } -export class SdsIntermediateExpressionLambda extends SdsIntermediateCallable { +export class IntermediateExpressionLambda extends IntermediateCallable { constructor( readonly parameters: SdsParameter[], readonly result: SdsExpression, @@ -46,7 +46,7 @@ export class SdsIntermediateExpressionLambda extends SdsIntermediateCallable { } } -export class SdsIntermediateStep extends SdsIntermediateCallable { +export class IntermediateStep extends IntermediateCallable { constructor( readonly parameters: SdsParameter[], readonly results: SdsResult[], @@ -55,12 +55,12 @@ export class SdsIntermediateStep extends SdsIntermediateCallable { } } -export class SdsIntermediateRecord extends SdsIntermediateExpression { +export class IntermediateRecord extends IntermediateExpression { constructor(readonly resultSubstitutions: ResultSubstitutions) { super(); } - getSubstitutionByReferenceOrNull(reference: SdsReference): SdsSimplifiedExpression | null { + getSubstitutionByReferenceOrNull(reference: SdsReference): SimplifiedExpression | null { const referencedDeclaration = reference.target; if (!isSdsAbstractResult(referencedDeclaration)) { return null; @@ -69,7 +69,7 @@ export class SdsIntermediateRecord extends SdsIntermediateExpression { return this.resultSubstitutions.get(referencedDeclaration) ?? null; } - getSubstitutionByIndexOrNull(index: number | null): SdsSimplifiedExpression | null { + getSubstitutionByIndexOrNull(index: number | null): SimplifiedExpression | null { if (index === null) { return null; } @@ -79,7 +79,7 @@ export class SdsIntermediateRecord extends SdsIntermediateExpression { /** * If the record contains exactly one substitution its value is returned. Otherwise, it returns `this`. */ - override unwrap(): SdsSimplifiedExpression { + override unwrap(): SimplifiedExpression { if (this.resultSubstitutions.size === 1) { return this.resultSubstitutions.values().next().value; } else { @@ -95,12 +95,12 @@ export class SdsIntermediateRecord extends SdsIntermediateExpression { } } -export class SdsIntermediateVariadicArguments extends SdsIntermediateExpression { - constructor(readonly arguments_: (SdsSimplifiedExpression | null)[]) { +export class IntermediateVariadicArguments extends IntermediateExpression { + constructor(readonly arguments_: (SimplifiedExpression | null)[]) { super(); } - getArgumentByIndexOrNull(index: number | null): SdsSimplifiedExpression | null { + getArgumentByIndexOrNull(index: number | null): SimplifiedExpression | null { if (index === null) { return null; } @@ -108,8 +108,8 @@ export class SdsIntermediateVariadicArguments extends SdsIntermediateExpression } } -export abstract class SdsConstantExpression extends SdsSimplifiedExpression { - abstract equals(other: SdsConstantExpression): boolean; +export abstract class ConstantExpression extends SimplifiedExpression { + abstract equals(other: ConstantExpression): boolean; abstract override toString(): string; @@ -121,13 +121,13 @@ export abstract class SdsConstantExpression extends SdsSimplifiedExpression { } } -export class SdsConstantBoolean extends SdsConstantExpression { +export class ConstantBoolean extends ConstantExpression { constructor(readonly value: boolean) { super(); } - equals(other: SdsConstantExpression): boolean { - return other instanceof SdsConstantBoolean && this.value === other.value; + equals(other: ConstantExpression): boolean { + return other instanceof ConstantBoolean && this.value === other.value; } toString(): string { @@ -135,13 +135,13 @@ export class SdsConstantBoolean extends SdsConstantExpression { } } -export class SdsConstantEnumVariant extends SdsConstantExpression { +export class ConstantEnumVariant extends ConstantExpression { constructor(readonly value: SdsEnumVariant) { super(); } - equals(other: SdsConstantExpression): boolean { - return other instanceof SdsConstantEnumVariant && this.value === other.value; + equals(other: ConstantExpression): boolean { + return other instanceof ConstantEnumVariant && this.value === other.value; } toString(): string { @@ -149,15 +149,15 @@ export class SdsConstantEnumVariant extends SdsConstantExpression { } } -export abstract class SdsConstantNumber extends SdsConstantExpression {} +export abstract class ConstantNumber extends ConstantExpression {} -export class SdsConstantFloat extends SdsConstantNumber { +export class ConstantFloat extends ConstantNumber { constructor(readonly value: number) { super(); } - equals(other: SdsConstantExpression): boolean { - return other instanceof SdsConstantFloat && this.value === other.value; + equals(other: ConstantExpression): boolean { + return other instanceof ConstantFloat && this.value === other.value; } toString(): string { @@ -165,13 +165,13 @@ export class SdsConstantFloat extends SdsConstantNumber { } } -export class SdsConstantInt extends SdsConstantNumber { +export class ConstantInt extends ConstantNumber { constructor(readonly value: bigint) { super(); } - equals(other: SdsConstantExpression): boolean { - return other instanceof SdsConstantInt && this.value === other.value; + equals(other: ConstantExpression): boolean { + return other instanceof ConstantInt && this.value === other.value; } toString(): string { @@ -179,9 +179,9 @@ export class SdsConstantInt extends SdsConstantNumber { } } -export class SdsConstantNull extends SdsConstantExpression { - equals(other: SdsConstantExpression): boolean { - return other instanceof SdsConstantNull; +class ConstantNullClass extends ConstantExpression { + equals(other: ConstantExpression): boolean { + return other instanceof ConstantNullClass; } toString(): string { @@ -189,13 +189,15 @@ export class SdsConstantNull extends SdsConstantExpression { } } -export class SdsConstantString extends SdsConstantExpression { +export const ConstantNull = new ConstantNullClass(); + +export class ConstantString extends ConstantExpression { constructor(readonly value: string) { super(); } - equals(other: SdsConstantExpression): boolean { - return other instanceof SdsConstantString && this.value === other.value; + equals(other: ConstantExpression): boolean { + return other instanceof ConstantString && this.value === other.value; } toString(): string { diff --git a/src/language/partialEvaluation/toConstantExpressionOrUndefined.ts b/src/language/partialEvaluation/toConstantExpressionOrUndefined.ts index b7b47ee94..062c0244c 100644 --- a/src/language/partialEvaluation/toConstantExpressionOrUndefined.ts +++ b/src/language/partialEvaluation/toConstantExpressionOrUndefined.ts @@ -1,14 +1,14 @@ import { ParameterSubstitutions, - SdsConstantBoolean, - SdsConstantExpression, - SdsConstantFloat, - SdsConstantInt, - SdsConstantNull, - SdsConstantString, - SdsIntermediateBlockLambda, - SdsIntermediateExpressionLambda, - SdsSimplifiedExpression, + ConstantBoolean, + ConstantExpression, + ConstantFloat, + ConstantInt, + ConstantNull, + ConstantString, + IntermediateBlockLambda, + IntermediateExpressionLambda, + SimplifiedExpression, } from './model.js'; import { AstNode } from 'langium'; import { @@ -47,7 +47,7 @@ import { /** * Tries to evaluate this expression. On success an SdsConstantExpression is returned, otherwise `undefined`. */ -export const toConstantExpressionOrUndefined = (node: AstNode | undefined): SdsConstantExpression | undefined => { +export const toConstantExpressionOrUndefined = (node: AstNode | undefined): ConstantExpression | undefined => { if (!node) { return undefined; } @@ -58,16 +58,16 @@ export const toConstantExpressionOrUndefined = (node: AstNode | undefined): SdsC const toConstantExpressionOrUndefinedImpl = ( node: AstNode, substitutions: ParameterSubstitutions, -): SdsConstantExpression | undefined => { +): ConstantExpression | undefined => { const simplifiedExpression = simplify(node, substitutions)?.unwrap(); - if (simplifiedExpression instanceof SdsConstantExpression) { + if (simplifiedExpression instanceof ConstantExpression) { return simplifiedExpression; } else { return undefined; } }; -const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SdsSimplifiedExpression | undefined => { +const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SimplifiedExpression | undefined => { // Only expressions have a value if (!isSdsExpression(node)) { return undefined; @@ -75,21 +75,21 @@ const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SdsSimp // Base cases if (isSdsBoolean(node)) { - return new SdsConstantBoolean(node.value); + return new ConstantBoolean(node.value); } else if (isSdsFloat(node)) { - return new SdsConstantFloat(node.value); + return new ConstantFloat(node.value); } else if (isSdsInt(node)) { - return new SdsConstantFloat(node.value); + return new ConstantInt(BigInt(node.value)); } else if (isSdsNull(node)) { - return new SdsConstantNull(); + return ConstantNull; } else if (isSdsString(node)) { - return new SdsConstantString(node.value); + return new ConstantString(node.value); } else if (isSdsTemplateStringStart(node)) { - return new SdsConstantString(node.value); + return new ConstantString(node.value); } else if (isSdsTemplateStringInner(node)) { - return new SdsConstantString(node.value); + return new ConstantString(node.value); } else if (isSdsTemplateStringEnd(node)) { - return new SdsConstantString(node.value); + return new ConstantString(node.value); } else if (isSdsBlockLambda(node)) { return simplifyBlockLambda(node, substitutions); } else if (isSdsExpressionLambda(node)) { @@ -128,7 +128,7 @@ const simplify = (node: AstNode, substitutions: ParameterSubstitutions): SdsSimp const simplifyBlockLambda = ( _node: SdsBlockLambda, _substitutions: ParameterSubstitutions, -): SdsIntermediateBlockLambda | undefined => { +): IntermediateBlockLambda | undefined => { // return when { // callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateBlockLambda( // parameters = parametersOrEmpty(), @@ -143,7 +143,7 @@ const simplifyBlockLambda = ( const simplifyExpressionLambda = ( _node: SdsExpressionLambda, _substitutions: ParameterSubstitutions, -): SdsIntermediateExpressionLambda | undefined => { +): IntermediateExpressionLambda | undefined => { // return when { // callableHasNoSideEffects(resultIfUnknown = true) -> SdsIntermediateExpressionLambda( // parameters = parametersOrEmpty(), @@ -158,7 +158,7 @@ const simplifyExpressionLambda = ( const simplifyInfixOperation = ( node: SdsInfixOperation, substitutions: ParameterSubstitutions, -): SdsConstantExpression | undefined => { +): ConstantExpression | undefined => { // By design none of the operators are short-circuited const constantLeft = toConstantExpressionOrUndefinedImpl(node.leftOperand, substitutions); if (!constantLeft) return; @@ -288,19 +288,19 @@ const simplifyInfixOperation = ( const simplifyPrefixOperation = ( node: SdsPrefixOperation, substitutions: ParameterSubstitutions, -): SdsConstantExpression | undefined => { +): ConstantExpression | undefined => { const constantOperand = toConstantExpressionOrUndefinedImpl(node.operand, substitutions); if (!constantOperand) return; if (node.operator === 'not') { - if (constantOperand instanceof SdsConstantBoolean) { - return new SdsConstantBoolean(!constantOperand.value); + if (constantOperand instanceof ConstantBoolean) { + return new ConstantBoolean(!constantOperand.value); } } else if (node.operator === '-') { - if (constantOperand instanceof SdsConstantFloat) { - return new SdsConstantFloat(-constantOperand.value); - } else if (constantOperand instanceof SdsConstantInt) { - return new SdsConstantInt(-constantOperand.value); + if (constantOperand instanceof ConstantFloat) { + return new ConstantFloat(-constantOperand.value); + } else if (constantOperand instanceof ConstantInt) { + return new ConstantInt(-constantOperand.value); } } @@ -310,16 +310,16 @@ const simplifyPrefixOperation = ( const simplifyTemplateString = ( node: SdsTemplateString, substitutions: ParameterSubstitutions, -): SdsConstantExpression | undefined => { +): ConstantExpression | undefined => { const constantExpressions = node.expressions.map((it) => toConstantExpressionOrUndefinedImpl(it, substitutions)); if (constantExpressions.some((it) => it === undefined)) { return undefined; } - return new SdsConstantString(constantExpressions.map((it) => it!.toInterpolationString()).join('')); + return new ConstantString(constantExpressions.map((it) => it!.toInterpolationString()).join('')); }; -const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): SdsSimplifiedExpression | undefined => { +const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): SimplifiedExpression | undefined => { // val simpleReceiver = simplifyReceiver(substitutions) ?: return undefined // val newSubstitutions = buildNewSubstitutions(simpleReceiver, substitutions) // @@ -387,7 +387,7 @@ const simplifyCall = (_node: SdsCall, _substitutions: ParameterSubstitutions): S const simplifyIndexedAccess = ( _node: SdsIndexedAccess, _substitutions: ParameterSubstitutions, -): SdsSimplifiedExpression | undefined => { +): SimplifiedExpression | undefined => { // val simpleReceiver = receiver.simplify(substitutions) as? SdsIntermediateVariadicArguments ?: return undefined // val simpleIndex = index.simplify(substitutions) as? SdsConstantInt ?: return undefined // @@ -399,7 +399,7 @@ const simplifyIndexedAccess = ( const simplifyMemberAccess = ( _node: SdsMemberAccess, _substitutions: ParameterSubstitutions, -): SdsSimplifiedExpression | undefined => { +): SimplifiedExpression | undefined => { // private fun SdsMemberAccess.simplifyMemberAccess(substitutions: ParameterSubstitutions): SdsSimplifiedExpression? { // if (member.declaration is SdsEnumVariant) { // return member.simplifyReference(substitutions) @@ -419,7 +419,7 @@ const simplifyMemberAccess = ( const simplifyReference = ( _node: SdsReference, _substitutions: ParameterSubstitutions, -): SdsSimplifiedExpression | undefined => { +): SimplifiedExpression | undefined => { // return when (val declaration = this.declaration) { // is SdsEnumVariant -> when { // declaration.parametersOrEmpty().isEmpty() -> SdsConstantEnumVariant(declaration) diff --git a/src/language/validation/other/expressions/infixOperations.ts b/src/language/validation/other/expressions/infixOperations.ts index 84447de54..06a6ebaad 100644 --- a/src/language/validation/other/expressions/infixOperations.ts +++ b/src/language/validation/other/expressions/infixOperations.ts @@ -2,16 +2,16 @@ import { SdsInfixOperation } from '../../../generated/ast.js'; import { ValidationAcceptor } from 'langium'; import { SafeDsServices } from '../../../safe-ds-module.js'; import { toConstantExpressionOrUndefined } from '../../../partialEvaluation/toConstantExpressionOrUndefined.js'; -import { SdsConstantFloat, SdsConstantInt } from '../../../partialEvaluation/model.js'; +import { ConstantFloat, ConstantInt } from '../../../partialEvaluation/model.js'; import { UnknownType } from '../../../typing/model.js'; export const CODE_INFIX_OPERATION_DIVISION_BY_ZERO = 'infix-operation/division-by-zero'; export const divisionDivisorMustNotBeZero = (services: SafeDsServices) => { const typeComputer = services.types.TypeComputer; - const zeroInt = new SdsConstantInt(BigInt(0)); - const zeroFloat = new SdsConstantFloat(0.0); - const minusZeroFloat = new SdsConstantFloat(-0.0); + const zeroInt = new ConstantInt(BigInt(0)); + const zeroFloat = new ConstantFloat(0.0); + const minusZeroFloat = new ConstantFloat(-0.0); return (node: SdsInfixOperation, accept: ValidationAcceptor): void => { if (node.operator !== '/') { diff --git a/src/language/validation/safe-ds-validator.ts b/src/language/validation/safe-ds-validator.ts index 67d173a40..ee0239bd5 100644 --- a/src/language/validation/safe-ds-validator.ts +++ b/src/language/validation/safe-ds-validator.ts @@ -26,6 +26,7 @@ import { callArgumentListShouldBeNeeded, classBodyShouldNotBeEmpty, constraintListShouldNotBeEmpty, + elvisOperatorShouldBeNeeded, enumBodyShouldNotBeEmpty, enumVariantParameterListShouldNotBeEmpty, functionResultListShouldNotBeEmpty, @@ -167,7 +168,7 @@ export const registerValidationChecks = function (services: SafeDsServices) { SdsImport: [importPackageMustExist(services), importPackageShouldNotBeEmpty(services)], SdsImportedDeclaration: [importedDeclarationAliasShouldDifferFromDeclarationName], SdsIndexedAccess: [indexedAccessesShouldBeUsedWithCaution], - SdsInfixOperation: [divisionDivisorMustNotBeZero(services)], + SdsInfixOperation: [divisionDivisorMustNotBeZero(services), elvisOperatorShouldBeNeeded(services)], SdsLambda: [lambdaParametersMustNotBeAnnotated, lambdaParameterMustNotHaveConstModifier], SdsMemberAccess: [ memberAccessMustBeNullSafeIfReceiverIsNullable(services), diff --git a/src/language/validation/style.ts b/src/language/validation/style.ts index 41e3a7c5a..01a4b718c 100644 --- a/src/language/validation/style.ts +++ b/src/language/validation/style.ts @@ -11,6 +11,7 @@ import { SdsEnumVariant, SdsFunction, SdsImportedDeclaration, + SdsInfixOperation, SdsMemberAccess, SdsNamedType, SdsSegment, @@ -22,6 +23,8 @@ import { isEmpty } from 'radash'; import { isRequiredParameter, parametersOrEmpty, typeParametersOrEmpty } from '../helpers/nodeProperties.js'; import { SafeDsServices } from '../safe-ds-module.js'; import { UnknownType } from '../typing/model.js'; +import { toConstantExpressionOrUndefined } from '../partialEvaluation/toConstantExpressionOrUndefined.js'; +import { ConstantNull } from '../partialEvaluation/model.js'; export const CODE_STYLE_UNNECESSARY_ASSIGNMENT = 'style/unnecessary-assignment'; export const CODE_STYLE_UNNECESSARY_ARGUMENT_LIST = 'style/unnecessary-argument-list'; @@ -152,6 +155,61 @@ export const constraintListShouldNotBeEmpty = (node: SdsConstraintList, accept: } }; +// ----------------------------------------------------------------------------- +// Unnecessary elvis operator +// ----------------------------------------------------------------------------- + +export const elvisOperatorShouldBeNeeded = (services: SafeDsServices) => { + const typeComputer = services.types.TypeComputer; + + return (node: SdsInfixOperation, accept: ValidationAcceptor): void => { + if (node.operator !== '?:') return; + + const leftType = typeComputer.computeType(node.leftOperand); + if (!leftType.isNullable) { + accept( + 'info', + 'The left operand is never null, so the elvis operator is unnecessary (keep the left operand).', + { + node, + code: CODE_STYLE_UNNECESSARY_ELVIS_OPERATOR, + }, + ); + } + + const leftValue = toConstantExpressionOrUndefined(node.leftOperand); + const rightValue = toConstantExpressionOrUndefined(node.rightOperand); + if (leftValue === ConstantNull && rightValue === ConstantNull) { + accept( + 'info', + 'Both operands are always null, so the elvis operator is unnecessary (replace it with null).', + { + node, + code: CODE_STYLE_UNNECESSARY_ELVIS_OPERATOR, + }, + ); + } else if (leftValue === ConstantNull) { + accept( + 'info', + 'The left operand is always null, so the elvis operator is unnecessary (keep the right operand).', + { + node, + code: CODE_STYLE_UNNECESSARY_ELVIS_OPERATOR, + }, + ); + } else if (rightValue === ConstantNull) { + accept( + 'info', + 'The right operand is always null, so the elvis operator is unnecessary (keep the left operand).', + { + node, + code: CODE_STYLE_UNNECESSARY_ELVIS_OPERATOR, + }, + ); + } + }; +}; + // ----------------------------------------------------------------------------- // Unnecessary import alias // ----------------------------------------------------------------------------- diff --git a/tests/resources/validation/style/unnecessary elvis operator/main.sdstest b/tests/resources/validation/style/unnecessary elvis operator/main.sdstest new file mode 100644 index 000000000..3ea67fc90 --- /dev/null +++ b/tests/resources/validation/style/unnecessary elvis operator/main.sdstest @@ -0,0 +1,42 @@ +package validation.style.unnecessaryElvisOperator + +fun f() -> result: Any? + +pipeline test { + + // $TEST$ info "The left operand is never null, so the elvis operator is unnecessary (keep the left operand)." + »1 ?: 2«; + // $TEST$ info "The left operand is never null, so the elvis operator is unnecessary (keep the left operand)." + »1 ?: null«; + // $TEST$ no info "The left operand is never null, so the elvis operator is unnecessary (keep the left operand)." + »null ?: 2«; + // $TEST$ no info "The left operand is never null, so the elvis operator is unnecessary (keep the left operand)." + »null ?: null«; + + // $TEST$ no info "The right operand is always null, so the elvis operator is unnecessary (keep the left operand)." + »1 ?: 2«; + // $TEST$ info "The right operand is always null, so the elvis operator is unnecessary (keep the left operand)." + »f() ?: null«; + // $TEST$ no info "The right operand is always null, so the elvis operator is unnecessary (keep the left operand)." + »null ?: 2«; + // $TEST$ no info "The right operand is always null, so the elvis operator is unnecessary (keep the left operand)." + »null ?: null«; + + // $TEST$ no info "The left operand is always null, so the elvis operator is unnecessary (keep the right operand)." + »1 ?: 2«; + // $TEST$ no info "The left operand is always null, so the elvis operator is unnecessary (keep the right operand)." + »1 ?: null«; + // $TEST$ info "The left operand is always null, so the elvis operator is unnecessary (keep the right operand)." + »null ?: 2«; + // $TEST$ no info "The left operand is always null, so the elvis operator is unnecessary (keep the right operand)." + »null ?: null«; + + // $TEST$ no info "Both operands are always null, so the elvis operator is unnecessary (replace it with null)." + »1 ?: 2«; + // $TEST$ no info "Both operands are always null, so the elvis operator is unnecessary (replace it with null)." + »1 ?: null«; + // $TEST$ no info "Both operands are always null, so the elvis operator is unnecessary (replace it with null)." + »null ?: 2«; + // $TEST$ info "Both operands are always null, so the elvis operator is unnecessary (replace it with null)." + »null ?: null«; +}