| /* |
| * 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 org.codehaus.groovy.transform.stc; |
| |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.ast.ASTNode; |
| import org.codehaus.groovy.ast.ClassCodeVisitorSupport; |
| 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.expr.ArgumentListExpression; |
| import org.codehaus.groovy.ast.expr.AttributeExpression; |
| import org.codehaus.groovy.ast.expr.ClassExpression; |
| import org.codehaus.groovy.ast.expr.Expression; |
| import org.codehaus.groovy.ast.expr.MethodCall; |
| import org.codehaus.groovy.ast.expr.MethodCallExpression; |
| import org.codehaus.groovy.ast.expr.PropertyExpression; |
| import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; |
| import org.codehaus.groovy.ast.expr.VariableExpression; |
| import org.codehaus.groovy.ast.stmt.ReturnStatement; |
| |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * This interface defines a high-level API for handling type checking errors. As a dynamic language and a platform |
| * for developing DSLs, the Groovy language provides a lot of means to supply custom bindings or methods that are |
| * not possible to find at compile time. However, it is still possible to help the compiler, for example by |
| * telling it what is the type of an unresolved property. |
| * |
| * For basic DSL type checking, implementing those methods would help the type checker and make it silent where it |
| * normally throws errors. |
| * |
| * @since 2.1.0 |
| */ |
| public class TypeCheckingExtension { |
| |
| protected final StaticTypeCheckingVisitor typeCheckingVisitor; |
| |
| public TypeCheckingExtension(final StaticTypeCheckingVisitor typeCheckingVisitor) { |
| this.typeCheckingVisitor = typeCheckingVisitor; |
| } |
| |
| /** |
| * Subclasses should implement this method whenever they need to perform |
| * special checks before the type checker starts working. |
| */ |
| public void setup() {} |
| |
| /** |
| * Subclasses should implement this method if they need to perform additional |
| * checks after the type checker has finished its work. This is particularly |
| * useful for situations where you need multiple passes. Some checks in that |
| * case may be deferred to the end, using this method. |
| */ |
| public void finish() {} |
| |
| /** |
| * This method is called by the type checker when a variable expression cannot |
| * be resolved. It gives the extension a chance to resolve it for the type |
| * checker. |
| * |
| * @param vexp the unresolved variable extension |
| * @return <code>boolean</code> false if the extension doesn't handle it, |
| * true if the extension handles this variable. |
| */ |
| public boolean handleUnresolvedVariableExpression(VariableExpression vexp) { |
| return false; |
| } |
| |
| /** |
| * This method is called by the type checker when a property expression cannot |
| * be resolved (for example, when a property doesn't exist). It gives the extension |
| * a chance to resolve it. |
| * |
| * @param pexp the unresolved property |
| * @return <code>boolean</code> false if this extension doesn't resolve the property, true |
| * if it resolves the property. |
| */ |
| public boolean handleUnresolvedProperty(PropertyExpression pexp) { |
| return false; |
| } |
| |
| /** |
| * This method is called by the type checker when an attribute expression cannot |
| * be resolved (for example, when an attribute doesn't exist). It gives the extension |
| * a chance to resolve it. |
| * |
| * @param aexp the unresolved attribute |
| * @return <code>boolean</code> false if this extension doesn't resolve the attribute, true |
| * if it resolves the attribute. |
| */ |
| public boolean handleUnresolvedAttribute(AttributeExpression aexp) { |
| return false; |
| } |
| |
| /** |
| * This method is called by the type checker when a method call cannot be resolved. Extensions |
| * may override this method to handle missing methods and prevent the type checker from throwing an |
| * error. |
| * |
| * |
| * @param receiver the type of the receiver |
| * @param name the name of the called method |
| * @param argumentList the list of arguments of the call |
| * @param argumentTypes the types of the arguments of the call |
| * @param call the method call itself, if needed |
| * @return an empty list if the extension cannot resolve the method, or a list of potential |
| * methods if the extension finds candidates. This method must not return null. |
| */ |
| public List<MethodNode> handleMissingMethod(ClassNode receiver, String name, ArgumentListExpression argumentList, ClassNode[] argumentTypes, MethodCall call) { |
| return Collections.emptyList(); |
| } |
| |
| /** |
| * This method is called by the type checker when an assignment is not allowed by the type checker. |
| * Extensions may override this method to allow such assignments where the type checker normally disallows |
| * them. |
| * |
| * @param lhsType the type of the left hand side of the assignment, as found by the type checker |
| * @param rhsType the type of the right hand side of the assignment, as found by the type checker |
| * @param assignmentExpression the assignment expression which triggered this call |
| * @return <code>boolean</code> false if the extension does not handle this assignment, true otherwise |
| */ |
| public boolean handleIncompatibleAssignment(final ClassNode lhsType, final ClassNode rhsType, final Expression assignmentExpression) { |
| return false; |
| } |
| |
| /** |
| * This method is called by the type checker before throwing an "ambiguous method" error, giving the chance |
| * to the extension to select the method properly. This means that when this method is called, the "nodes" |
| * parameter contains at least two methods. If the returned list still contains at least two methods, then the |
| * type checker will throw an ambiguous method call error. If the returned method contains 1 element, then |
| * the type checker will not throw any error. |
| * |
| * It is invalid to return an empty list. |
| * |
| * @param nodes the list of ambiguous methods |
| * @param origin the expression which originated the method selection process |
| * @return a single element list of disambiguated selection, or more elements if still ambiguous. It is not allowed |
| * to return an empty list. |
| */ |
| public List<MethodNode> handleAmbiguousMethods(final List<MethodNode> nodes, final Expression origin) { |
| return nodes; |
| } |
| |
| /** |
| * Allows the extension to perform additional tasks before the type checker actually visits a method node. |
| * Compared to a custom visitor, this method ensures that the node being visited is a node which would have |
| * been visited by the type checker. This is in particular important for nodes which are marked with |
| * {@link groovy.transform.TypeCheckingMode#SKIP}. |
| * @param node a method node |
| * @return false if the type checker should visit the node, or true if this extension replaces what the |
| * type checker would do with the method. |
| */ |
| public boolean beforeVisitMethod(MethodNode node) { |
| return false; |
| } |
| |
| /** |
| * Allows the extension to perform additional tasks after the type checker actually visited a method node. |
| * Compared to a custom visitor, this method ensures that the node being visited is a node which would have |
| * been visited by the type checker. This is in particular important for nodes which are marked with |
| * {@link groovy.transform.TypeCheckingMode#SKIP}. |
| * @param node a method node |
| */ |
| public void afterVisitMethod(MethodNode node) { |
| } |
| |
| /** |
| * Allows the extension to perform additional tasks before the type checker actually visits a class node. |
| * Compared to a custom visitor, this method ensures that the node being visited is a node which would have |
| * been visited by the type checker. This is in particular important for nodes which are marked with |
| * {@link groovy.transform.TypeCheckingMode#SKIP}. |
| * @param node a class node |
| * @return false if the type checker should visit the node, or true if this extension replaces what the |
| * type checker would do with the class. |
| */ |
| public boolean beforeVisitClass(ClassNode node) { |
| return false; |
| } |
| |
| /** |
| * Allows the extension to perform additional tasks after the type checker actually visited a class node. |
| * Compared to a custom visitor, this method ensures that the node being visited is a node which would have |
| * been visited by the type checker. This is in particular important for nodes which are marked with |
| * {@link groovy.transform.TypeCheckingMode#SKIP}. |
| * @param node a class node |
| */ |
| public void afterVisitClass(ClassNode node) { |
| } |
| |
| /** |
| * Allows the extension to perform additional tasks before the type checker actually visits a method call. |
| * Compared to a custom visitor, this method ensures that the node being visited is a node which would have |
| * been visited by the type checker. This is in particular important for nodes which are marked with |
| * {@link groovy.transform.TypeCheckingMode#SKIP}. |
| * |
| * @param call a method call, either a {@link org.codehaus.groovy.ast.expr.MethodCallExpression}, {@link org.codehaus.groovy.ast.expr.StaticMethodCallExpression}, or {@link org.codehaus.groovy.ast.expr.ConstructorCallExpression} |
| * @return false if the type checker should visit the node, or true if this extension replaces what the |
| * type checker would do with the method call. |
| */ |
| public boolean beforeMethodCall(MethodCall call) { |
| return false; |
| } |
| |
| /** |
| * Allows the extension to perform additional tasks after the type checker actually visits a method call. |
| * Compared to a custom visitor, this method ensures that the node being visited is a node which would have |
| * been visited by the type checker. This is in particular important for nodes which are marked with |
| * {@link groovy.transform.TypeCheckingMode#SKIP}. |
| * @param call a method call, either a {@link org.codehaus.groovy.ast.expr.MethodCallExpression}, {@link org.codehaus.groovy.ast.expr.StaticMethodCallExpression}, or {@link org.codehaus.groovy.ast.expr.ConstructorCallExpression} |
| */ |
| public void afterMethodCall(MethodCall call) { |
| } |
| |
| /** |
| * Allows the extension to listen to method selection events. Given an expression, which may be a method |
| * call expression, a static method call expression, a pre/postfix expression, ..., if a corresponding |
| * method is found, this method is called. |
| * @param expression the expression for which a corresponding method has been found |
| * @param target the method which has been chosen by the type checker |
| */ |
| public void onMethodSelection(Expression expression, MethodNode target) { |
| } |
| |
| /** |
| * Allows the extension to catch incompatible return types. This event is called whenever the type |
| * checker finds that an inferred return type is incompatible with the declared return type of |
| * a method. |
| * |
| * @param returnStatement the statement that triggered the error |
| * @param inferredReturnType the inferred return type for this statement |
| * @return false if the extension doesn't handle the error, true otherwise |
| */ |
| public boolean handleIncompatibleReturnType(ReturnStatement returnStatement, ClassNode inferredReturnType) { |
| return false; |
| } |
| |
| // ------------------------------------------------------------------------------------------ |
| // Below, you will find various helper methods aimed at simplifying algorithms for subclasses |
| // ------------------------------------------------------------------------------------------ |
| |
| /** |
| * Returns the inferred type of an expression. Delegates to the type checker implementation. |
| * @param exp the expression for which we want to find the inferred type |
| * @return the inferred type of the expression, as found by the type checker |
| */ |
| public ClassNode getType(final ASTNode exp) { |
| return typeCheckingVisitor.getType(exp); |
| } |
| |
| /** |
| * Adds a type checking error, which will be displayed to the user during compilation. |
| * @param msg the message for the error |
| * @param expr the expression which is the root cause of the error |
| */ |
| public void addStaticTypeError(final String msg, final ASTNode expr) { |
| typeCheckingVisitor.addStaticTypeError(msg, expr); |
| } |
| |
| /** |
| * Stores an inferred type for an expression. Delegates to the type checker. |
| * @param exp the expression for which we want to store an inferred type |
| * @param cn the type of the expression |
| */ |
| public void storeType(final Expression exp, final ClassNode cn) { |
| typeCheckingVisitor.storeType(exp, cn); |
| } |
| |
| public boolean existsProperty(final PropertyExpression pexp, final boolean checkForReadOnly) { |
| return typeCheckingVisitor.existsProperty(pexp, checkForReadOnly); |
| } |
| |
| public boolean existsProperty(final PropertyExpression pexp, final boolean checkForReadOnly, final ClassCodeVisitorSupport visitor) { |
| return typeCheckingVisitor.existsProperty(pexp, checkForReadOnly, visitor); |
| } |
| |
| public ClassNode[] getArgumentTypes(final ArgumentListExpression args) { |
| return typeCheckingVisitor.getArgumentTypes(args); |
| } |
| |
| public MethodNode getTargetMethod(final Expression expression) { |
| return (MethodNode) expression.getNodeMetaData(StaticTypesMarker.DIRECT_METHOD_CALL_TARGET); |
| } |
| |
| public ClassNode classNodeFor(Class type) { |
| return ClassHelper.make(type); |
| } |
| |
| public ClassNode classNodeFor(String type) { |
| return ClassHelper.make(type); |
| } |
| |
| /** |
| * Lookup a ClassNode by its name from the source unit |
| * |
| * @param type the name of the class whose ClassNode we want to lookup |
| * @return a ClassNode representing the class |
| */ |
| public ClassNode lookupClassNodeFor(String type) { |
| for (ClassNode cn : typeCheckingVisitor.getSourceUnit().getAST().getClasses()) { |
| if (cn.getName().equals(type)) |
| return cn; |
| } |
| |
| return null; |
| } |
| |
| public ClassNode parameterizedType(final ClassNode baseType, final ClassNode... genericsTypeArguments) { |
| if (baseType.isArray()) { |
| return parameterizedType(baseType.getComponentType(), genericsTypeArguments).makeArray(); |
| } |
| ClassNode result = baseType.getPlainNodeReference(); |
| GenericsType[] generics = baseType.redirect().getGenericsTypes(); |
| if (generics != null) { |
| int expectedLength = generics.length; |
| if (expectedLength != genericsTypeArguments.length) { |
| throw new GroovyBugError("Expected number of generic type arguments for " + StaticTypeCheckingSupport.prettyPrintType(baseType) + " is " + expectedLength + " but you gave " + genericsTypeArguments.length); |
| } |
| result.setGenericsTypes(Arrays.stream(genericsTypeArguments) |
| .map(StaticTypeCheckingVisitor::wrapTypeIfNecessary) |
| .map(GenericsType::new).toArray(GenericsType[]::new) |
| ); |
| } |
| return result; |
| } |
| |
| /** |
| * Builds a parametrized class node for List, to represent List<X> |
| * @param componentType the classnode for the component type of the list |
| * @return a classnode representing List<componentType> |
| * @since 2.2.0 |
| */ |
| public ClassNode buildListType(ClassNode componentType) { |
| return parameterizedType(ClassHelper.LIST_TYPE, componentType); |
| } |
| |
| /** |
| * Builds a parametrized class node representing the Map<keyType,valueType> type. |
| * @param keyType the classnode type of the key |
| * @param valueType the classnode type of the value |
| * @return a class node for Map<keyType,valueType> |
| * @since 2.2.0 |
| */ |
| public ClassNode buildMapType(ClassNode keyType, ClassNode valueType) { |
| return parameterizedType(ClassHelper.MAP_TYPE, keyType, valueType); |
| } |
| |
| /** |
| * Given a method call, first checks that it's a static method call, and if it is, returns the |
| * class node for the receiver. For example, with the following code: |
| * <code></code>Person.findAll { ... }</code>, it would return the class node for <i>Person</i>. |
| * If it's not a static method call, returns null. |
| * @param call a method call |
| * @return null if it's not a static method call, or the class node for the receiver instead. |
| */ |
| public ClassNode extractStaticReceiver(MethodCall call) { |
| if (call instanceof StaticMethodCallExpression) { |
| return ((StaticMethodCallExpression) call).getOwnerType(); |
| } else if (call instanceof MethodCallExpression) { |
| Expression objectExpr = ((MethodCallExpression) call).getObjectExpression(); |
| if (objectExpr instanceof ClassExpression && ClassHelper.CLASS_Type.equals(objectExpr.getType())) { |
| GenericsType[] genericsTypes = objectExpr.getType().getGenericsTypes(); |
| if (genericsTypes!=null && genericsTypes.length==1) { |
| return genericsTypes[0].getType(); |
| } |
| } |
| if (objectExpr instanceof ClassExpression) { |
| return objectExpr.getType(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Given a method call, checks if it's a static method call and if it is, tells if the receiver matches |
| * the one supplied as an argument. |
| * @param call a method call |
| * @param receiver a class node |
| * @return true if the method call is a static method call on the receiver |
| */ |
| public boolean isStaticMethodCallOnClass(MethodCall call, ClassNode receiver) { |
| ClassNode staticReceiver = extractStaticReceiver(call); |
| return staticReceiver!=null && staticReceiver.equals(receiver); |
| } |
| |
| } |