blob: a4df93b70873eb4bde9d0ecf5951b54a2146a467 [file] [log] [blame]
// Copyright 2017 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.packages;
import static com.google.common.base.MoreObjects.firstNonNull;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Ordering;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import java.util.List;
import javax.annotation.Nullable;
import net.starlark.java.eval.BuiltinFunction;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Mutability;
import net.starlark.java.eval.Printer;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkSemantics;
import net.starlark.java.syntax.Location;
/**
* Abstract base class for implementations of {@link Info} that expose StarlarkCallable-annotated
* fields (not just methods) to Starlark code. Subclasses must be immutable.
*/
// TODO(adonovan): ensure that all subclasses are named *Info and not *Provider.
// (Info is to object as Provider is to class.)
@Immutable
public abstract class NativeInfo implements Info {
private final Location location;
protected NativeInfo() {
this(Location.BUILTIN);
}
// TODO(adonovan): most subclasses pass Location.BUILTIN most of the time.
// Make only those classes that pass a real location pay for it.
protected NativeInfo(@Nullable Location location) {
this.location = firstNonNull(location, Location.BUILTIN);
}
@Override
public final Location getCreationLocation() {
return location;
}
@Override
public boolean isImmutable() {
return true; // immutable and Starlark-hashable
}
// TODO(b/408391489) repr, hash, equals for native providers are inefficient; implement them
// directly and remove getLegacyStarlarkMethodNames getLegacyFields
private List<String> getLegacyStarlarkMethodNames() {
return Ordering.natural()
.sortedCopy(Starlark.dir(Mutability.IMMUTABLE, StarlarkSemantics.DEFAULT, this));
}
private ImmutableMap<String, Object> getLegacyFields() {
ImmutableMap.Builder<String, Object> fields = ImmutableMap.builder();
for (String fieldName : getLegacyStarlarkMethodNames()) {
try {
Object value =
Starlark.getattr(
Mutability.IMMUTABLE, StarlarkSemantics.DEFAULT, this, fieldName, null);
if (value instanceof BuiltinFunction) {
continue;
}
fields.put(fieldName, value);
} catch (EvalException e) {
fields.put(fieldName, Starlark.NONE);
} catch (InterruptedException e) {
// Struct fields on NativeInfo objects are supposed to behave well and not throw
// exceptions, as they should be logicless field accessors. If this occurs, it's
// indicative of a bad NativeInfo implementation.
throw new IllegalStateException(
String.format(
"Access of field %s was unexpectedly interrupted, but should be "
+ "uninterruptible. This is indicative of a bad provider implementation.",
fieldName),
e);
}
}
return fields.buildOrThrow();
}
@Override
public boolean equals(Object otherObject) {
if (!(otherObject instanceof NativeInfo other)) {
return false;
}
if (this == other) {
return true;
}
if (!this.getProvider().equals(other.getProvider())) {
return false;
}
// Compare objects' fields and their values
if (!Objects.equal(getLegacyStarlarkMethodNames(), other.getLegacyStarlarkMethodNames())) {
return false;
}
return Objects.equal(getLegacyFields(), other.getLegacyFields());
}
@Override
public int hashCode() {
return Objects.hashCode(getProvider(), getLegacyFields());
}
/**
* Convert the object to string using Starlark syntax. The output tries to be reversible (but
* there is no guarantee, it depends on the actual values).
*/
@Override
public void repr(Printer printer) {
boolean first = true;
printer.append("struct(");
for (var field : getLegacyFields().entrySet()) {
if (!first) {
printer.append(", ");
}
first = false;
printer.append(field.getKey());
printer.append(" = ");
printer.repr(field.getValue());
}
printer.append(")");
}
@Override
public String toString() {
return Starlark.repr(this);
}
}