| /******************************************************************************* |
| * Copyright (c) 2010 xored software, Inc. |
| * |
| * 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 |
| * |
| * Contributors: |
| * xored software, Inc. - initial API and Implementation (Alex Panchenko) |
| *******************************************************************************/ |
| package org.eclipse.dltk.internal.javascript.ti; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.dltk.annotations.Internal; |
| import org.eclipse.dltk.ast.ASTNode; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.javascript.ast.Script; |
| import org.eclipse.dltk.javascript.core.JavaScriptPlugin; |
| import org.eclipse.dltk.javascript.internal.core.ThreadTypeSystemImpl; |
| import org.eclipse.dltk.javascript.parser.JSProblem; |
| import org.eclipse.dltk.javascript.parser.JSProblemReporter; |
| import org.eclipse.dltk.javascript.typeinference.IValueCollection; |
| import org.eclipse.dltk.javascript.typeinference.IValueParent; |
| import org.eclipse.dltk.javascript.typeinference.IValueReference; |
| import org.eclipse.dltk.javascript.typeinference.ReferenceKind; |
| import org.eclipse.dltk.javascript.typeinfo.AttributeKey; |
| import org.eclipse.dltk.javascript.typeinfo.IElementResolver; |
| import org.eclipse.dltk.javascript.typeinfo.ILocalTypeReference; |
| import org.eclipse.dltk.javascript.typeinfo.IMemberEvaluator; |
| import org.eclipse.dltk.javascript.typeinfo.IModelBuilder; |
| import org.eclipse.dltk.javascript.typeinfo.IRLocalType; |
| import org.eclipse.dltk.javascript.typeinfo.IRMember; |
| import org.eclipse.dltk.javascript.typeinfo.IRRecordType; |
| import org.eclipse.dltk.javascript.typeinfo.IRType; |
| import org.eclipse.dltk.javascript.typeinfo.IRTypeDeclaration; |
| import org.eclipse.dltk.javascript.typeinfo.IRTypeTransformer; |
| import org.eclipse.dltk.javascript.typeinfo.ITypeInfoContext; |
| import org.eclipse.dltk.javascript.typeinfo.ITypeProvider; |
| import org.eclipse.dltk.javascript.typeinfo.ITypeSystem; |
| import org.eclipse.dltk.javascript.typeinfo.RTypes; |
| import org.eclipse.dltk.javascript.typeinfo.ReferenceSource; |
| import org.eclipse.dltk.javascript.typeinfo.TypeInfoManager; |
| import org.eclipse.dltk.javascript.typeinfo.TypeMode; |
| import org.eclipse.dltk.javascript.typeinfo.TypeUtil; |
| import org.eclipse.dltk.javascript.typeinfo.model.JSType; |
| import org.eclipse.dltk.javascript.typeinfo.model.Member; |
| import org.eclipse.dltk.javascript.typeinfo.model.Property; |
| import org.eclipse.dltk.javascript.typeinfo.model.RecordType; |
| import org.eclipse.dltk.javascript.typeinfo.model.SimpleType; |
| import org.eclipse.dltk.javascript.typeinfo.model.Type; |
| import org.eclipse.dltk.javascript.typeinfo.model.TypeInfoModelFactory; |
| import org.eclipse.dltk.javascript.typeinfo.model.TypeInfoModelLoader; |
| import org.eclipse.dltk.javascript.typeinfo.model.TypeKind; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.InternalEObject; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.resource.impl.ResourceImpl; |
| import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; |
| |
| public class TypeInferencer2 extends TypeSystemImpl implements |
| ITypeInferenceContext { |
| |
| private TypeInferencerVisitor visitor; |
| private ReferenceSource source; |
| |
| private void initializeVisitor() { |
| if (visitor == null) { |
| visitor = new TypeInferencerVisitor(this); |
| } |
| visitor.initialize(); |
| } |
| |
| public void setVisitor(TypeInferencerVisitor visitor) { |
| this.visitor = visitor; |
| } |
| |
| public void setModelElement(IModelElement modelElement) { |
| setSource(ReferenceSource.create(modelElement)); |
| } |
| |
| public void setSource(ReferenceSource source) { |
| this.source = source; |
| } |
| |
| private static final boolean DEBUG = false; |
| |
| public void doInferencing(Script script) { |
| if (DEBUG) |
| System.out.println("Visiting " |
| + source |
| + " with " |
| + (visitor != null ? visitor.getClass().getName() |
| : "Default") + " in " |
| + Thread.currentThread().getName()); |
| final ITypeSystem saved = CURRENT.get(); |
| try { |
| ((ThreadTypeSystemImpl) CURRENT).set(this); |
| elements.clear(); |
| modelBuilders = null; |
| typeProviders = null; |
| initializeVisitor(); |
| visitor.visit(script); |
| visitor.done(); |
| // IValueCollection collection = visitor.getCollection(); |
| // visitor = null; |
| // return collection; |
| } catch (PositionReachedException e) { |
| // visitor = null; |
| throw e; |
| } catch (TypeInferencerVisitorBase.TIWrappedException e) { |
| log(e.getCause()); |
| } catch (RuntimeException e) { |
| log(e); |
| } catch (AssertionError e) { |
| log(e); |
| } finally { |
| ((ThreadTypeSystemImpl) CURRENT).set(saved); |
| } |
| // return null; |
| } |
| |
| protected void log(Throwable e) { |
| final JSProblemReporter reporter = visitor.getProblemReporter(); |
| if (reporter != null) { |
| reporter.reportProblem(new JSProblem(e)); |
| } |
| JavaScriptPlugin.error(e); |
| } |
| |
| public IValueReference evaluate(ASTNode node) { |
| initializeVisitor(); |
| return visitor.visit(node); |
| } |
| |
| public IValueCollection getCollection() { |
| return visitor.getCollection(); |
| } |
| |
| public IValueCollection currentCollection() { |
| return visitor.peekContext(); |
| } |
| |
| /** |
| * Implementation of the {@link ILocalTypeReference} |
| */ |
| private static class LocalType implements ILocalTypeReference { |
| final Type type; |
| private boolean enabled; |
| |
| public LocalType(Type type) { |
| this.type = type; |
| this.enabled = true; |
| } |
| |
| public Type getType() { |
| return type; |
| } |
| |
| public boolean isEnabled() { |
| return enabled; |
| } |
| |
| public void setEnabled(boolean value) { |
| this.enabled = value; |
| } |
| |
| @Override |
| public String toString() { |
| return "(" + type + ", enabled=" + enabled + ")"; |
| } |
| } |
| |
| /** |
| * Container for potentially multiple local types with the same name. |
| */ |
| @SuppressWarnings("serial") |
| private static class LocalTypeBucket extends ArrayList<LocalType> { |
| public LocalTypeBucket() { |
| super(1); |
| } |
| |
| Type unknownType; |
| } |
| |
| /** |
| * Local (i.e. temporary and transient) types, which are manually registered |
| * using {@link ITypeInfoContext#registerLocalType(Type, Object)}. The |
| * registration is valid only within the same module, and this field is |
| * cleared before processing the next module. |
| */ |
| private final Map<String, LocalTypeBucket> localTypes = new HashMap<String, LocalTypeBucket>(); |
| |
| /* |
| * @see ITypeInfoContext#registerLocalType(Type) |
| */ |
| public ILocalTypeReference registerLocalType(Type type) { |
| assert type != null; |
| final String name = type.getName(); |
| assert name != null; |
| assert type.eResource() == null; |
| final LocalType result; |
| synchronized (localTypes) { |
| LocalTypeBucket locals = localTypes.get(name); |
| if (locals == null) { |
| locals = new LocalTypeBucket(); |
| localTypes.put(name, locals); |
| } else { |
| for (LocalType localType : locals) { |
| if (type.equals(localType.type)) { |
| return localType; |
| } |
| } |
| } |
| result = new LocalType(type); |
| typeRS.addToResource(type); |
| locals.add(result); |
| } |
| return result; |
| } |
| |
| /* |
| * @see ITypeInfoContext#getLocalTypes(String) |
| */ |
| public ILocalTypeReference[] getLocalTypes(String name) { |
| synchronized (localTypes) { |
| LocalTypeBucket locals = localTypes.get(name); |
| if (locals != null) { |
| return locals.toArray(new ILocalTypeReference[locals.size()]); |
| } else { |
| return new ILocalTypeReference[0]; |
| } |
| } |
| } |
| |
| private final Map<String, Type> types = new HashMap<String, Type>(); |
| |
| public Type getType(String typeName) { |
| if (typeName == null || typeName.length() == 0) { |
| return null; |
| } |
| final boolean queryProviders = canQueryTypeProviders(); |
| return getType(typeName, null, queryProviders, true, !queryProviders, |
| true); |
| } |
| |
| public SimpleType getTypeRef(String typeName) { |
| return TypeUtil.ref(getType(typeName)); |
| } |
| |
| @Override |
| public Type getKnownType(String typeName) { |
| return getKnownType(typeName, null); |
| } |
| |
| public Type getKnownType(String typeName, TypeMode mode) { |
| if (typeName == null || typeName.length() == 0) { |
| return null; |
| } |
| final boolean queryProviders = canQueryTypeProviders(); |
| return getType(typeName, mode, queryProviders, true, !queryProviders, |
| false); |
| } |
| |
| /** |
| * Allows creation of unknown types in {@link #resolveType(Type)}. Most of |
| * the time it doesn't matter, but in some special cases original proxy |
| * should be kept, that's why this method is available for override. |
| */ |
| protected boolean resolveToUnknown() { |
| return true; |
| } |
| |
| @Override |
| public Type doResolveType(Type type) { |
| final String typeName = URI.decode(((InternalEObject) type).eProxyURI() |
| .fragment()); |
| final Type resolved = getType(typeName, null, true, true, false, |
| resolveToUnknown()); |
| if (resolved != null) { |
| return resolved; |
| } |
| return type; |
| } |
| |
| public Set<String> listTypes(TypeMode mode, String prefix) { |
| Set<String> result = new HashSet<String>(); |
| final TypeInfoModelLoader loader = TypeInfoModelLoader.getInstance(); |
| Set<String> typeNames = mode == TypeMode.CODE ? loader |
| .listTypeLiterals(prefix) : loader.listTypes(prefix); |
| if (typeNames != null) { |
| result.addAll(typeNames); |
| } |
| for (ITypeProvider provider : getTypeProviders()) { |
| typeNames = provider.listTypes(this, mode, prefix); |
| if (typeNames != null) { |
| result.addAll(typeNames); |
| } |
| } |
| |
| String lowerPrefix = prefix.toLowerCase(); |
| synchronized (localTypes) { |
| Set<String> localTypeNames = localTypes.keySet(); |
| for (String name : localTypeNames) { |
| String typeName = name; |
| if (typeName.toLowerCase().equals(lowerPrefix)) |
| continue; // skip the thing that is already typed. |
| int index = typeName.lastIndexOf('.'); |
| if (index != -1) { |
| typeName = typeName.substring(index + 1); |
| } |
| if (typeName.toLowerCase().startsWith(lowerPrefix)) { |
| result.add(name); |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * @return the source |
| */ |
| public ReferenceSource getSource() { |
| return source; |
| } |
| |
| public IValueCollection getTopValueCollection() { |
| if (resolve) { |
| for (IMemberEvaluator evaluator : TypeInfoManager |
| .getMemberEvaluators()) { |
| final IValueCollection collection = evaluator |
| .getTopValueCollection(this); |
| if (collection != null) { |
| return collection; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public IModelElement getModelElement() { |
| return source != null ? source.getModelElement() : null; |
| } |
| |
| private enum TypeResolveMode { |
| SIMPLE, PROXY, UNKNOWN |
| } |
| |
| @Internal |
| Type getType(String typeName, TypeMode mode, boolean queryProviders, |
| boolean queryPredefined, boolean allowProxy, boolean allowUnknown) { |
| synchronized (localTypes) { |
| final LocalTypeBucket locals = localTypes.get(typeName); |
| if (locals != null) { |
| for (LocalType localType : locals) { |
| if (localType.isEnabled()) { |
| return localType.type; |
| } |
| } |
| if (locals.unknownType != null) { |
| return allowUnknown ? locals.unknownType : null; |
| } |
| } |
| } |
| Type type; |
| synchronized (types) { |
| type = types.get(typeName); |
| } |
| if (type != null) { |
| return type; |
| } |
| type = loadType(typeName, mode, queryProviders, queryPredefined); |
| if (type != null) { |
| validateTypeInfo(type); |
| synchronized (types) { |
| types.put(typeName, type); |
| } |
| typeRS.addToResource(type); |
| return type; |
| } |
| if (allowProxy) { |
| return TypeUtil.createProxy(typeName); |
| } |
| if (allowUnknown) { |
| return createUnknown(typeName); |
| } |
| return null; |
| } |
| |
| private void validateTypeInfo(Type type) { |
| final Resource resource = ((EObject) type).eResource(); |
| if (resource != null) { |
| final ResourceSet resourceSet = resource.getResourceSet(); |
| if (resourceSet != null) { |
| Assert.isLegal(!(resourceSet instanceof TypeResourceSet), |
| "Type " + type.getName() + " has invalid resource: " |
| + resource + " (" + resourceSet.getClass() |
| + ")"); |
| } |
| } |
| // TODO check that member referenced types are contained or proxy |
| } |
| |
| @Internal |
| Type createUnknown(String typeName) { |
| final Type type = TypeInfoModelFactory.eINSTANCE.createType(); |
| type.setName(typeName); |
| type.setKind(TypeKind.UNKNOWN); |
| synchronized (localTypes) { |
| LocalTypeBucket locals = localTypes.get(typeName); |
| if (locals != null) { |
| for (LocalType localType : locals) { |
| if (localType.isEnabled()) { |
| return localType.type; |
| } |
| } |
| if (locals.unknownType != null) { |
| return locals.unknownType; |
| } |
| } else { |
| locals = new LocalTypeBucket(); |
| localTypes.put(typeName, locals); |
| } |
| locals.unknownType = type; |
| } |
| typeRS.add(type); |
| return type; |
| } |
| |
| private final Map<String, Boolean> activeTypeRequests = new HashMap<String, Boolean>(); |
| |
| private boolean canQueryTypeProviders() { |
| return activeTypeRequests.isEmpty(); |
| } |
| |
| private Type loadType(String typeName, TypeMode mode, |
| boolean queryProviders, boolean queryPredefined) { |
| if (queryProviders |
| && activeTypeRequests.put(typeName, Boolean.FALSE) == null) { |
| try { |
| for (ITypeProvider provider : getTypeProviders()) { |
| final Type type = provider.getType(this, mode, typeName); |
| if (type != null && !type.eIsProxy()) { |
| return type; |
| } |
| } |
| } finally { |
| activeTypeRequests.remove(typeName); |
| } |
| } |
| if (queryPredefined) { |
| final TypeInfoModelLoader loader = TypeInfoModelLoader |
| .getInstance(); |
| final Type type; |
| if (mode == TypeMode.CODE) { |
| type = loader.getTypeLiteral(typeName); |
| } else { |
| type = loader.getType(typeName); |
| } |
| if (type != null) { |
| return type; |
| } |
| } |
| return null; |
| } |
| |
| private ITypeProvider[] typeProviders = null; |
| |
| public ITypeProvider[] getTypeProviders() { |
| if (typeProviders == null) { |
| typeProviders = createTypeProviders(); |
| } |
| return typeProviders; |
| } |
| |
| protected ITypeProvider[] createTypeProviders() { |
| return TypeInfoManager.createTypeProviders(this); |
| } |
| |
| private class TypeResourceSet extends ResourceSetImpl { |
| |
| public TypeResourceSet() { |
| TypeInfoModelLoader.getInstance().initializeURIMap(this); |
| } |
| |
| @Override |
| public EObject getEObject(URI uri, boolean loadOnDemand) { |
| if (TypeUtil.isTypeProxy(uri)) { |
| final String typeName = URI.decode(uri.fragment()); |
| final Type type = resolveTypeProxy(typeName); |
| if (type == null) { |
| return createUnknown(typeName); |
| } else { |
| return type; |
| } |
| } |
| return super.getEObject(uri, loadOnDemand); |
| } |
| |
| protected Type resolveTypeProxy(String typeName) { |
| return getType(typeName, null, true, false, false, false); |
| } |
| |
| public synchronized Resource getResource() { |
| if (typesResource == null) { |
| typesResource = new ResourceImpl( |
| TypeUtil.createProxyResourceURI()); |
| getResources().add(typesResource); |
| } |
| return typesResource; |
| } |
| |
| private Resource typesResource = null; |
| |
| public void addToResource(final Type type) { |
| final EObject object = type; |
| if (object.eResource() == null) { |
| add(type); |
| } |
| } |
| |
| protected synchronized void add(Type type) { |
| getResource().getContents().add(type); |
| } |
| |
| protected synchronized void removeAll(Collection<Type> types) { |
| getResource().getContents().removeAll(types); |
| } |
| |
| } |
| |
| private final TypeResourceSet typeRS = new TypeResourceSet(); |
| |
| private boolean resolve = true; |
| |
| private Map<String, IRMember> elements = new HashMap<String, IRMember>(); |
| |
| public IRMember resolve(String name) { |
| if (name == null) |
| return null; |
| { |
| final IRMember element = elements.get(name); |
| if (element != null) { |
| return element; |
| } |
| } |
| Member element = TypeInfoModelLoader.getInstance().getMember(name); |
| if (element != null) { |
| final IRMember r = convertMember(element, null); |
| elements.put(name, r); |
| return r; |
| } |
| if (resolve) { |
| for (IElementResolver resolver : TypeInfoManager |
| .getElementResolvers()) { |
| element = resolver.resolveElement(this, name); |
| if (element != null) { |
| final IRMember r = convertMember(element, null); |
| elements.put(name, r); |
| return r; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public IValue valueOf(IRMember member) { |
| if (!(member.getSource() instanceof Member)) { |
| return null; |
| } |
| final Member source = (Member) member.getSource(); |
| for (IMemberEvaluator evaluator : TypeInfoManager.getMemberEvaluators()) { |
| final IValueCollection collection = evaluator.valueOf(this, source); |
| if (collection != null) { |
| if (collection instanceof IValueProvider) { |
| IValue value = ((IValueProvider) collection).getValue(); |
| if (member.getType() != null) { |
| value.setDeclaredType(member.getType()); |
| } |
| if (value.getKind() == ReferenceKind.UNKNOWN) { |
| if (source instanceof Property) { |
| value.setKind(ReferenceKind.PROPERTY); |
| } else { |
| value.setKind(ReferenceKind.METHOD); |
| } |
| } |
| if (value instanceof ImmutableValue) { |
| ElementValue elementValue = ElementValue |
| .createFor(member); |
| return new ValueWithElementValue( |
| (ImmutableValue) value, elementValue); |
| } |
| return value; |
| } else { |
| break; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public Set<String> listGlobals(String prefix) { |
| final Set<String> result = new HashSet<String>(); |
| for (Member member : TypeInfoModelLoader.getInstance().listMembers( |
| prefix)) { |
| result.add(member.getName()); |
| } |
| for (IElementResolver resolver : TypeInfoManager.getElementResolvers()) { |
| Set<String> globals = resolver.listGlobals(this, prefix); |
| if (globals != null) { |
| result.addAll(globals); |
| } |
| } |
| return result; |
| } |
| |
| public void setDoResolve(boolean resolve) { |
| this.resolve = resolve; |
| } |
| |
| private IModelBuilder[] modelBuilders = null; |
| |
| public IModelBuilder[] getModelBuilders() { |
| if (modelBuilders == null) { |
| modelBuilders = TypeInfoManager.getModelBuilders(this); |
| } |
| return modelBuilders; |
| } |
| |
| private final Map<AttributeKey<?>, List<Object>> attributes = new HashMap<AttributeKey<?>, List<Object>>(); |
| |
| @SuppressWarnings("unchecked") |
| public <T> T getAttribute(AttributeKey<T> key) { |
| final List<Object> values = attributes.get(key); |
| return values != null && !values.isEmpty() ? (T) values.get(values |
| .size() - 1) : null; |
| } |
| |
| public <T> void pushAttribute(AttributeKey<T> key, T value) { |
| List<Object> values = attributes.get(key); |
| if (values == null) { |
| values = new ArrayList<Object>(4); |
| attributes.put(key, values); |
| } |
| values.add(value); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public <T> T popAttribute(AttributeKey<T> key) { |
| final List<Object> values = attributes.get(key); |
| if (values != null && !values.isEmpty()) { |
| return (T) values.remove(values.size() - 1); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| final TypeInferencerVisitor v = visitor; |
| String className = getClass().getSimpleName(); |
| if (className.length() == 0) { |
| className = getClass().getName(); |
| } |
| return className + "(" + source + "," |
| + (v != null ? v.getClass().getSimpleName() : null) + ")"; |
| } |
| |
| public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) { |
| if (adapter == ITypeInfoContext.class || adapter == ITypeSystem.class) { |
| return this; |
| } else if (adapter == ReferenceSource.class) { |
| return getSource(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Removes all registered local types, this method is called by the |
| * {@link org.eclipse.dltk.internal.javascript.validation.TypeInfoValidator} |
| * after each module is processed. |
| */ |
| public void resetLocalState() { |
| final List<Type> copy; |
| synchronized (localTypes) { |
| copy = new ArrayList<Type>(); |
| for (LocalTypeBucket locals : localTypes.values()) { |
| for (LocalType localType : locals) { |
| copy.add(localType.type); |
| } |
| if (locals.unknownType != null) { |
| copy.add(locals.unknownType); |
| } |
| } |
| localTypes.clear(); |
| } |
| if (!copy.isEmpty()) { |
| typeRS.removeAll(copy); |
| } |
| attributes.clear(); |
| } |
| |
| /* |
| * @see ITypeInfoContext#contextualize(JSType) |
| */ |
| public IRType contextualize(JSType type) { |
| if (type != null) { |
| final IRType rt = RTypes.create(this, type); |
| final IRTypeDeclaration contextTypeDeclaration = getAttribute(CONTEXTUALIZE_WITH); |
| if (contextTypeDeclaration != null && isContextualizable(rt)) { |
| final IRTypeTransformer transformer = newTypeContextualizer(contextTypeDeclaration); |
| return transformer.transform(rt); |
| } |
| return rt; |
| } else { |
| return null; |
| } |
| } |
| |
| private Map<String, Object> recordTypes = new HashMap<String, Object>(); |
| |
| public void registerRecordType(RecordType type) { |
| recordTypes.put(type.getName(), type); |
| } |
| |
| public IRRecordType resolveRecordType(String name) { |
| Object recordType = recordTypes.get(name); |
| if (recordType instanceof IRRecordType) |
| return (IRRecordType) recordType; |
| if (recordType instanceof RecordType) { |
| IRRecordType irType = RTypes.recordType(); |
| recordTypes.put(name, irType); |
| irType.init(this, ((RecordType) recordType).getMembers()); |
| return irType; |
| } |
| return null; |
| } |
| |
| public IRLocalType resolveLocalType(String name) { |
| if (visitor == null) |
| return null; |
| IValueReference result = null; |
| IValueCollection currentCollection = currentCollection(); |
| if (name.indexOf('.') != -1) { |
| String[] scopes = name.split("\\."); |
| IValueParent child = currentCollection; |
| for (String scope : scopes) { |
| child = child.getChild(scope); |
| } |
| result = (IValueReference) child; |
| } else { |
| while (currentCollection != null) { |
| IValueReference child = currentCollection.getChild(name); |
| if (child.exists()) { |
| result = child; |
| break; |
| } else { |
| currentCollection = currentCollection.getParent(); |
| } |
| |
| } |
| } |
| if (result != null && result.getKind() == ReferenceKind.FUNCTION) { |
| return RTypes.localType(name, result); |
| } |
| return null; |
| } |
| } |