/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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 org.apache.flex.compiler.internal.definitions; import org.apache.flex.abc.ABCConstants; import org.apache.flex.compiler.constants.IASKeywordConstants; import org.apache.flex.compiler.definitions.IClassDefinition; import org.apache.flex.compiler.definitions.IConstantDefinition; import org.apache.flex.compiler.internal.projects.CompilerProject; import org.apache.flex.compiler.internal.scopes.ASScope; import org.apache.flex.compiler.internal.scopes.ScopeView; import org.apache.flex.compiler.internal.tree.as.NodeBase; import org.apache.flex.compiler.projects.ICompilerProject; import org.apache.flex.compiler.tree.as.IExpressionNode; public class ConstantDefinition extends VariableDefinition implements IConstantDefinition { public ConstantDefinition(String name) { super(name); } /** * Construct a ConstantDefinition with a constant value - for use when * building defs from an ABC. * * @param name the name of the definition * @param value the value of the constant */ public ConstantDefinition(String name, Object value) { super(name, value); } /** * For debugging only. Produces a string such as * <code>public const N:int</code>. */ @Override public void buildInnerString(StringBuilder sb) { sb.append(getNamespaceReferenceAsString()); sb.append(' '); sb.append(IASKeywordConstants.CONST); sb.append(' '); sb.append(getBaseName()); String type = getTypeAsDisplayString(); if (!type.isEmpty()) { sb.append(':'); sb.append(type); } } @Override public Object resolveValue(ICompilerProject project) { // Fail fast if we statically know we won't have a value // constants in control flow always fail to constant eval if( declaredInControlFlow() ) return null; // Fastest way out for Constants that came from ABCs if (initValue != null) return resolveUndefined(project, initValue); // TODO: possible optimizations // 1. cache result on a per-project basis (ASScopeCache) // 2. Copy the init expr out of the original tree, so we don't have to // reparse the whole file if the AST is collected. return ((CompilerProject)project).getCacheForScope(getContainingASScope()).getConstantValue(this); } /** * Try to calculate the constant value for this Constant Definition when * referenced from the passed in Node. If the passed in node is a Forward * reference to this ConstantDefinition then we will return null, if it is * not a forward reference this method will return the same as resolveValue * above. * * @param project project to use to resolve the initializer * @param fromNode the node that is referencing this constant * @return the constant value of this definition, or null if one can't be * determined, or if this is a forward reference, as determined by the * fromNode. */ public Object resolveValueFrom(ICompilerProject project, NodeBase fromNode) { // Fail fast if we statically know we won't have a value // constants in control flow always fail to constant eval if(declaredInControlFlow()) return null; // fast path for values from ABC if (initValue != null) return resolveUndefined(project, initValue); if (fromNode != null && fromNode.getFileScope() == this.getFileScope()) { // Declared in the same file, figure out if we're a forward reference. if (fromNode.getAbsoluteStart() <= this.getAbsoluteStart()) { // We can reference a static property from an instance context even if the // static property occurs later in the file - this is because we know the // static initializer will have run by the time we get to the instance if( !isReferenceToStaticFromInstanceScope(fromNode) ) return null; } // Not a forward reference, but are we referencing ourselves from our own initializer? else { IExpressionNode initNode = this.getInitExpression(); if (initNode != null) { // We are looking up ourselves from inside our own initializer // contains returns false if the start positions match, so just check that here if (initNode.getAbsoluteStart() == fromNode.getAbsoluteStart() || initNode.contains(fromNode.getAbsoluteStart())) return null; } } } return resolveValue(project); } public Object resolveValueImpl(ICompilerProject project) { // Fail fast if we statically know we won't have a value // constants in control flow always fail to constant eval if( declaredInControlFlow() ) return null; Object value = super.resolveInitialValue(project); value = resolveUndefined(project, value); return value; } /** * This mimics ASCs strange behavior with UNDEFINED constants. This is due * to the ABC file not actually having enough information to differentiate * btwn no initializer, and "undefined" was the initializer */ private Object resolveUndefined(ICompilerProject project, Object value) { if (value == ABCConstants.UNDEFINED_VALUE && resolveType(project) != ClassDefinition.getAnyTypeClassDefinition()) { // If we are a type that can't hold undefined, then return null // so that this constant won't participate in constant folding. // This is because the ABC format does not differentiate between // no initializer, and the intializer was the "undefined" value. // In old-ASC there were bugs that made it appear to work this way most of the time, // so we're replicating that here. value = null; } return value; } /** * Determine if the reference from 'fromNode' is a reference to a static property from an instance * context of the same class. If it is, then we can allow the forward reference. * @param fromNode the Node making the reference * @return true if 'fromNode' is from an instance context, and this definition is a static property * of the same class */ private boolean isReferenceToStaticFromInstanceScope (NodeBase fromNode) { // Only have to check if this Definition is static if( this.isStatic() ) { // Get the class this definition is in IClassDefinition containingClass = (IClassDefinition)this.getAncestorOfType(IClassDefinition.class); // Grab the scope the fromNode uses to resolve itself, and walk // up the containing scopes looking for an instance scope ASScope fromScope = fromNode.getASScope(); while( fromScope != null ) { if( fromScope instanceof ScopeView ) { // return true if we hit an instance scope, and it's for the same // class as this definition is in return ((ScopeView)fromScope).isInstanceScope() && fromScope.getDefinition() == containingClass; } fromScope = fromScope.getContainingScope(); } } return false; } }