/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.netbeans.modules.ruby; import java.util.List; import org.jrubyparser.ast.ArgsCatNode; import org.jrubyparser.ast.ArgsNode; import org.jrubyparser.ast.ArgumentNode; import org.jrubyparser.ast.ArrayNode; import org.jrubyparser.ast.CallNode; import org.jrubyparser.ast.DefnNode; import org.jrubyparser.ast.DefsNode; import org.jrubyparser.ast.FCallNode; import org.jrubyparser.ast.ListNode; import org.jrubyparser.ast.LocalAsgnNode; import org.jrubyparser.ast.Node; import org.jrubyparser.ast.SplatNode; import org.jrubyparser.ast.VCallNode; /** * The arity of a method is the number of arguments it takes. * * JRuby already has an Arity class (org.jrubyparser.runtime.Arity), but * it doesn't have all we need - such as a maximum number of arguments. * * @todo I handle optional arguments and splats (*), but what about blocks? * def foo(arg1, arg2 = deflt, *rest, &block) * * @author Tor Norbye */ public final class Arity { /** * Represents unknown arity, for example the arity of a method that * we're referring to as a symbol, such as "private :foo". */ public static final Arity UNKNOWN = new Arity(-1, -1); /** * Minimum number of arguments required by this method */ private int min; /** * Maximum number of arguments required by this method. Use {@link Integer#MAX_VALUE} to denote no * upper bound (e.g. *args). */ private int max; private Arity(int min, int max) { // Examples: // call: foo(bar,baz) -> Arity(2,2) // call: foo(bar,*baz) -> Arity(1,Integer.MAX_VALUE) // method: foo(bar,baz) -> Arity(2,2) // method: foo(bar,baz=5) -> Arity(1,2) this.min = min; this.max = max; } public int getMinArgs() { return min; } public int getMaxArgs() { return max; } public static Arity getCallArity(Node call) { assert call instanceof CallNode || call instanceof VCallNode || call instanceof FCallNode; Arity arity = new Arity(0, 0); arity.initializeFromCall(call); if (arity.min == -1) { return UNKNOWN; } else { return arity; } } public static boolean callHasArguments(Node call) { assert call instanceof CallNode || call instanceof VCallNode || call instanceof FCallNode; return getCallArity(call).min > 0; } private void initializeFromCall(Node node) { if (node instanceof FCallNode) { Node argsNode = ((FCallNode)node).getArgsNode(); if (argsNode == null) { return; } initializeFromCall(argsNode); } else if (node instanceof LocalAsgnNode) { if (max < Integer.MAX_VALUE) { max++; } } else if (node instanceof ArgsCatNode) { ArgsCatNode args = (ArgsCatNode)node; initializeFromCall(args.getFirstNode()); // ArgsCatNode seems to be used only to append splats // but I don't get a splat node. So just pretend I did. //initializeFromCall(args.getSecondNode()); max = Integer.MAX_VALUE; } else if (node instanceof ArgsNode) { ArgsNode args = (ArgsNode)node; if (args != null) { // int value = args.getArity().getValue(); //XXX: jruby-parser int value = args.getMaxArgumentsCount(); if (value < 0) { value = -(1 + value); } min = max = value; } } else if (node instanceof SplatNode) { // Flexible number of arguments max = Integer.MAX_VALUE; } else if (node instanceof CallNode) { Node argsNode = ((CallNode)node).getArgsNode(); if (argsNode == null) { return; } initializeFromCall(argsNode); } else if (node instanceof VCallNode) { List<Node> children = node.childNodes(); for (Node child : children) { if (child.isInvisible()) { continue; } initializeFromCall(child); } } else if (node instanceof ListNode) { List<Node> children = node.childNodes(); for (Node child : children) { if (child.isInvisible()) { continue; } if (AstUtilities.isCall(child)) { min++; // The argument is a call. Normally, it will just return a single item // which would then add one to max, but it could return an array or a splat, // so we go to unknown. max = Integer.MAX_VALUE; } else if (child instanceof ArrayNode) { // This is an array inside an array - this is a single argument min++; if (max < Integer.MAX_VALUE) { // TODO: Consider walking through the elements here in case the array can be // matched up max++; } } else { initializeFromCall(child); } } } else { //assert (node instanceof LocalVarNode || node instanceof SymbolNode || node instanceof FixnumNode || ... min++; if (max < Integer.MAX_VALUE) { max++; } } } public static Arity getDefArity(Node method) { assert method instanceof DefsNode || method instanceof DefnNode; Arity arity = new Arity(0, 0); List<Node> nodes = method.childNodes(); for (Node c : nodes) { if (c instanceof ArgsNode) { arity.initializeFromDef(c); break; } } if (arity.min == -1) { return UNKNOWN; } else { return arity; } } private void initializeFromDef(Node node) { if (node instanceof ArgsNode) { ArgsNode argsNode = (ArgsNode)node; if (argsNode.getPre() != null) { initializeFromDef(argsNode.getPre()); } if (argsNode.getOptional() != null) { initializeFromDef(argsNode.getOptional()); } if (argsNode.getBlock() != null) { if (max < Integer.MAX_VALUE) { max++; } } if (argsNode.getRest() != null) { max = Integer.MAX_VALUE; } // TODO: Block arg node. Not sure how this should affect arity. //argsNode.getBlock() } else if (node instanceof ArgumentNode) { min++; max++; } else if (node instanceof LocalAsgnNode) { max++; } else if (node instanceof ListNode) { List<Node> children = node.childNodes(); for (Node child : children) { if (child.isInvisible()) { continue; } initializeFromDef(child); } } } /** * Return true iff the given call arity matches the given method arity. * * This isn't fool-proof; if you're passing around "*args" I treat * these as unknown sizes that will match - but at runtime it can fail. * For example, def jump(*args) test(*args) end; def test(foo) end; jump * will look compatible but fail since you will wind up calling test with * 0 arguments... */ public static boolean matches(Arity call, Arity method) { // If we don't know the arity for either side, consider it a match. // This may mean we get false positives (e.g. a method call doesn't // really a hit a method, so we may think it is used where it is not) if ((call == UNKNOWN) || (method == UNKNOWN)) { return true; } if (call.max < method.min) { return false; } if (call.max == Integer.MAX_VALUE) { // Unknown number of args - assume it's okay return true; } if (call.max > method.max) { return false; } return true; } @Override public String toString() { return "Arity(" + min + ":" + ((max == Integer.MAX_VALUE) ? "unlimited" : max) + ")"; } public static Arity createTestArity(int min, int max) { return new Arity(min, max); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Arity other = (Arity) obj; if (this.min != other.min) { return false; } if (this.max != other.max) { return false; } return true; } @Override public int hashCode() { int hash = 7; hash = 31 * hash + this.min; hash = 31 * hash + this.max; return hash; } }