Skip to content

Validation warning: Statement does nothing #664

@lars-reimann

Description

@lars-reimann

Show a warning if we detect that a statement has no effect. For assignment statements, the assignee list must contain only wildcards, and the RHS must have no effect:

package tests.validation.other.statements.assignments.hasNoEffect

step myFunction() -> a: Int {
    // $TEST$ warning "This statement does nothing."
    »_ = 1 + 2;«

    // $TEST$ no warning "This statement does nothing."
    »val a = 1;«
    // $TEST$ no warning "This statement does nothing."
    »yield a = 1;«

    () {
        // $TEST$ warning "This statement does nothing."
        »_ = 1 + 2;«

        // $TEST$ no warning "This statement does nothing."
        »val a = 1;«
        // $TEST$ no warning "This statement does nothing."
        »yield a = 1;«
    };
}

For expression statements, the expression must have no effect:

package tests.validation.other.statements.expressionStatements.hasNoEffect

fun impureFunction()
@Pure fun pureFunction() -> a: Int

class MyClass() {
    fun impureFunction()
    @Pure fun pureFunction()
}

segment pureStep() {
    val a = pureFunction();
}

segment impureStep() {
    impureFunction();
}

segment recursiveA() {
    recursiveB();
}

segment recursiveB() {
    recursiveA();
}

segment myStep() {
    // $TEST$ warning "This statement does nothing."
    »1 + 2;«
    // $TEST$ warning "This statement does nothing."
    »pureFunction();«
    // $TEST$ warning "This statement does nothing."
    »MyClass().pureFunction();«

    // $TEST$ no warning "This statement does nothing."
    »impureFunction();«
    // $TEST$ no warning "This statement does nothing."
    »MyClass().impureFunction();«

    () {
        // $TEST$ warning "This statement does nothing."
        »1 + 2;«
        // $TEST$ warning "This statement does nothing."
        »pureFunction();«
        // $TEST$ warning "This statement does nothing."
        »MyClass().pureFunction();«

        // $TEST$ no warning "This statement does nothing."
        »impureFunction();«
        // $TEST$ no warning "This statement does nothing."
        »MyClass().impureFunction();«
    };

    // $TEST$ warning "This statement does nothing."
    »(() {
        pureFunction();
        MyClass().pureFunction();
    })();«

    // $TEST$ warning "This statement does nothing."
    »pureStep();«

    // $TEST$ no warning "This statement does nothing."
    »(() {
        impureFunction();
    })();«

    // $TEST$ no warning "This statement does nothing."
    »(() {
        MyClass().impureFunction();
    })();«

    // $TEST$ no warning "This statement does nothing."
    »impureStep();«

    // $TEST$ no warning "This statement does nothing."
    »recursiveA();«
}

For side-effect inference:

package tests.staticAnalysis.sideEffects

// Positive examples -----------------------------------------------------------

annotation ShouldHaveNoSideEffects

// Call to class constructor

class C()

@ShouldHaveNoSideEffects
step callOfClassConstructor() {
    C();
}

// Call to enum variant constructor

enum MyEnum {
    Variant
}

@ShouldHaveNoSideEffects
step callOfEnumVariantConstructor() {
    MyEnum.Variant();
}

// Function without side effects

@Pure
fun pureFunction()

@NoSideEffects
fun functionWithoutSideEffects()

@ShouldHaveNoSideEffects
step callToPureFunction() {
    pureFunction();
    functionWithoutSideEffects();
}

// Lambdas without side effects

@ShouldHaveNoSideEffects
step callToPureLambdas() {
    (() {})();
    (() -> null)();

    () {
        (() {})();
    };

    () -> (() -> null)();
}

// Steps without side effects

step pureStep() {}

@ShouldHaveNoSideEffects
step callToPureSteps() {
    pureStep();
}

// Uncalled lambdas

step pureStepWithUncalledLambdas() {
    () -> impureFunction();
}

@ShouldHaveNoSideEffects
step uncalledLambdas() {
    pureStepWithUncalledLambdas();
}

// Function as result

@ShouldHaveNoSideEffects
step pureFunctionAsResult() {
    (() -> pureFunction)()();
}

// Negative examples -----------------------------------------------------------

annotation ShouldHaveSideEffects

// Callable type

@ShouldHaveSideEffects
step callToCallableType(f: () -> ()) {
    f();
}

// Function with side effects

fun impureFunction()

@ShouldHaveSideEffects
step callToImpureFunction() {
    impureFunction();
}

// Lambdas with side effects

@ShouldHaveSideEffects
step callToImpureLambdas() {
    (() { impureFunction(); })();
    (() -> impureFunction())();

    () {
        (() { impureFunction(); })();
    };

    () -> (() -> impureFunction())();
}

// Steps with side effects

step impureStep() {
    impureFunction();
}

@ShouldHaveSideEffects
step callToImpureSteps() {
    impureStep();
}

// Recursion

@ShouldHaveSideEffects
step recursion() {
    recursion();
}

// Unresolved callable

@ShouldHaveSideEffects
step unresolvedCallable() {
    unresolved();
}

// Function as parameter

@ShouldHaveSideEffects
step impureFunctionAsParameter() {
    ((f) -> f())(pureFunction); // This is actually pure, but we match in a conservative manner. Can be improved later.
    ((f) -> f())(impureFunction);
}

Metadata

Metadata

Assignees

Labels

releasedIncluded in a releasevalidation ✔️Improved or new static checks

Type

No type

Projects

Status

✔️ Done

Relationships

None yet

Development

No branches or pull requests

Issue actions