Skip to content

Commit

Permalink
GROOVY-11443: Support multiple Requires/Ensures/Invariant annotations…
Browse files Browse the repository at this point in the history
… in groovy-contracts
  • Loading branch information
paulk-asert committed Jul 18, 2024
1 parent 265b49f commit db35968
Show file tree
Hide file tree
Showing 33 changed files with 375 additions and 169 deletions.
4 changes: 4 additions & 0 deletions src/main/java/org/codehaus/groovy/ast/tools/GeneralUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,10 @@ public static MapEntryExpression mapEntryX(final String key, final Expression va
return new MapEntryExpression(constX(key), valueExpr);
}

public static MapExpression mapX() {
return new MapExpression();
}

public static MapExpression mapX(final List<MapEntryExpression> expressions) {
return new MapExpression(expressions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.groovy.lang.annotation.Incubating;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand Down Expand Up @@ -80,7 +81,8 @@
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
@Incubating
@Postcondition
@Repeatable(EnsuresConditions.class)
@AnnotationProcessorImplementation(EnsuresAnnotationProcessor.class)
public @interface Ensures {
Class value();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package groovy.contracts;

import org.apache.groovy.lang.annotation.Incubating;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Represents multiple postconditions.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
@Incubating
public @interface EnsuresConditions {
Ensures[] value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.groovy.lang.annotation.Incubating;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand Down Expand Up @@ -53,7 +54,8 @@
@Target(ElementType.TYPE)
@Incubating
@ClassInvariant
@Repeatable(Invariants.class)
@AnnotationProcessorImplementation(ClassInvariantAnnotationProcessor.class)
public @interface Invariant {
Class value();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package groovy.contracts;

import org.apache.groovy.lang.annotation.Incubating;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Represents multiple invariants
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Incubating
public @interface Invariants {
Invariant[] value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.groovy.lang.annotation.Incubating;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand Down Expand Up @@ -58,6 +59,7 @@
@Incubating
@Precondition
@AnnotationProcessorImplementation(RequiresAnnotationProcessor.class)
@Repeatable(RequiresConditions.class)
public @interface Requires {
Class value();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package groovy.contracts;

import org.apache.groovy.lang.annotation.Incubating;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Represents multiple preconditions.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
@Incubating
public @interface RequiresConditions {
Requires[] value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,45 +37,46 @@
import java.util.List;

/**
* Evaluates {@link org.codehaus.groovy.ast.expr.ClosureExpression} instances in as actual annotation parameters and
* Evaluates {@link org.codehaus.groovy.ast.expr.ClosureExpression} instances in annotation parameters and
* generates special contract closure classes from them.
*/
@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)
public class ClosureExpressionEvaluationASTTransformation extends BaseASTTransformation {

/**
* {@link org.codehaus.groovy.transform.ASTTransformation#visit(org.codehaus.groovy.ast.ASTNode[], org.codehaus.groovy.control.SourceUnit)}
*/
@Override
public void visit(ASTNode[] nodes, SourceUnit unit) {
final ModuleNode moduleNode = unit.getAST();

ReaderSource source = getReaderSource(unit);
final List<ClassNode> classNodes = new ArrayList<>(moduleNode.getClasses());

generateAnnotationClosureClasses(unit, source, classNodes);
}

private void generateAnnotationClosureClasses(SourceUnit unit, ReaderSource source, List<ClassNode> classNodes) {
final AnnotationClosureVisitor annotationClosureVisitor = new AnnotationClosureVisitor(unit, source);

for (final ClassNode classNode : classNodes) {
annotationClosureVisitor.visitClass(classNode);

if (!CandidateChecks.isContractsCandidate(classNode)) continue;
if (!CandidateChecks.isContractsCandidate(classNode) && !CandidateChecks.isInterfaceContractsCandidate(classNode))
continue;

final ContractElementVisitor contractElementVisitor = new ContractElementVisitor(unit, source);
contractElementVisitor.visitClass(classNode);

if (!contractElementVisitor.isFoundContractElement()) continue;

annotationClosureVisitor.visitClass(classNode);
markClassNodeAsContracted(classNode);
if (classNode.isInterface()) continue;

new ConfigurationSetup().init(classNode);
}
}

/**
* {@link org.codehaus.groovy.transform.ASTTransformation#visit(org.codehaus.groovy.ast.ASTNode[], org.codehaus.groovy.control.SourceUnit)}
*/
@Override
public void visit(ASTNode[] nodes, SourceUnit unit) {
final ModuleNode moduleNode = unit.getAST();

ReaderSource source = getReaderSource(unit);
final List<ClassNode> classNodes = new ArrayList<ClassNode>(moduleNode.getClasses());

generateAnnotationClosureClasses(unit, source, classNodes);
}

private void markClassNodeAsContracted(final ClassNode classNode) {
final ClassNode contractedAnnotationClassNode = ClassHelper.makeWithoutCaching(Contracted.class);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ public AnnotationClosureVisitor(final SourceUnit sourceUnit, final ReaderSource

@Override
public void visitClass(ClassNode classNode) {
if (classNode == null || !(CandidateChecks.isInterfaceContractsCandidate(classNode) || CandidateChecks.isContractsCandidate(classNode))) return;
if (classNode == null || !(CandidateChecks.isContractsCandidate(classNode) || CandidateChecks.isInterfaceContractsCandidate(classNode))) return;

this.classNode = classNode;

if (classNode.getNodeMetaData(PROCESSED) == null && CandidateChecks.isContractsCandidate(classNode)) {
if (classNode.getNodeMetaData(PROCESSED) == null) {
List<AnnotationNode> annotationNodes = AnnotationUtils.hasMetaAnnotations(classNode, ContractElement.class.getName());
for (AnnotationNode annotationNode : annotationNodes) {
ClosureExpression closureExpression = getOriginalCondition(annotationNode);
Expand Down
Loading

0 comments on commit db35968

Please sign in to comment.