// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed 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 com.google.devtools.build.lib.syntax; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.syntax.FuncallExpression.MethodDescriptor; import com.google.devtools.build.lib.syntax.compiler.ByteCodeUtils; import com.google.devtools.build.lib.syntax.compiler.DebugInfo; import com.google.devtools.build.lib.syntax.compiler.VariableScope; import java.util.ArrayList; import java.util.List; import net.bytebuddy.implementation.bytecode.ByteCodeAppender; import net.bytebuddy.implementation.bytecode.Duplication; import net.bytebuddy.implementation.bytecode.constant.TextConstant; /** Syntax node for a dot expression. e.g. obj.field, but not obj.method() */ public final class DotExpression extends Expression { private final Expression obj; private final Identifier field; public DotExpression(Expression obj, Identifier field) { this.obj = obj; this.field = field; } public Expression getObj() { return obj; } public Identifier getField() { return field; } @Override public String toString() { return obj + "." + field; } @Override Object doEval(Environment env) throws EvalException, InterruptedException { Object objValue = obj.eval(env); String name = field.getName(); Object result = eval(objValue, name, getLocation(), env); return checkResult(objValue, result, name, getLocation()); } /** * Throws the correct error message if the result is null depending on the objValue. */ public static Object checkResult(Object objValue, Object result, String name, Location loc) throws EvalException { if (result == null) { if (objValue instanceof ClassObject) { String customErrorMessage = ((ClassObject) objValue).errorMessage(name); if (customErrorMessage != null) { throw new EvalException(loc, customErrorMessage); } } throw new EvalException( loc, Printer.format( "Object of type '%s' has no field %r", EvalUtils.getDataTypeName(objValue), name)); } return result; } /** * Returns the field of the given name of the struct objValue, or null if no such field exists. */ public static Object eval(Object objValue, String name, Location loc, Environment env) throws EvalException { if (objValue instanceof ClassObject) { Object result = null; try { result = ((ClassObject) objValue).getValue(name); } catch (IllegalArgumentException ex) { throw new EvalException(loc, ex); } // ClassObjects may have fields that are annotated with @SkylarkCallable. // Since getValue() does not know about those, we cannot expect that result is a valid object. if (result != null) { result = SkylarkType.convertToSkylark(result, env); // If we access NestedSets using ClassObject.getValue() we won't know the generic type, // so we have to disable it. This should not happen. SkylarkType.checkTypeAllowedInSkylark(result, loc); return result; } } Iterable methods = objValue instanceof Class ? FuncallExpression.getMethods((Class) objValue, name, loc) : FuncallExpression.getMethods(objValue.getClass(), name, loc); if (methods != null) { methods = Iterables.filter( methods, new Predicate() { @Override public boolean apply(MethodDescriptor methodDescriptor) { return methodDescriptor.getAnnotation().structField(); } }); if (methods.iterator().hasNext()) { MethodDescriptor method = Iterables.getOnlyElement(methods); if (method.getAnnotation().structField()) { return FuncallExpression.callMethod(method, name, objValue, new Object[] {}, loc, env); } } } return null; } @Override public void accept(SyntaxTreeVisitor visitor) { visitor.visit(this); } @Override void validate(ValidationEnvironment env) throws EvalException { obj.validate(env); } @Override ByteCodeAppender compile(VariableScope scope, DebugInfo debugInfo) throws EvalException { List code = new ArrayList<>(); code.add(obj.compile(scope, debugInfo)); TextConstant name = new TextConstant(field.getName()); ByteCodeUtils.append( code, Duplication.SINGLE, name, debugInfo.add(this).loadLocation, scope.loadEnvironment(), ByteCodeUtils.invoke( DotExpression.class, "eval", Object.class, String.class, Location.class, Environment.class), // at this point we have the value of obj and the result of eval on the stack name, debugInfo.add(this).loadLocation, ByteCodeUtils.invoke( DotExpression.class, "checkResult", Object.class, Object.class, String.class, Location.class)); return ByteCodeUtils.compoundAppender(code); } }