Paul King created GROOVY-12729:
----------------------------------
Summary: groovy-contracts: @Decreases is silently disabled by {}
or by a qualified class literal, rejects { [] } on every loop, and rejects a
simple class name only because it is never resolved
Key: GROOVY-12729
URL: https://issues.apache.org/jira/browse/GROOVY-12729
Project: Groovy
Issue Type: Bug
Components: groovy-contracts
Reporter: Paul King
Probed on master (715553465f). Each loop runs 3 times. "Decreasing" is {{while
(n > 0) \{ n-- \}}}, and "not decreasing" is {{while (i < 3) \{ i++ \}}}.
||Annotation||Decreasing loop||Loop that does not decrease||
|{{@Decreases(\{ n \})}}, {{@Decreases(\{ [n] \})}}
(controls)|OK|{{LoopVariantViolation: loop variant did not decrease: was 3, now
3}}|
|{{@Decreases(\{\})}}|OK|*OK*: no check is made, and nothing reports that|
|{{@Decreases(\{ [] \})}}|*{{LoopVariantViolation: loop variant did not
decrease: was [], now []}}*|same|
|{{@Decreases(java.lang.Object)}}|OK|*OK*: no check is made|
|{{@Decreases(Object)}}|compile error: {{Only classes, closures, and method
references can be used for attribute 'value' in @groovy.contracts.Decreases}}| |
|{{@Decreases([1])}}|same compile error| |
|{{@Decreases(Object.class)}}|compile error: {{unable to find class
'Object.class' for annotation attribute constant}}| |
The results are the same in a script and in a class method.
h3. 1. Values that disable the check
* {{LoopVariantASTTransformation}} returns without doing anything when the
value is not a closure. It does the same when the closure has no expression. So
{{@Decreases(\{\})}} and a qualified class literal compile to an unchecked loop.
* Unlike {{@Modifies(\{\})}}, which declares an empty frame, an empty
{{@Decreases}} has no meaning.
h3. 2. {{\{ [] \}}} can never pass
An empty tuple never decreases, so any loop that runs more than once fails.
That is correct as far as it goes, but the result is almost certainly not what
was meant.
h3. 3. Simple class names in loop annotations are never resolved
{{@Decreases(Object)}} is rejected only by accident. An AST probe at
CONVERSION, SEMANTIC_ANALYSIS and CANONICALIZATION shows the member value
staying a {{VariableExpression(Object)}} throughout, so the generic annotation
check rejects it. A qualified {{java.lang.Object}} becomes a
{{ClassExpression}} during SEMANTIC_ANALYSIS. On a method annotation,
{{@Requires(Object)}} becomes a {{ClassExpression}} as expected.
{{Object.class}} gives the misleading "unable to find class 'Object.class'".
This affects any annotation placed on a loop, not just {{@Decreases}}.
h3. Suggested fix
* {{@Decreases}}: report an error for a value that is not a closure, for an
empty closure, and for an empty list, in the style of the {{@Modifies}} errors
(GROOVY-12723).
* Resolve class names in loop-statement annotations the same way as in
declaration annotations. This could be a separate issue.
Related: GROOVY-12128 (where loop contract checks fire), GROOVY-12723
(@Modifies), and the issue raised alongside this one (class literals on
@Requires, @Ensures and @Invariant).
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)