| /******************************************************************************* |
| * Copyright (c) 2005, 2016 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.ruby.typeinference; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| import org.eclipse.dltk.ast.ASTNode; |
| import org.eclipse.dltk.ast.ASTVisitor; |
| import org.eclipse.dltk.ast.declarations.MethodDeclaration; |
| import org.eclipse.dltk.ast.declarations.ModuleDeclaration; |
| import org.eclipse.dltk.ast.declarations.TypeDeclaration; |
| import org.eclipse.dltk.ast.references.VariableReference; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.IType; |
| import org.eclipse.dltk.core.ScriptModelUtil; |
| import org.eclipse.dltk.core.SourceParserUtil; |
| import org.eclipse.dltk.core.mixin.IMixinElement; |
| import org.eclipse.dltk.core.mixin.IMixinRequestor; |
| import org.eclipse.dltk.core.mixin.MixinModel; |
| import org.eclipse.dltk.core.search.TypeNameMatch; |
| import org.eclipse.dltk.core.search.TypeNameMatchRequestor; |
| import org.eclipse.dltk.core.search.indexing.IIndexConstants; |
| import org.eclipse.dltk.evaluation.types.AmbiguousType; |
| import org.eclipse.dltk.evaluation.types.UnknownType; |
| import org.eclipse.dltk.ruby.ast.RubyAssignment; |
| import org.eclipse.dltk.ruby.ast.RubyBlock; |
| import org.eclipse.dltk.ruby.ast.RubyDAssgnExpression; |
| import org.eclipse.dltk.ruby.ast.RubyForStatement2; |
| import org.eclipse.dltk.ruby.ast.RubyIfStatement; |
| import org.eclipse.dltk.ruby.ast.RubyMethodArgument; |
| import org.eclipse.dltk.ruby.ast.RubyUnlessStatement; |
| import org.eclipse.dltk.ruby.ast.RubyUntilStatement; |
| import org.eclipse.dltk.ruby.ast.RubyWhileStatement; |
| import org.eclipse.dltk.ruby.core.RubyPlugin; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.IRubyMixinElement; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixinBuildVisitor; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixinClass; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixinMethod; |
| import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixinModel; |
| import org.eclipse.dltk.ruby.internal.parsers.jruby.ASTUtils; |
| import org.eclipse.dltk.ti.IContext; |
| import org.eclipse.dltk.ti.IInstanceContext; |
| import org.eclipse.dltk.ti.ISourceModuleContext; |
| import org.eclipse.dltk.ti.types.IEvaluatedType; |
| import org.eclipse.dltk.ti.types.RecursionTypeCall; |
| |
| public class RubyTypeInferencingUtils { |
| |
| /** |
| * Searches all top level types, which starts with prefix |
| */ |
| public static IType[] getAllTypes(ISourceModule module, String prefix) { |
| final List<IType> types = new ArrayList<IType>(); |
| |
| TypeNameMatchRequestor requestor = new TypeNameMatchRequestor() { |
| |
| @Override |
| public void acceptTypeNameMatch(TypeNameMatch match) { |
| IType type = match.getType(); |
| if (type.getParent() instanceof ISourceModule) { |
| types.add(type); |
| } |
| } |
| |
| }; |
| |
| ScriptModelUtil.searchTypeDeclarations(module.getScriptProject(), |
| prefix + "*", requestor); //$NON-NLS-1$ |
| |
| return types.toArray(new IType[types.size()]); |
| } |
| |
| public static ASTNode[] getAllStaticScopes(ModuleDeclaration rootNode, |
| final int requestedOffset) { |
| final Collection<ASTNode> scopes = new ArrayList<ASTNode>(); |
| ASTVisitor visitor = new OffsetTargetedASTVisitor(requestedOffset) { |
| @Override |
| public boolean visitInteresting(MethodDeclaration s) { |
| scopes.add(s); |
| return true; |
| } |
| |
| @Override |
| public boolean visitInteresting(ModuleDeclaration s) { |
| scopes.add(s); |
| return true; |
| } |
| |
| @Override |
| public boolean visitInteresting(TypeDeclaration s) { |
| scopes.add(s); |
| return true; |
| } |
| |
| @Override |
| protected boolean visitInteresting(RubyBlock b) { |
| scopes.add(b); |
| return true; |
| } |
| |
| @Override |
| protected boolean visitGeneralInteresting(ASTNode s) { |
| if (ASTUtils.isNodeScoping(s)) { |
| scopes.add(s); |
| return true; |
| } |
| return super.visitGeneralInteresting(s); |
| } |
| |
| }; |
| try { |
| rootNode.traverse(visitor); |
| } catch (Exception e) { |
| RubyPlugin.log(e); |
| } |
| if (scopes.size() == 0) |
| scopes.add(rootNode); |
| return scopes.toArray(new ASTNode[scopes.size()]); |
| } |
| |
| public static IMixinElement[] getModelStaticScopes(MixinModel model, |
| ModuleDeclaration rootNode, final int requestedOffset) { |
| String[] modelStaticScopesKeys = getModelStaticScopesKeys(model, |
| rootNode, requestedOffset); |
| IMixinElement[] result = new IMixinElement[modelStaticScopesKeys.length]; |
| for (int i = 1; i < modelStaticScopesKeys.length; i++) { //XXX-fourdman: |
| // removed |
| // Object |
| // resulution |
| result[i] = model.get(modelStaticScopesKeys[i]); |
| // if (result[i] == null) |
| // throw new RuntimeException("getModelStaticScopes(): Failed to get |
| // element from mixin-model: " + modelStaticScopesKeys[i]); |
| } |
| return result; |
| } |
| |
| public static String[] getModelStaticScopesKeys(MixinModel model, |
| ModuleDeclaration rootNode, final int requestedOffset) { |
| ASTNode[] allStaticScopes = RubyTypeInferencingUtils |
| .getAllStaticScopes(rootNode, requestedOffset); |
| return RubyMixinBuildVisitor.restoreScopesByNodes(allStaticScopes); |
| } |
| |
| public static IEvaluatedType determineSelfClass(RubyMixinModel mixinModel, |
| IContext context, int keyOffset) { |
| if (context instanceof IInstanceContext) { |
| IInstanceContext instanceContext = (IInstanceContext) context; |
| return instanceContext.getInstanceType(); |
| } else { |
| ISourceModuleContext basicContext = (ISourceModuleContext) context; |
| return determineSelfClass(mixinModel, basicContext |
| .getSourceModule(), basicContext.getRootNode(), keyOffset); |
| } |
| } |
| |
| /** |
| * Determines a fully-qualified names of the class scope that the given |
| * offset is statically enclosed in. |
| * |
| * @param sourceModule |
| * a module containing the given offsets |
| * @param rootNode |
| * the root of AST corresponding to the given source module |
| * @param keyOffset |
| * the offset |
| * @return The type of <code>self</code> at the given offset (never null) |
| */ |
| public static RubyClassType determineSelfClass(RubyMixinModel rubyModel, |
| final ISourceModule sourceModule, ModuleDeclaration rootNode, |
| final int keyOffset) { |
| String[] keys = getModelStaticScopesKeys(rubyModel.getRawModel(), |
| rootNode, keyOffset); |
| if (keys != null && keys.length > 0) { |
| String inner = keys[keys.length - 1]; |
| IRubyMixinElement rubyElement = rubyModel.createRubyElement(inner); |
| // ssanders: Fallback to locating the immediate parent |
| if ((rubyElement == null) |
| && (inner.indexOf(IIndexConstants.SEPARATOR) != -1)) { |
| rubyElement = rubyModel.createRubyElement(inner.substring(0, |
| inner.lastIndexOf(IIndexConstants.SEPARATOR))); |
| } |
| if (rubyElement instanceof RubyMixinMethod) { |
| RubyMixinMethod method = (RubyMixinMethod) rubyElement; |
| RubyMixinClass selfType = method.getSelfType(); |
| if (selfType != null) { |
| return new RubyClassType(selfType.getKey()); |
| } |
| } else if (rubyElement instanceof RubyMixinClass) { |
| RubyMixinClass rubyMixinClass = (RubyMixinClass) rubyElement; |
| return new RubyClassType(rubyMixinClass.getKey()); |
| } |
| } |
| return null; |
| } |
| |
| public static RubyAssignment[] findLocalVariableAssignments( |
| final ASTNode scope, final ASTNode nextScope, final String varName) { |
| final Collection<RubyAssignment> assignments = new ArrayList<RubyAssignment>(); |
| ASTVisitor visitor = new ASTVisitor() { |
| |
| @Override |
| public boolean visit(MethodDeclaration s) throws Exception { |
| if (s == scope) |
| return true; |
| return false; |
| } |
| |
| @Override |
| public boolean visit(TypeDeclaration s) throws Exception { |
| if (s == scope) |
| return true; |
| return false; |
| } |
| |
| @Override |
| public boolean visit(ASTNode node) throws Exception { |
| if (node instanceof RubyAssignment) { |
| RubyAssignment assignment = (RubyAssignment) node; |
| ASTNode lhs = assignment.getLeft(); |
| if (lhs instanceof VariableReference) { |
| VariableReference varRef = (VariableReference) lhs; |
| if (varName.equals(varRef.getName())) { |
| assignments.add(assignment); |
| } |
| } |
| } |
| if (node == nextScope) |
| return false; |
| return true; |
| } |
| |
| }; |
| try { |
| scope.traverse(visitor); |
| } catch (Exception e) { |
| RubyPlugin.log(e); |
| } |
| return assignments.toArray(new RubyAssignment[assignments.size()]); |
| } |
| |
| public static boolean isRootLocalScope(ASTNode node) { |
| return node instanceof ModuleDeclaration |
| || node instanceof TypeDeclaration |
| || node instanceof MethodDeclaration; |
| } |
| |
| public static IEvaluatedType combineTypes(Collection<IEvaluatedType> evaluaedTypes) { |
| Set<IEvaluatedType> types = new HashSet<IEvaluatedType>(evaluaedTypes); |
| types.remove(null); |
| if (types.size() > 1 && types.contains(RecursionTypeCall.INSTANCE)) |
| types.remove(RecursionTypeCall.INSTANCE); |
| return combineUniqueTypes(types.toArray(new IEvaluatedType[types.size()])); |
| } |
| |
| private static IEvaluatedType combineUniqueTypes(IEvaluatedType[] types) { |
| if (types.length == 0) |
| return UnknownType.INSTANCE; |
| if (types.length == 1) |
| return types[0]; |
| return new AmbiguousType(types); |
| } |
| |
| public static IEvaluatedType combineTypes(IEvaluatedType[] evaluaedTypes) { |
| return combineTypes(Arrays.asList(evaluaedTypes)); |
| } |
| |
| public static ModuleDeclaration parseSource(ISourceModule module) { |
| return SourceParserUtil.getModuleDeclaration(module); |
| } |
| |
| public static IEvaluatedType getAmbiguousMetaType(IEvaluatedType receiver) { |
| if (receiver instanceof AmbiguousType) { |
| Set<IEvaluatedType> possibleReturns = new HashSet<IEvaluatedType>(); |
| AmbiguousType ambiguousType = (AmbiguousType) receiver; |
| IEvaluatedType[] possibleTypes = ambiguousType.getPossibleTypes(); |
| for (int i = 0; i < possibleTypes.length; i++) { |
| IEvaluatedType type = possibleTypes[i]; |
| IEvaluatedType possibleReturn = getAmbiguousMetaType(type); |
| possibleReturns.add(possibleReturn); |
| } |
| return RubyTypeInferencingUtils.combineTypes(possibleReturns); |
| } |
| return null; |
| } |
| |
| public static String searchConstantElement(MixinModel mixinModel, |
| ModuleDeclaration module, int calculationOffset, String constantName) { |
| String[] modelStaticScopes = getModelStaticScopesKeys(mixinModel, |
| module, calculationOffset); |
| |
| String resultKey = null; |
| |
| for (int i = modelStaticScopes.length - 1; i >= 0; i--) { |
| String possibleKey = modelStaticScopes[i] |
| + IMixinRequestor.MIXIN_NAME_SEPARATOR + constantName; |
| if (mixinModel.keyExists(possibleKey)) { |
| resultKey = possibleKey; |
| break; |
| } |
| } |
| |
| // check top-most scope |
| if (resultKey == null) { |
| if (mixinModel.keyExists(constantName)) { |
| resultKey = constantName; |
| } |
| } |
| return resultKey; |
| } |
| |
| private static class LocalVariablesSearchVisitor extends ASTVisitor { |
| |
| private class VariableAssignment { |
| public RubyAssignment assignment; |
| public int level; |
| |
| public VariableAssignment(RubyAssignment assignenment, int level) { |
| super(); |
| this.assignment = assignenment; |
| this.level = level; |
| } |
| |
| } |
| |
| private List<VariableAssignment> assignements; |
| private Stack<ASTNode> level = new Stack<ASTNode>(); |
| private int maxLevel; |
| |
| private final String name; |
| private final int offset; |
| private final ASTNode root; |
| |
| public LocalVariablesSearchVisitor(String name, ASTNode root, int offset) { |
| super(); |
| this.name = name; |
| this.root = root; |
| this.offset = offset; |
| this.maxLevel = 0; |
| this.assignements = new ArrayList<VariableAssignment>(); |
| this.level.clear(); |
| } |
| |
| @Override |
| public boolean visitGeneral(ASTNode node) throws Exception { |
| if (node == root) |
| return true; |
| if (node instanceof MethodDeclaration |
| || node instanceof TypeDeclaration) |
| return false; |
| if (node instanceof RubyAssignment) { |
| if (!(node.sourceEnd() <= offset)) |
| return false; |
| RubyAssignment rubyAssignment = (RubyAssignment) node; |
| ASTNode lhs = rubyAssignment.getLeft(); |
| if (lhs instanceof VariableReference) { |
| VariableReference varRef = (VariableReference) lhs; |
| if (name.equals(varRef.getName())) { |
| assignements.add(new VariableAssignment(rubyAssignment, |
| level.size())); |
| } |
| } |
| } else if (node instanceof RubyIfStatement |
| || node instanceof RubyForStatement2 |
| || node instanceof RubyWhileStatement |
| || node instanceof RubyBlock |
| || node instanceof RubyUntilStatement |
| || node instanceof RubyUnlessStatement) { |
| if (node.sourceStart() >= offset) |
| return false; |
| level.push(node); |
| if (node.sourceEnd() >= offset && level.size() > maxLevel) |
| maxLevel = level.size(); |
| } |
| return true; |
| } |
| |
| @Override |
| public void endvisitGeneral(ASTNode node) throws Exception { |
| if (level.size() > 0 && level.peek().equals(node)) { |
| level.pop(); |
| } |
| } |
| |
| public RubyAssignment getUnconditionalAssignment() { |
| VariableAssignment[] array = assignements |
| .toArray(new VariableAssignment[assignements.size()]); |
| for (int i = array.length - 1; i >= 0; i--) { |
| VariableAssignment a = array[i]; |
| if (a.level <= maxLevel) { |
| return a.assignment; |
| } |
| } |
| return null; |
| } |
| |
| public List<RubyAssignment> getConditionals() { |
| RubyAssignment unconditionalAssignment = getUnconditionalAssignment(); |
| List<RubyAssignment> result = new ArrayList<RubyAssignment>(); |
| for (Iterator<VariableAssignment> iter = assignements.iterator(); iter.hasNext();) { |
| VariableAssignment assign = iter.next(); |
| if (unconditionalAssignment == null |
| || assign.assignment.sourceStart() > unconditionalAssignment |
| .sourceStart()) |
| result.add(assign.assignment); |
| |
| } |
| return result; |
| |
| } |
| |
| } |
| |
| private static RubyAssignment findAssignments(String variableName, |
| ASTNode scopeNode, int tillOffset, List<RubyAssignment> conditionals) { |
| LocalVariablesSearchVisitor visitor = new LocalVariablesSearchVisitor( |
| variableName, scopeNode, tillOffset); |
| try { |
| scopeNode.traverse(visitor); |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| // System.out.println(); |
| if (conditionals != null) |
| conditionals.addAll(visitor.getConditionals()); |
| return visitor.getUnconditionalAssignment(); |
| } |
| |
| public static LocalVariableInfo inspectLocalVariable( |
| ModuleDeclaration module, int offset, String name) { |
| LocalVariableInfo info = new LocalVariableInfo(); |
| ASTNode[] scopes = getAllStaticScopes(module, offset); |
| int i = -1; |
| for (i = scopes.length - 1; i >= 0; i--) { |
| ASTNode scope = scopes[i]; |
| if (scope instanceof TypeDeclaration) { |
| info.setDeclaringScope(scope); |
| List<RubyAssignment> conditionals = new ArrayList<RubyAssignment>(); |
| RubyAssignment last = findAssignments(name, scope, offset, |
| conditionals); |
| info.setLastAssignment(last); |
| info.setDeclaringScope(scope); |
| info.setConditionalAssignments(conditionals); |
| break; |
| } else if (scope instanceof MethodDeclaration) { |
| MethodDeclaration method = (MethodDeclaration) scope; |
| boolean isArgument = false; |
| List<RubyMethodArgument> arguments = method.getArguments(); |
| for (Iterator<RubyMethodArgument> iterator = arguments.iterator(); iterator |
| .hasNext();) { |
| RubyMethodArgument arg = iterator.next(); |
| String argName = arg.getName(); |
| if (argName.equals(name)) { |
| isArgument = true; |
| break; |
| } |
| } |
| if (isArgument |
| && (info.getKind() == LocalVariableInfo.KIND_DEFAULT)) |
| info.setKind(LocalVariableInfo.KIND_METHOD_ARG); |
| List<RubyAssignment> conditionals = new ArrayList<RubyAssignment>(); |
| RubyAssignment last = findAssignments(name, scope, offset, |
| conditionals); |
| info.setLastAssignment(last); |
| info.setDeclaringScope(scope); |
| info.setConditionalAssignments(conditionals); |
| break; |
| } else if (scope instanceof RubyBlock) { |
| boolean isArgument = false; |
| RubyBlock block = (RubyBlock) scope; |
| Set<ASTNode> vars = block.getVars(); |
| for (Iterator<ASTNode> iterator = vars.iterator(); iterator.hasNext();) { |
| ASTNode vnode = iterator.next(); |
| if (vnode instanceof RubyDAssgnExpression) { |
| RubyDAssgnExpression v = (RubyDAssgnExpression) vnode; |
| if (v.getName().equals(name)) { |
| isArgument = true; |
| break; |
| } |
| } |
| } |
| if (isArgument) { |
| if (info.getKind() == LocalVariableInfo.KIND_DEFAULT) |
| info.setKind(LocalVariableInfo.KIND_BLOCK_ARG); |
| // List conditionals = new ArrayList(); |
| // RubyAssignment last = findAssignments(name, scope, |
| // offset, |
| // conditionals); |
| // info.setLastAssignment(last); |
| // info.setDeclaringScope(scope); |
| // info.setConditionalAssignments(conditionals); |
| // break; |
| } |
| } else if (scope instanceof RubyForStatement2) { |
| |
| RubyForStatement2 forStatement = (RubyForStatement2) scope; |
| ASTNode var = forStatement.getTarget(); |
| |
| if (var instanceof RubyAssignment) { |
| |
| RubyAssignment rubyAssignment = (RubyAssignment) var; |
| if (rubyAssignment.getLeft() instanceof VariableReference) { |
| VariableReference ref = (VariableReference) rubyAssignment |
| .getLeft(); |
| if (ref.getName().equals(name)) { |
| if (info.getKind() == LocalVariableInfo.KIND_DEFAULT) |
| info.setKind(LocalVariableInfo.KIND_LOOP_VAR); |
| // List conditionals = new ArrayList(); |
| // RubyAssignment last = findAssignments(name, |
| // scope, |
| // offset, conditionals); |
| // info.setLastAssignment(last); |
| // info.setDeclaringScope(scope); |
| // info.setConditionalAssignments(conditionals); |
| // break; |
| } |
| } |
| } |
| |
| } |
| } |
| if (i < 0) { |
| // consider the whole module |
| List<RubyAssignment> conditionals = new ArrayList<RubyAssignment>(); |
| RubyAssignment last = findAssignments(name, module, offset, |
| conditionals); |
| info.setLastAssignment(last); |
| info.setDeclaringScope(module); |
| info.setConditionalAssignments(conditionals); |
| } |
| return info; |
| } |
| |
| // public static LocalVariableInfo searchLocalVars(ModuleDeclaration module, |
| // int offset, String name) { |
| // ASTNode[] scopes = getAllStaticScopes(module, offset); |
| // int i = -1; |
| // loop: for (i = scopes.length - 1; i >= 0; i--) { |
| // if (scopes[i] instanceof MethodDeclaration |
| // || scopes[i] instanceof TypeDeclaration) { |
| // break; |
| // } else if (scopes[i] instanceof RubyBlock) { |
| // RubyBlock rubyBlock = (RubyBlock) scopes[i]; |
| // Set vars = rubyBlock.getVars(); |
| // for (Iterator iterator = vars.iterator(); iterator.hasNext();) { |
| // ASTNode vnode = (ASTNode) iterator.next(); |
| // if (vnode instanceof RubyDAssgnExpression) { |
| // RubyDAssgnExpression v = (RubyDAssgnExpression) vnode; |
| // if (v.getName().equals(name)) { |
| // break loop; |
| // } |
| // } |
| // } |
| // } else if (scopes[i] instanceof RubyForStatement2) { |
| // RubyForStatement2 forst = (RubyForStatement2) scopes[i]; |
| // ASTListNode vars = forst.getList(); |
| // for (Iterator iterator = vars.getChilds().iterator(); iterator |
| // .hasNext();) { |
| // ASTNode vnode = (ASTNode) iterator.next(); |
| // if (vnode instanceof RubyAssignment) { |
| // RubyAssignment assign = (RubyAssignment) vnode; |
| // if (assign.getLeft() instanceof VariableReference) { |
| // VariableReference ref = (VariableReference) assign |
| // .getLeft(); |
| // if (ref.getName().equals(name)) |
| // break loop; |
| // } |
| // } |
| // } |
| // } |
| // } |
| // if (i < 0) |
| // i = 0; |
| // LocalVarSearchVisitor visitor = new LocalVarSearchVisitor(name, |
| // scopes[i], offset); |
| // try { |
| // scopes[i].traverse(visitor); |
| // } catch (Exception e) { |
| // e.printStackTrace(); |
| // } |
| // List conds = visitor.getConditionalAssignments(); |
| // RubyAssignment[] c = (RubyAssignment[]) conds |
| // .toArray(new RubyAssignment[conds.size()]); |
| // return new LocalVariableInfo(scopes[i], c, visitor.getLast()); |
| // |
| // } |
| |
| } |