All Downloads are FREE. Search and download functionalities are using the official Maven repository.

groovy.transform.stc.ClosureSignatureHint Maven / Gradle / Ivy

The newest version!
/*
 *  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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy