/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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 com.google.dart.engine.internal.scope;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.Identifier;
import com.google.dart.engine.element.CompilationUnitElement;
import com.google.dart.engine.element.Element;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.element.MethodElement;
import com.google.dart.engine.error.AnalysisError;
import com.google.dart.engine.error.AnalysisErrorListener;
import com.google.dart.engine.error.CompileTimeErrorCode;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.utilities.general.StringUtilities;
import java.util.HashMap;
/**
* The abstract class {@code Scope} defines the behavior common to name scopes used by the resolver
* to determine which names are visible at any given point in the code.
*
* @coverage dart.engine.resolver
*/
public abstract class Scope {
/**
* The prefix used to mark an identifier as being private to its library.
*/
public static final int PRIVATE_NAME_PREFIX = '_';
/**
* The suffix added to the declared name of a setter when looking up the setter. Used to
* disambiguate between a getter and a setter that have the same name.
*/
public static final String SETTER_SUFFIX = "="; //$NON-NLS-1$
/**
* The name used to look up the method used to implement the unary minus operator. Used to
* disambiguate between the unary and binary operators.
*/
public static final String UNARY_MINUS = "unary-"; //$NON-NLS-1$
/**
* Return {@code true} if the given name is a library-private name.
*
* @param name the name being tested
* @return {@code true} if the given name is a library-private name
*/
public static boolean isPrivateName(String name) {
return name != null && StringUtilities.startsWithChar(name, PRIVATE_NAME_PREFIX);
}
/**
* A table mapping names that are defined in this scope to the element representing the thing
* declared with that name.
*/
private HashMap<String, Element> definedNames = new HashMap<String, Element>();
/**
* A flag indicating whether there are any names defined in this scope.
*/
private boolean hasName = false;
/**
* Initialize a newly created scope to be empty.
*/
public Scope() {
super();
}
/**
* Add the given element to this scope. If there is already an element with the given name defined
* in this scope, then an error will be generated and the original element will continue to be
* mapped to the name. If there is an element with the given name in an enclosing scope, then a
* warning will be generated but the given element will hide the inherited element.
*
* @param element the element to be added to this scope
*/
public void define(Element element) {
String name = getName(element);
if (name != null && !name.isEmpty()) {
if (definedNames.containsKey(name)) {
getErrorListener().onError(getErrorForDuplicate(definedNames.get(name), element));
} else {
definedNames.put(name, element);
hasName = true;
}
}
}
/**
* Return the scope in which this scope is lexically enclosed.
*
* @return the scope in which this scope is lexically enclosed
*/
public Scope getEnclosingScope() {
return null;
}
/**
* Return the element with which the given identifier is associated, or {@code null} if the name
* is not defined within this scope.
*
* @param identifier the identifier associated with the element to be returned
* @param referencingLibrary the library that contains the reference to the name, used to
* implement library-level privacy
* @return the element with which the given identifier is associated
*/
public Element lookup(Identifier identifier, LibraryElement referencingLibrary) {
return internalLookup(identifier, identifier.getName(), referencingLibrary);
}
/**
* Add the given element to this scope without checking for duplication or hiding.
*
* @param name the name of the element to be added
* @param element the element to be added to this scope
*/
protected void defineNameWithoutChecking(String name, Element element) {
definedNames.put(name, element);
hasName = true;
}
/**
* Add the given element to this scope without checking for duplication or hiding.
*
* @param element the element to be added to this scope
*/
protected void defineWithoutChecking(Element element) {
definedNames.put(getName(element), element);
hasName = true;
}
/**
* Return the error code to be used when reporting that a name being defined locally conflicts
* with another element of the same name in the local scope.
*
* @param existing the first element to be declared with the conflicting name
* @param duplicate another element declared with the conflicting name
* @return the error code used to report duplicate names within a scope
*/
protected AnalysisError getErrorForDuplicate(Element existing, Element duplicate) {
// TODO(brianwilkerson) Customize the error message based on the types of elements that share
// the same name.
// TODO(jwren) There are 4 error codes for duplicate, but only 1 is being generated.
Source source = duplicate.getSource();
return new AnalysisError(
source,
duplicate.getNameOffset(),
duplicate.getDisplayName().length(),
CompileTimeErrorCode.DUPLICATE_DEFINITION,
existing.getDisplayName());
}
/**
* Return the listener that is to be informed when an error is encountered.
*
* @return the listener that is to be informed when an error is encountered
*/
protected abstract AnalysisErrorListener getErrorListener();
/**
* Return the source that contains the given identifier, or the source associated with this scope
* if the source containing the identifier could not be determined.
*
* @param identifier the identifier whose source is to be returned
* @return the source that contains the given identifier
*/
protected final Source getSource(AstNode node) {
CompilationUnit unit = node.getAncestor(CompilationUnit.class);
if (unit != null) {
CompilationUnitElement unitElement = unit.getElement();
if (unitElement != null) {
return unitElement.getSource();
}
}
return null;
}
/**
* Return the element with which the given name is associated, or {@code null} if the name is not
* defined within this scope.
*
* @param identifier the identifier node to lookup element for, used to report correct kind of a
* problem and associate problem with
* @param name the name associated with the element to be returned
* @param referencingLibrary the library that contains the reference to the name, used to
* implement library-level privacy
* @return the element with which the given name is associated
*/
protected abstract Element internalLookup(Identifier identifier, String name,
LibraryElement referencingLibrary);
/**
* Return the element with which the given name is associated, or {@code null} if the name is not
* defined within this scope. This method only returns elements that are directly defined within
* this scope, not elements that are defined in an enclosing scope.
*
* @param name the name associated with the element to be returned
* @param referencingLibrary the library that contains the reference to the name, used to
* implement library-level privacy
* @return the element with which the given name is associated
*/
protected Element localLookup(String name, LibraryElement referencingLibrary) {
if (hasName) {
return definedNames.get(name);
}
return null;
}
/**
* Return the name that will be used to look up the given element.
*
* @param element the element whose look-up name is to be returned
* @return the name that will be used to look up the given element
*/
private String getName(Element element) {
if (element instanceof MethodElement) {
MethodElement method = (MethodElement) element;
if (method.getName().equals("-") && method.getParameters().length == 0) {
return UNARY_MINUS;
}
}
return element.getName();
}
}