blob: de6bd3f846c201e43a62c510e2e2b14f6e28f7e5 [file] [log] [blame]
/*******************************************************************************
* 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.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
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.expressions.Expression;
import org.eclipse.dltk.ast.references.VariableReference;
import org.eclipse.dltk.ast.statements.Block;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.DLTKLanguageManager;
import org.eclipse.dltk.core.IField;
import org.eclipse.dltk.core.IMethod;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IParameter;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.ISourceRange;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.core.mixin.MixinModel;
import org.eclipse.dltk.core.search.IDLTKSearchConstants;
import org.eclipse.dltk.core.search.IDLTKSearchScope;
import org.eclipse.dltk.core.search.SearchEngine;
import org.eclipse.dltk.core.search.SearchMatch;
import org.eclipse.dltk.core.search.SearchParticipant;
import org.eclipse.dltk.core.search.SearchPattern;
import org.eclipse.dltk.core.search.SearchRequestor;
import org.eclipse.dltk.evaluation.types.AmbiguousType;
import org.eclipse.dltk.internal.core.MethodParameterInfo;
import org.eclipse.dltk.internal.core.ModelElement;
import org.eclipse.dltk.ruby.core.RubyLanguageToolkit;
import org.eclipse.dltk.ruby.core.RubyPlugin;
import org.eclipse.dltk.ruby.core.model.FakeMethod;
import org.eclipse.dltk.ruby.internal.parser.mixin.ExactMixinSearchPattern;
import org.eclipse.dltk.ruby.internal.parser.mixin.IRubyMixinElement;
import org.eclipse.dltk.ruby.internal.parser.mixin.PrefixMixinSearchPattern;
import org.eclipse.dltk.ruby.internal.parser.mixin.RubyMixin;
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.parser.mixin.RubyMixinVariable;
import org.eclipse.dltk.ruby.internal.parser.mixin.RubyObjectMixinClass;
import org.eclipse.dltk.ruby.typeinference.BuiltinMethodsDatabase.ClassMetaclass;
import org.eclipse.dltk.ruby.typeinference.BuiltinMethodsDatabase.Metaclass;
import org.eclipse.dltk.ruby.typeinference.BuiltinMethodsDatabase.MethodInfo;
import org.eclipse.dltk.ruby.typeinference.BuiltinMethodsDatabase.ModuleMetaclass;
import org.eclipse.dltk.ti.types.IEvaluatedType;
public class RubyModelUtils {
public static IType getModelTypeByAST(TypeDeclaration astNode,
ISourceModule sourceModule) throws ModelException {
IType[] types = sourceModule.getAllTypes();
int astStart = astNode.sourceStart();
int astEnd = astNode.sourceEnd();
int bestScore = Integer.MAX_VALUE;
IType bestType = null;
for (int i = 0; i < types.length; i++) {
IType type = types[i];
ISourceRange sourceRange = type.getSourceRange();
int modelStart = sourceRange.getOffset();
int modelEnd = modelStart + sourceRange.getLength();
if (modelStart == astStart && modelEnd == astEnd)
// XXX modelEnd == astEnd + 1 currently, so this never happens
return type;
if (type.getElementName().equals(astNode.getName())) {
int diff1 = modelStart - astStart;
int diff2 = modelEnd - astEnd;
int score = diff1 * diff1 + diff2 * diff2;
if (score < bestScore) {
bestScore = score;
bestType = type;
}
}
}
return bestType;
}
public static MethodDeclaration getNodeByMethod(ModuleDeclaration rootNode,
IMethod method) throws ModelException {
ISourceRange sourceRange = method.getSourceRange();
final int modelStart = sourceRange.getOffset();
final int modelEnd = modelStart + sourceRange.getLength();
final int modelCutoffStart = modelStart - 100;
final int modelCutoffEnd = modelEnd + 100;
final String methodName = method.getElementName();
final MethodDeclaration[] bestResult = new MethodDeclaration[1];
ASTVisitor visitor = new ASTVisitor() {
int bestScore = Integer.MAX_VALUE;
private boolean interesting(ASTNode s) {
if (s.sourceStart() < 0 || s.sourceEnd() < s.sourceStart())
return true;
if (modelCutoffEnd < s.sourceStart()
|| modelCutoffStart >= s.sourceEnd())
return false;
return true;
}
@Override
public boolean visit(Expression s) throws Exception {
if (!interesting(s))
return false;
return true;
}
@Override
public boolean visit(MethodDeclaration s) throws Exception {
if (!interesting(s))
return false;
if (s.getName().equals(methodName)) {
int astStart = s.sourceStart();
int astEnd = s.sourceEnd();
int diff1 = modelStart - astStart;
int diff2 = modelEnd - astEnd;
int score = diff1 * diff1 + diff2 * diff2;
if (score < bestScore) {
bestScore = score;
bestResult[0] = s;
}
}
return true;
}
@Override
public boolean visit(ModuleDeclaration s) throws Exception {
if (!interesting(s))
return false;
return true;
}
@Override
public boolean visit(TypeDeclaration s) throws Exception {
if (!interesting(s))
return false;
return true;
}
@Override
public boolean endvisit(TypeDeclaration s) throws Exception {
if (!interesting(s))
return false;
return false /* dummy */;
}
@Override
public boolean visitGeneral(ASTNode s) throws Exception {
if (s instanceof Block)
return true;
if (!interesting(s))
return false;
return true;
}
};
try {
rootNode.traverse(visitor);
} catch (Exception e) {
RubyPlugin.log(e);
}
return bestResult[0];
}
public static IField[] findFields(RubyMixinModel rubyModel,
ISourceModule modelModule, ModuleDeclaration parsedUnit,
String prefix, int position) {
Assert.isNotNull(prefix);
List result = new ArrayList();
String[] keys = RubyTypeInferencingUtils.getModelStaticScopesKeys(
rubyModel.getRawModel(), parsedUnit, position);
IRubyMixinElement innerElement = null;
RubyMixinClass selfClass = new RubyObjectMixinClass(rubyModel, true);
if (keys != null && keys.length > 0) {
String inner = keys[keys.length - 1];
if (prefix.length() > 0 && !prefix.startsWith("@")) { //$NON-NLS-1$ // locals & constants
String varkey = inner + MixinModel.SEPARATOR + prefix;
String[] keys2 = rubyModel.getRawModel().findKeys(varkey + "*"); //$NON-NLS-1$
for (int i = 0; i < keys2.length; i++) {
IRubyMixinElement element = rubyModel
.createRubyElement(keys2[i]);
if (element instanceof RubyMixinVariable) {
RubyMixinVariable variable = (RubyMixinVariable) element;
IField field = variable.getSourceFields()[0];
if (field.getElementName().startsWith(prefix))
result.add(field);
}
}
} else {
innerElement = rubyModel.createRubyElement(inner);
if (innerElement instanceof RubyMixinMethod) {
RubyMixinMethod method = (RubyMixinMethod) innerElement;
selfClass = method.getSelfType();
RubyMixinVariable[] fields2 = method.getVariables();
addVariablesFrom(fields2, prefix, result);
} else if (innerElement instanceof RubyMixinClass) {
selfClass = (RubyMixinClass) innerElement;
}
}
}
if (selfClass != null) {
RubyMixinVariable[] fields2 = selfClass.getFields();
addVariablesFrom(fields2, prefix, result);
if (selfClass.getKey().equals("Object")) { //$NON-NLS-1$
// variables
try {
IModelElement[] children = modelModule.getChildren();
for (int i = 0; i < children.length; i++) {
if (children[i] instanceof IField) {
IField field = (IField) children[i];
if (field.getElementName().startsWith(prefix))
result.add(field);
}
}
} catch (ModelException e) {
e.printStackTrace();
}
}
}
return (IField[]) result.toArray(new IField[result.size()]);
}
/**
* Handling for "new" method
*
* @param method
* @param selfKlass
* @return
*/
private static List handleSpecialMethod(RubyMixinMethod method,
RubyMixinClass selfKlass) {
if (method.getKey().equals("Class%{new")) { //$NON-NLS-1$
RubyMixinMethod init = selfKlass.getInstanceClass() != null ? selfKlass
.getInstanceClass().getMethod("initialize") //$NON-NLS-1$
: null;
if (init != null) {
IMethod[] initMethods = init.getSourceMethods();
List result = new ArrayList();
for (int i = 0; i < initMethods.length; i++) {
try {
IParameter[] parameters = initMethods[i]
.getParameters();
int flags = initMethods[i].getFlags();
ISourceRange sourceRange = initMethods[i]
.getSourceRange();
ISourceRange nameRange = initMethods[i].getNameRange();
IModelElement parent = initMethods[i].getParent();
FakeMethod newMethod = new FakeMethod(
(ModelElement) parent,
"new", //$NON-NLS-1$
sourceRange.getOffset(), sourceRange
.getLength(), nameRange.getOffset(),
nameRange.getLength());
newMethod.setParameters(parameters);
newMethod.setFlags(flags);
String receiver = ""; //$NON-NLS-1$
if (parent instanceof IType) {
IType type = (IType) parent;
receiver = type.getTypeQualifiedName("::"); //$NON-NLS-1$
}
newMethod.setReceiver(receiver);
result.add(newMethod);
} catch (ModelException e) {
e.printStackTrace();
}
}
return result;
}
}
return null;
}
public static List getAllSourceMethods(RubyMixinMethod[] methods,
RubyMixinClass selfKlass) {
List result = new ArrayList();
for (int i = 0; i < methods.length; i++) {
if (selfKlass != null) {
List m = handleSpecialMethod(methods[i], selfKlass);
if (m != null) {
result.addAll(m);
continue;
}
}
IMethod[] sourceMethods = methods[i].getSourceMethods();
for (int j = 0; j < sourceMethods.length; j++) {
if (sourceMethods[j] != null/*
* &&
* sourceMethods[j].getElementName()
* .startsWith(prefix)
*/) {
result.add(sourceMethods[j]);
}
}
}
return result;
}
public static IMethod[] searchClassMethods(final RubyMixinModel mixinModel,
org.eclipse.dltk.core.ISourceModule modelModule,
ModuleDeclaration moduleDeclaration, IEvaluatedType type,
String prefix) {
List result = new ArrayList();
if (type instanceof RubyClassType) {
RubyClassType rubyClassType = (RubyClassType) type;
RubyMixinClass rubyClass = mixinModel
.createRubyClass(rubyClassType);
if (rubyClass != null) {
RubyMixinMethod[] methods = rubyClass
.findMethods(new PrefixMixinSearchPattern(prefix));
result.addAll(getAllSourceMethods(methods, rubyClass));
}
} else if (type instanceof AmbiguousType) {
AmbiguousType type2 = (AmbiguousType) type;
IEvaluatedType[] possibleTypes = type2.getPossibleTypes();
for (int i = 0; i < possibleTypes.length; i++) {
IMethod[] m = searchClassMethods(mixinModel, modelModule,
moduleDeclaration, possibleTypes[i], prefix);
for (int j = 0; j < m.length; j++) {
result.add(m[j]);
}
}
}
return (IMethod[]) result.toArray(new IMethod[result.size()]);
}
public static IMethod[] searchClassMethodsExact(
final RubyMixinModel mixinModel,
org.eclipse.dltk.core.ISourceModule modelModule,
ModuleDeclaration moduleDeclaration, IEvaluatedType type,
String methodName) {
List result = new ArrayList();
if (type instanceof RubyClassType) {
RubyClassType rubyClassType = (RubyClassType) type;
RubyMixinClass rubyClass = mixinModel
.createRubyClass(rubyClassType);
if (rubyClass != null) {
RubyMixinMethod[] methods = rubyClass
.findMethods(new ExactMixinSearchPattern(methodName));
result.addAll(getAllSourceMethods(methods, rubyClass));
}
} else if (type instanceof AmbiguousType) {
AmbiguousType type2 = (AmbiguousType) type;
IEvaluatedType[] possibleTypes = type2.getPossibleTypes();
for (int i = 0; i < possibleTypes.length; i++) {
IMethod[] m = searchClassMethodsExact(mixinModel, modelModule,
moduleDeclaration, possibleTypes[i], methodName);
for (int j = 0; j < m.length; j++) {
result.add(m[j]);
}
}
}
return (IMethod[]) result.toArray(new IMethod[result.size()]);
}
private static void addVariablesFrom(RubyMixinVariable[] fields2,
String prefix, List resultList) {
for (int i = 0; i < fields2.length; i++) {
IField[] sourceFields = fields2[i].getSourceFields();
if (sourceFields != null) {
for (int j = 0; j < sourceFields.length; j++) {
if (sourceFields[j] != null) {
if (sourceFields[j].getElementName().startsWith(prefix)) {
resultList.add(sourceFields[j]);
break;
}
}
}
}
}
}
public static IMethod[] getSingletonMethods(
final RubyMixinModel mixinModel, VariableReference receiver,
ModuleDeclaration parsedUnit, ISourceModule modelModule,
String methodName) {
IMethod[] res = null;
String[] scopesKeys = RubyTypeInferencingUtils
.getModelStaticScopesKeys(mixinModel.getRawModel(), parsedUnit,
receiver.sourceStart());
if (scopesKeys != null && scopesKeys.length > 0) {
String possibleName;
if (scopesKeys.length == 1) {
possibleName = receiver.getName() + RubyMixin.VIRTUAL_SUFFIX;
} else {
String last = scopesKeys[scopesKeys.length - 1];
possibleName = last + MixinModel.SEPARATOR + receiver.getName()
+ RubyMixin.VIRTUAL_SUFFIX;
}
IRubyMixinElement element = mixinModel
.createRubyElement(possibleName);
if (element instanceof RubyMixinClass) {
RubyMixinClass rubyMixinClass = (RubyMixinClass) element;
res = RubyModelUtils.searchClassMethodsExact(mixinModel,
modelModule, parsedUnit, new RubyClassType(
rubyMixinClass.getKey()), methodName);
}
}
return res;
}
// public static RubyClassType getSuperType(IType type) {
// String[] superClasses;
// try {
// superClasses = type.getSuperClasses();
// } catch (ModelException e) {
// e.printStackTrace();
// return null;
// }
// if (superClasses != null && superClasses.length == 1) {
// String name = superClasses[0];
// IType[] types;
// if (name.startsWith("::")) {
// types = DLTKModelUtil.getAllTypes(type.getScriptProject(), name, "::");
// } else {
// String scopeFQN = type.getTypeQualifiedName("::");
// types = DLTKModelUtil.getAllScopedTypes(type.getScriptProject(), name,
// "::", scopeFQN);
// }
// if (types != null && types.length > 0) {
// String typeQualifiedName =
// types[0].getTypeQualifiedName("::").substring(2);
// String[] FQN = typeQualifiedName.split("::");
// return new RubyClassType(FQN, types, null);
// } else {
// FakeMethod[] fakeMethods = getFakeMethods((ModelElement) type, name);
// if (fakeMethods != null) {
// return new RubyClassType(new String[]{name}, null, fakeMethods);
// }
// }
// }
// FakeMethod[] fakeMethods = getFakeMethods((ModelElement) type, "Object");
// return new RubyClassType(new String[]{"Object"}, null, fakeMethods);
// }
public static FakeMethod[] getFakeMethods(ModelElement parent, String klass) {
Metaclass metaclass = BuiltinMethodsDatabase.get(klass);
if (metaclass != null) {
List fakeMethods = new ArrayList();
addFakeMethods(parent, metaclass, fakeMethods);
return (FakeMethod[]) fakeMethods
.toArray(new FakeMethod[fakeMethods.size()]);
}
// XXX the following code is legacy
String[] names = getBuiltinMethodNames(klass);
if (names == null)
return new FakeMethod[0];
List methods = new ArrayList();
for (int i = 0; i < names.length; i++) {
FakeMethod method = new FakeMethod(parent, names[i]);
method.setReceiver(klass);
methods.add(method);
}
return (FakeMethod[]) methods.toArray(new FakeMethod[methods.size()]);
}
private static void addFakeMethods(ModelElement parent,
Metaclass metaclass, List fakeMethods) {
// process included modules first, to allow the class to override
// some of the methods
ModuleMetaclass[] includedModules = metaclass.getIncludedModules();
for (int i = 0; i < includedModules.length; i++) {
ModuleMetaclass module = includedModules[i];
addFakeMethods(parent, module, fakeMethods);
}
MethodInfo[] methods = metaclass.getMethods();
for (int i = 0; i < methods.length; i++) {
MethodInfo info = methods[i];
FakeMethod method = createFakeMethod(parent, metaclass, info);
fakeMethods.add(method);
}
if (metaclass instanceof BuiltinMethodsDatabase.ClassMetaclass) {
BuiltinMethodsDatabase.ClassMetaclass classMeta = (BuiltinMethodsDatabase.ClassMetaclass) metaclass;
ClassMetaclass superClass = classMeta.getSuperClass();
if (superClass != null)
addFakeMethods(parent, superClass, fakeMethods);
}
}
private static FakeMethod createFakeMethod(ModelElement parent,
Metaclass metaclass, MethodInfo info) {
FakeMethod method = new FakeMethod(parent, info.getName());
method.setFlags(info.getFlags());
int arity = info.getArity();
IParameter[] parameters;
if (arity > 0) {
parameters = new IParameter[arity];
for (int i = 0; i < arity; i++)
parameters[i] = new MethodParameterInfo("arg" + (i + 1)); //$NON-NLS-1$
} else if (arity < 0) {
parameters = new IParameter[-arity];
for (int i = 0; i < -arity - 1; i++)
parameters[i] = new MethodParameterInfo("arg" + (i + 1)); //$NON-NLS-1$
parameters[-arity - 1] = new MethodParameterInfo("..."); //$NON-NLS-1$
} else {
parameters = new IParameter[0];
}
method.setParameters(parameters);
method.setReceiver(metaclass.getName());
return method;
}
public static FakeMethod[] getFakeMetaMethods(ModelElement parent,
String klass) {
Metaclass metaclass = BuiltinMethodsDatabase.get(klass);
if (metaclass != null) {
ClassMetaclass metaMetaclass = metaclass.getMetaClass();
List fakeMethods = new ArrayList();
addFakeMethods(parent, metaMetaclass, fakeMethods);
return (FakeMethod[]) fakeMethods
.toArray(new FakeMethod[fakeMethods.size()]);
}
String[] names = getBuiltinMetaMethodNames(klass);
if (names == null)
return new FakeMethod[0];
List methods = new ArrayList();
for (int i = 0; i < names.length; i++) {
FakeMethod method = new FakeMethod(parent, names[i]);
method.setReceiver(klass);
methods.add(method);
}
return (FakeMethod[]) methods.toArray(new FakeMethod[methods.size()]);
}
public static String[] getBuiltinMethodNames(String klass) {
if (klass.equals("Object")) { //$NON-NLS-1$
return BuiltinTypeMethods.objectMethods;
} else if (klass.equals("String")) { //$NON-NLS-1$
return BuiltinTypeMethods.stringMethods;
} else if (klass.equals("Fixnum")) { //$NON-NLS-1$
return BuiltinTypeMethods.fixnumMethods;
} else if (klass.equals("Float")) { //$NON-NLS-1$
return BuiltinTypeMethods.floatMethods;
} else if (klass.equals("Regexp")) { //$NON-NLS-1$
return BuiltinTypeMethods.regexpMethods;
} else if (klass.equals("Array")) { //$NON-NLS-1$
return BuiltinTypeMethods.arrayMethods;
}
return null;
}
public static String[] getBuiltinMetaMethodNames(String klass) {
if (klass.equals("Object")) { //$NON-NLS-1$
return BuiltinMethodsDatabase.objectMethods;
}
return null;
}
public static IType[] findTopLevelTypes(ISourceModule module,
String namePrefix) {
List result = new ArrayList();
try {
// TODO: add handling of "require"
IModelElement[] children = module.getChildren();
for (int i = 0; i < children.length; i++) {
if (children[i] instanceof IType
&& children[i].getElementName().startsWith(namePrefix))
result.add(children[i]);
}
} catch (ModelException e) {
e.printStackTrace();
}
return (IType[]) result.toArray(new IType[result.size()]);
}
public static IField[] findTopLevelFields(ISourceModule module,
String namePrefix) {
List result = new ArrayList();
try {
IModelElement[] children = module.getChildren();
for (int i = 0; i < children.length; i++) {
if (children[i] instanceof IField
&& children[i].getElementName().startsWith(namePrefix))
result.add(children[i]);
}
} catch (ModelException e) {
e.printStackTrace();
}
return (IField[]) result.toArray(new IField[result.size()]);
}
public static IMethod[] findTopLevelMethods(IScriptProject project,
String namePattern) {
final List result = new ArrayList();
SearchRequestor requestor = new SearchRequestor() {
@Override
public void acceptSearchMatch(SearchMatch match)
throws CoreException {
Object element = match.getElement();
if (element instanceof IMethod) {
IMethod meth = (IMethod) element;
if (meth.getParent() instanceof ISourceModule) {
result.add(meth);
}
}
}
};
SearchPattern pattern = SearchPattern.createPattern(namePattern,
IDLTKSearchConstants.METHOD, IDLTKSearchConstants.DECLARATIONS,
SearchPattern.R_PATTERN_MATCH | SearchPattern.R_EXACT_MATCH,
DLTKLanguageManager.getLanguageToolkit(project));
IDLTKSearchScope scope;
if (project != null)
scope = SearchEngine.createSearchScope(project);
else
scope = SearchEngine.createWorkspaceScope(RubyLanguageToolkit
.getDefault());
try {
SearchEngine engine = new SearchEngine();
engine.search(pattern, new SearchParticipant[] { SearchEngine
.getDefaultSearchParticipant() }, scope, requestor, null);
} catch (CoreException e) {
if (DLTKCore.DEBUG)
e.printStackTrace();
}
return (IMethod[]) result.toArray(new IMethod[result.size()]);
}
/**
* Should return mixin-key of superclass
*
* @param type
* @return
*/
public static String evaluateSuperClass(IType type) {
String superclass = null;
String[] superClasses;
try {
superClasses = type.getSuperClasses();
} catch (ModelException e) {
e.printStackTrace();
return null;
}
if (superClasses != null && superClasses.length > 0) {
superclass = superClasses[0];
if (superclass.startsWith("::")) //$NON-NLS-1$
superclass = superclass.substring(2);
superclass = superclass.replaceAll("::", "{"); //$NON-NLS-1$ //$NON-NLS-2$
}
// TODO: add appropriate evaluation here
return superclass;
}
}