Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | package com.google.devtools.build.lib.syntax; |
| 15 | |
laurentlb | d698367 | 2017-06-29 14:53:12 +0200 | [diff] [blame] | 16 | import com.google.common.collect.Streams; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 17 | import com.google.devtools.build.lib.events.Location; |
| 18 | import com.google.devtools.build.lib.syntax.FuncallExpression.MethodDescriptor; |
Laurent Le Brun | 57badf4 | 2017-01-02 15:12:24 +0000 | [diff] [blame] | 19 | import com.google.devtools.build.lib.util.SpellChecker; |
brandjon | e2ffd5d | 2017-06-27 18:14:54 +0200 | [diff] [blame] | 20 | import java.io.IOException; |
laurentlb | d698367 | 2017-06-29 14:53:12 +0200 | [diff] [blame] | 21 | import java.util.Optional; |
Florian Weikert | a9dd72a | 2015-11-09 14:09:18 +0000 | [diff] [blame] | 22 | |
Damien Martin-Guillerez | 2d32c58 | 2016-08-04 14:29:18 +0000 | [diff] [blame] | 23 | /** Syntax node for a dot expression. e.g. obj.field, but not obj.method() */ |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 24 | public final class DotExpression extends Expression { |
| 25 | |
brandjon | 990622b | 2017-07-11 19:56:45 +0200 | [diff] [blame] | 26 | private final Expression object; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 27 | |
Florian Weikert | 6f864c3 | 2015-07-23 11:26:39 +0000 | [diff] [blame] | 28 | private final Identifier field; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 29 | |
brandjon | 990622b | 2017-07-11 19:56:45 +0200 | [diff] [blame] | 30 | public DotExpression(Expression object, Identifier field) { |
| 31 | this.object = object; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 32 | this.field = field; |
| 33 | } |
| 34 | |
brandjon | 990622b | 2017-07-11 19:56:45 +0200 | [diff] [blame] | 35 | public Expression getObject() { |
| 36 | return object; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 37 | } |
| 38 | |
Florian Weikert | 6f864c3 | 2015-07-23 11:26:39 +0000 | [diff] [blame] | 39 | public Identifier getField() { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 40 | return field; |
| 41 | } |
| 42 | |
| 43 | @Override |
brandjon | e2ffd5d | 2017-06-27 18:14:54 +0200 | [diff] [blame] | 44 | public void prettyPrint(Appendable buffer) throws IOException { |
brandjon | 990622b | 2017-07-11 19:56:45 +0200 | [diff] [blame] | 45 | object.prettyPrint(buffer); |
brandjon | e2ffd5d | 2017-06-27 18:14:54 +0200 | [diff] [blame] | 46 | buffer.append('.'); |
| 47 | field.prettyPrint(buffer); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 48 | } |
| 49 | |
| 50 | @Override |
Florian Weikert | 90a1596 | 2015-09-11 13:43:10 +0000 | [diff] [blame] | 51 | Object doEval(Environment env) throws EvalException, InterruptedException { |
brandjon | 990622b | 2017-07-11 19:56:45 +0200 | [diff] [blame] | 52 | Object objValue = object.eval(env); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 53 | String name = field.getName(); |
Francois-Rene Rideau | 4e99410 | 2015-09-17 22:41:28 +0000 | [diff] [blame] | 54 | Object result = eval(objValue, name, getLocation(), env); |
Florian Weikert | a9dd72a | 2015-11-09 14:09:18 +0000 | [diff] [blame] | 55 | return checkResult(objValue, result, name, getLocation()); |
| 56 | } |
| 57 | |
| 58 | /** |
| 59 | * Throws the correct error message if the result is null depending on the objValue. |
| 60 | */ |
| 61 | public static Object checkResult(Object objValue, Object result, String name, Location loc) |
Florian Weikert | 6edbf3b | 2015-11-09 21:33:26 +0000 | [diff] [blame] | 62 | throws EvalException { |
Laurent Le Brun | 57badf4 | 2017-01-02 15:12:24 +0000 | [diff] [blame] | 63 | if (result != null) { |
| 64 | return result; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 65 | } |
Googler | 055d6c6 | 2018-05-22 13:21:04 -0700 | [diff] [blame^] | 66 | throw getMissingFieldException(objValue, name, loc, "field"); |
| 67 | } |
| 68 | |
| 69 | static EvalException getMissingFieldException( |
| 70 | Object objValue, String name, Location loc, String accessName) { |
Laurent Le Brun | 57badf4 | 2017-01-02 15:12:24 +0000 | [diff] [blame] | 71 | String suffix = ""; |
Googler | 055d6c6 | 2018-05-22 13:21:04 -0700 | [diff] [blame^] | 72 | EvalException toSuppress = null; |
Laurent Le Brun | 57badf4 | 2017-01-02 15:12:24 +0000 | [diff] [blame] | 73 | if (objValue instanceof ClassObject) { |
brandjon | d331fa7 | 2017-12-28 07:38:31 -0800 | [diff] [blame] | 74 | String customErrorMessage = ((ClassObject) objValue).getErrorMessageForUnknownField(name); |
Laurent Le Brun | 57badf4 | 2017-01-02 15:12:24 +0000 | [diff] [blame] | 75 | if (customErrorMessage != null) { |
Googler | 055d6c6 | 2018-05-22 13:21:04 -0700 | [diff] [blame^] | 76 | return new EvalException(loc, customErrorMessage); |
Laurent Le Brun | 57badf4 | 2017-01-02 15:12:24 +0000 | [diff] [blame] | 77 | } |
Googler | 055d6c6 | 2018-05-22 13:21:04 -0700 | [diff] [blame^] | 78 | try { |
| 79 | suffix = SpellChecker.didYouMean(name, ((ClassObject) objValue).getFieldNames()); |
| 80 | } catch (EvalException ee) { |
| 81 | toSuppress = ee; |
| 82 | } |
| 83 | } else { |
| 84 | suffix = |
| 85 | SpellChecker.didYouMean( |
| 86 | name, |
| 87 | FuncallExpression.getStructFieldNames( |
| 88 | objValue instanceof Class ? (Class<?>) objValue : objValue.getClass())); |
Laurent Le Brun | 57badf4 | 2017-01-02 15:12:24 +0000 | [diff] [blame] | 89 | } |
Googler | 055d6c6 | 2018-05-22 13:21:04 -0700 | [diff] [blame^] | 90 | if (suffix.isEmpty() && hasMethod(objValue, name)) { |
| 91 | // If looking up the field failed, then we know that this method must have struct_field=false |
| 92 | suffix = ", however, a method of that name exists"; |
| 93 | } |
| 94 | EvalException ee = |
| 95 | new EvalException( |
| 96 | loc, |
| 97 | String.format( |
| 98 | "object of type '%s' has no %s '%s'%s", |
| 99 | EvalUtils.getDataTypeName(objValue), accessName, name, suffix)); |
| 100 | if (toSuppress != null) { |
| 101 | ee.addSuppressed(toSuppress); |
| 102 | } |
| 103 | return ee; |
| 104 | } |
| 105 | |
| 106 | /** Returns whether the given object has a method with the given name. */ |
| 107 | static boolean hasMethod(Object obj, String name) { |
| 108 | Class<?> cls = obj instanceof Class ? (Class<?>) obj : obj.getClass(); |
| 109 | if (Runtime.getBuiltinRegistry().getFunctionNames(cls).contains(name)) { |
| 110 | return true; |
| 111 | } |
| 112 | |
| 113 | return FuncallExpression.getMethodNames(cls).contains(name); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Returns the field of the given name of the struct objValue, or null if no such field exists. |
| 118 | */ |
Francois-Rene Rideau | 4e99410 | 2015-09-17 22:41:28 +0000 | [diff] [blame] | 119 | public static Object eval(Object objValue, String name, |
cparsons | 4803293 | 2018-04-18 09:39:02 -0700 | [diff] [blame] | 120 | Location loc, Environment env) throws EvalException, InterruptedException { |
nharmata | d223a2e | 2018-03-08 12:45:49 -0800 | [diff] [blame] | 121 | if (objValue instanceof SkylarkClassObject) { |
| 122 | try { |
| 123 | return ((SkylarkClassObject) objValue).getValue(name); |
| 124 | } catch (IllegalArgumentException ex) { |
| 125 | throw new EvalException(loc, ex); |
| 126 | } |
| 127 | } else if (objValue instanceof ClassObject) { |
Florian Weikert | 3f610e8 | 2015-08-18 14:37:46 +0000 | [diff] [blame] | 128 | Object result = null; |
| 129 | try { |
| 130 | result = ((ClassObject) objValue).getValue(name); |
| 131 | } catch (IllegalArgumentException ex) { |
| 132 | throw new EvalException(loc, ex); |
| 133 | } |
Florian Weikert | eee8be6 | 2015-07-30 13:28:26 +0000 | [diff] [blame] | 134 | // ClassObjects may have fields that are annotated with @SkylarkCallable. |
| 135 | // Since getValue() does not know about those, we cannot expect that result is a valid object. |
| 136 | if (result != null) { |
Francois-Rene Rideau | 4e99410 | 2015-09-17 22:41:28 +0000 | [diff] [blame] | 137 | result = SkylarkType.convertToSkylark(result, env); |
Florian Weikert | eee8be6 | 2015-07-30 13:28:26 +0000 | [diff] [blame] | 138 | // If we access NestedSets using ClassObject.getValue() we won't know the generic type, |
| 139 | // so we have to disable it. This should not happen. |
| 140 | SkylarkType.checkTypeAllowedInSkylark(result, loc); |
| 141 | return result; |
| 142 | } |
Laurent Le Brun | 427bd97 | 2015-05-20 13:28:44 +0000 | [diff] [blame] | 143 | } |
Florian Weikert | eee8be6 | 2015-07-30 13:28:26 +0000 | [diff] [blame] | 144 | |
Laurent Le Brun | 57badf4 | 2017-01-02 15:12:24 +0000 | [diff] [blame] | 145 | Iterable<MethodDescriptor> methods = |
| 146 | objValue instanceof Class<?> |
| 147 | ? FuncallExpression.getMethods((Class<?>) objValue, name) |
| 148 | : FuncallExpression.getMethods(objValue.getClass(), name); |
Damien Martin-Guillerez | 2d32c58 | 2016-08-04 14:29:18 +0000 | [diff] [blame] | 149 | |
| 150 | if (methods != null) { |
laurentlb | d698367 | 2017-06-29 14:53:12 +0200 | [diff] [blame] | 151 | Optional<MethodDescriptor> method = |
| 152 | Streams.stream(methods) |
| 153 | .filter(methodDescriptor -> methodDescriptor.getAnnotation().structField()) |
| 154 | .findFirst(); |
| 155 | if (method.isPresent() && method.get().getAnnotation().structField()) { |
| 156 | return FuncallExpression.callMethod( |
| 157 | method.get(), name, objValue, new Object[] {}, loc, env); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 158 | } |
| 159 | } |
Florian Weikert | a9dd72a | 2015-11-09 14:09:18 +0000 | [diff] [blame] | 160 | |
Laurent Le Brun | 427bd97 | 2015-05-20 13:28:44 +0000 | [diff] [blame] | 161 | return null; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 162 | } |
| 163 | |
| 164 | @Override |
| 165 | public void accept(SyntaxTreeVisitor visitor) { |
| 166 | visitor.visit(this); |
| 167 | } |
laurentlb | af682d1 | 2017-08-24 20:32:02 +0200 | [diff] [blame] | 168 | |
| 169 | @Override |
| 170 | public Kind kind() { |
| 171 | return Kind.DOT; |
| 172 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 173 | } |