groovy.transform.stc.ClosureSignatureHint Maven / Gradle / Ivy
/*
* 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.transform.stc;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.SourceUnit;
import java.util.List;
/**
* A closure signature hint class is always used in conjunction with the {@link ClosureParams} annotation. It is
* called at compile time (or may be used by IDEs) to infer the types of the parameters of a {@link groovy.lang.Closure}.
* A closure hint class is responsible for generating the list of arguments that a closure accepts. Since closures
* may accept several signatures, {@link #getClosureSignatures(org.codehaus.groovy.ast.MethodNode, org.codehaus.groovy.control.SourceUnit, org.codehaus.groovy.control.CompilationUnit, String[], org.codehaus.groovy.ast.ASTNode)} should
* return a list.
*
* Whenever the type checker encounters a method call that targets a method accepting a closure, it will search
* for the {@link ClosureParams} annotation on the {@link groovy.lang.Closure} argument. If it is found, then it
* creates an instance of the hint class and calls the {@link #getClosureSignatures(org.codehaus.groovy.ast.MethodNode, org.codehaus.groovy.control.SourceUnit, org.codehaus.groovy.control.CompilationUnit, String[], org.codehaus.groovy.ast.ASTNode)}
* method, which will in turn return the list of signatures.
*
* Note that the signature concept here is used only to describe the parameter types, not the result type, which
* is found in the generic type argument of the {@link groovy.lang.Closure} class.
*
* Several predefined hints can be found, which should cover most of the use cases.
*
* @author Cédric Champeau
* @since 2.3.0
*
*/
public abstract class ClosureSignatureHint {
/**
* A helper method which will extract the n-th generic type from a class node.
* @param type the class node from which to pick a generic type
* @param gtIndex the index of the generic type to extract
* @return the n-th generic type, or {@link org.codehaus.groovy.ast.ClassHelper#OBJECT_TYPE} if it doesn't exist.
*/
public static ClassNode pickGenericType(ClassNode type, int gtIndex) {
final GenericsType[] genericsTypes = type.getGenericsTypes();
if (genericsTypes==null || genericsTypes.lengthSubclasses should implement this method, which returns the list of accepted closure signatures.
*
* The compiler will call this method each time, in a source file, a method call using a closure
* literal is encountered and that the target method has the corresponding {@link groovy.lang.Closure} parameter
* annotated with {@link groovy.transform.stc.ClosureParams}. So imagine the following code needs to be compiled:
*
* @groovy.transform.TypeChecked
* void doSomething() {
* println ['a','b'].collect { it.toUpperCase() }
* }
*
* The collect method accepts a closure, but normally, the type checker doesn't have enough type information
* in the sole {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#collect(java.util.Collection, groovy.lang.Closure)} method
* signature to infer the type of it. With the annotation, it will now try to find an annotation on the closure parameter.
* If it finds it, then an instance of the hint class is created and the type checker calls it with the following arguments:
*
* - the method node corresponding to the target method (here, the {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#collect(java.util.Collection, groovy.lang.Closure)} method
* - the (optional) list of options found in the annotation
*
*
* Now, the hint instance can return the list of expected parameters. Here, it would have to say that the collect method accepts
* a closure for which the only argument is of the type of the first generic type of the first argument.
* With that type information, the type checker can now infer that the type of it is String, because the first argument (here the receiver of the collect method)
* is a List<String>
*
*
*
* Subclasses are therefore expected to return the signatures according to the available context, which is only the target method and the potential options.
*
*
* @param node the method node for which a {@link groovy.lang.Closure} parameter was annotated with
* {@link ClosureParams}
* @param sourceUnit the source unit of the file being compiled
* @param compilationUnit the compilation unit of the file being compiled
* @param options the options, corresponding to the {@link ClosureParams#options()} found on the annotation @return a non-null list of signature, where a signature corresponds to an array of class nodes, each of them matching a parameter.
* @param usage the AST node, in the compiled file, which triggered a call to this method. Normally only used for logging/error handling
*/
public abstract List getClosureSignatures(MethodNode node, SourceUnit sourceUnit, CompilationUnit compilationUnit, String[] options, ASTNode usage);
/**
* Finds a class node given a string representing the type. Performs a lookup in the compilation unit to check if it is done in the same source unit.
* @param sourceUnit source unit
* @param compilationUnit compilation unit
* @param className the name of the class we want to get a {@link org.codehaus.groovy.ast.ClassNode} for
* @return a ClassNode representing the type
*/
protected ClassNode findClassNode(final SourceUnit sourceUnit, final CompilationUnit compilationUnit, final String className) {
if (className.endsWith("[]")) {
return findClassNode(sourceUnit, compilationUnit, className.substring(0, className.length() - 2)).makeArray();
}
ClassNode cn = compilationUnit.getClassNode(className);
if (cn == null) {
try {
cn = ClassHelper.make(Class.forName(className, false, sourceUnit.getClassLoader()));
} catch (ClassNotFoundException e) {
cn = ClassHelper.make(className);
}
}
return cn;
}
}