Skip to content

Commit 6200202

Browse files
Wyveraldcopybara-github
authored andcommitted
Bzlmod: Starlarkify default attr values for TypeCheckedTags
(#13316) Attribute values are stored normally as "native" values (ie. Java types), and we Starlarkify them before exposing them to Starlark. We were, however, not doing it for attribute default values, which means that the Java `null` was not being converted to Starlark `None`, and the Java `ImmutableMap` was not being converted to Starlark `Dict`, etc. Fixes #14528. PiperOrigin-RevId: 421046160
1 parent 15a602a commit 6200202

File tree

3 files changed

+140
-13
lines changed

3 files changed

+140
-13
lines changed

src/main/java/com/google/devtools/build/lib/bazel/bzlmod/TypeCheckedTag.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,18 @@ public static TypeCheckedTag create(
8181
attrValues[attrIndex] = Attribute.valueToStarlark(nativeValue);
8282
}
8383

84-
// Check that all mandatory attributes have been specified.
84+
// Check that all mandatory attributes have been specified, and fill in default values.
8585
for (int i = 0; i < attrValues.length; i++) {
86-
if (tagClass.getAttributes().get(i).isMandatory() && attrValues[i] == null) {
86+
Attribute attr = tagClass.getAttributes().get(i);
87+
if (attr.isMandatory() && attrValues[i] == null) {
8788
throw ExternalDepsException.withMessage(
8889
Code.BAD_MODULE,
8990
"in tag at %s, mandatory attribute %s isn't being specified",
9091
tag.getLocation(),
91-
tagClass.getAttributes().get(i).getPublicName());
92+
attr.getPublicName());
93+
}
94+
if (attrValues[i] == null) {
95+
attrValues[i] = Attribute.valueToStarlark(attr.getDefaultValueUnchecked());
9296
}
9397
}
9498
return new TypeCheckedTag(tagClass, attrValues);
@@ -106,11 +110,7 @@ public Object getValue(String name) throws EvalException {
106110
if (attrIndex == null) {
107111
return null;
108112
}
109-
Object value = attrValues[attrIndex];
110-
if (value != null) {
111-
return value;
112-
}
113-
return tagClass.getAttributes().get(attrIndex).getDefaultValueUnchecked();
113+
return attrValues[attrIndex];
114114
}
115115

116116
@Override

src/test/java/com/google/devtools/build/lib/bazel/bzlmod/ModuleExtensionResolutionTest.java

+78
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,84 @@ public void labels_constructedInModuleExtension() throws Exception {
720720
.isEqualTo("requirements: get up at 6am. go to bed at 11pm.");
721721
}
722722

723+
/** Tests that a complex-typed attribute (here, string_list_dict) behaves well on a tag. */
724+
@Test
725+
public void complexTypedAttribute() throws Exception {
726+
scratch.file(
727+
workspaceRoot.getRelative("MODULE.bazel").getPathString(),
728+
"bazel_dep(name='data_repo', version='1.0')",
729+
"ext = use_extension('//:defs.bzl', 'ext')",
730+
"ext.tag(data={'foo':['val1','val2'],'bar':['val3','val4']})",
731+
"use_repo(ext, 'foo', 'bar')");
732+
scratch.file(
733+
workspaceRoot.getRelative("defs.bzl").getPathString(),
734+
"load('@data_repo//:defs.bzl','data_repo')",
735+
"tag = tag_class(attrs = {'data':attr.string_list_dict()})",
736+
"def _ext_impl(ctx):",
737+
" for mod in ctx.modules:",
738+
" for tag in mod.tags.tag:",
739+
" for key in tag.data:",
740+
" data_repo(name=key,data=','.join(tag.data[key]))",
741+
"ext = module_extension(implementation=_ext_impl, tag_classes={'tag':tag})");
742+
scratch.file(workspaceRoot.getRelative("BUILD").getPathString());
743+
scratch.file(
744+
workspaceRoot.getRelative("data.bzl").getPathString(),
745+
"load('@foo//:data.bzl', foo_data='data')",
746+
"load('@bar//:data.bzl', bar_data='data')",
747+
"data = 'foo:'+foo_data+' bar:'+bar_data");
748+
749+
SkyKey skyKey = BzlLoadValue.keyForBuild(Label.parseAbsoluteUnchecked("//:data.bzl"));
750+
EvaluationResult<BzlLoadValue> result =
751+
evaluator.evaluate(ImmutableList.of(skyKey), evaluationContext);
752+
if (result.hasError()) {
753+
throw result.getError().getException();
754+
}
755+
assertThat(result.get(skyKey).getModule().getGlobal("data"))
756+
.isEqualTo("foo:val1,val2 bar:val3,val4");
757+
}
758+
759+
/**
760+
* Tests that a complex-typed attribute (here, string_list_dict) behaves well when it has a
761+
* default value and is omitted in a tag.
762+
*/
763+
@Test
764+
public void complexTypedAttribute_default() throws Exception {
765+
scratch.file(
766+
workspaceRoot.getRelative("MODULE.bazel").getPathString(),
767+
"bazel_dep(name='data_repo', version='1.0')",
768+
"ext = use_extension('//:defs.bzl', 'ext')",
769+
"ext.tag()",
770+
"use_repo(ext, 'foo', 'bar')");
771+
scratch.file(
772+
workspaceRoot.getRelative("defs.bzl").getPathString(),
773+
"load('@data_repo//:defs.bzl','data_repo')",
774+
"tag = tag_class(attrs = {",
775+
" 'data': attr.string_list_dict(",
776+
" default = {'foo':['val1','val2'],'bar':['val3','val4']},",
777+
")})",
778+
"def _ext_impl(ctx):",
779+
" for mod in ctx.modules:",
780+
" for tag in mod.tags.tag:",
781+
" for key in tag.data:",
782+
" data_repo(name=key,data=','.join(tag.data[key]))",
783+
"ext = module_extension(implementation=_ext_impl, tag_classes={'tag':tag})");
784+
scratch.file(workspaceRoot.getRelative("BUILD").getPathString());
785+
scratch.file(
786+
workspaceRoot.getRelative("data.bzl").getPathString(),
787+
"load('@foo//:data.bzl', foo_data='data')",
788+
"load('@bar//:data.bzl', bar_data='data')",
789+
"data = 'foo:'+foo_data+' bar:'+bar_data");
790+
791+
SkyKey skyKey = BzlLoadValue.keyForBuild(Label.parseAbsoluteUnchecked("//:data.bzl"));
792+
EvaluationResult<BzlLoadValue> result =
793+
evaluator.evaluate(ImmutableList.of(skyKey), evaluationContext);
794+
if (result.hasError()) {
795+
throw result.getError().getException();
796+
}
797+
assertThat(result.get(skyKey).getModule().getGlobal("data"))
798+
.isEqualTo("foo:val1,val2 bar:val3,val4");
799+
}
800+
723801
@Test
724802
public void generatedReposHaveCorrectMappings() throws Exception {
725803
scratch.file(

src/test/java/com/google/devtools/build/lib/bazel/bzlmod/TypeCheckedTagTest.java

+54-5
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,22 @@
2323
import static com.google.devtools.build.lib.packages.Attribute.attr;
2424
import static org.junit.Assert.assertThrows;
2525

26+
import com.google.common.collect.ImmutableList;
27+
import com.google.common.collect.ImmutableMap;
2628
import com.google.devtools.build.lib.cmdline.Label;
2729
import com.google.devtools.build.lib.packages.Attribute.AllowedValueSet;
2830
import com.google.devtools.build.lib.packages.BuildType;
2931
import com.google.devtools.build.lib.packages.BuildType.LabelConversionContext;
3032
import com.google.devtools.build.lib.packages.Type;
3133
import com.google.devtools.build.lib.util.FileTypeSet;
3234
import java.util.HashMap;
35+
import net.starlark.java.eval.Dict;
36+
import net.starlark.java.eval.Mutability;
37+
import net.starlark.java.eval.Starlark;
3338
import net.starlark.java.eval.StarlarkInt;
3439
import net.starlark.java.eval.StarlarkList;
40+
import net.starlark.java.eval.StarlarkSemantics;
41+
import net.starlark.java.eval.Structure;
3542
import org.junit.Test;
3643
import org.junit.runner.RunWith;
3744
import org.junit.runners.JUnit4;
@@ -40,6 +47,15 @@
4047
@RunWith(JUnit4.class)
4148
public class TypeCheckedTagTest {
4249

50+
private static Object getattr(Structure structure, String fieldName) throws Exception {
51+
return Starlark.getattr(
52+
Mutability.IMMUTABLE,
53+
StarlarkSemantics.DEFAULT,
54+
structure,
55+
fieldName,
56+
/*defaultValue=*/ null);
57+
}
58+
4359
@Test
4460
public void basic() throws Exception {
4561
TypeCheckedTag typeCheckedTag =
@@ -48,7 +64,7 @@ public void basic() throws Exception {
4864
buildTag("tag_name").addAttr("foo", StarlarkInt.of(3)).build(),
4965
/*labelConversionContext=*/ null);
5066
assertThat(typeCheckedTag.getFieldNames()).containsExactly("foo");
51-
assertThat(typeCheckedTag.getValue("foo")).isEqualTo(StarlarkInt.of(3));
67+
assertThat(getattr(typeCheckedTag, "foo")).isEqualTo(StarlarkInt.of(3));
5268
}
5369

5470
@Test
@@ -66,14 +82,47 @@ public void label() throws Exception {
6682
createRepositoryMapping(createModuleKey("test", "1.0"), "repo", "other_repo"),
6783
new HashMap<>()));
6884
assertThat(typeCheckedTag.getFieldNames()).containsExactly("foo");
69-
assertThat(typeCheckedTag.getValue("foo"))
85+
assertThat(getattr(typeCheckedTag, "foo"))
7086
.isEqualTo(
7187
StarlarkList.immutableOf(
7288
Label.parseAbsoluteUnchecked("@myrepo//mypkg:thing1"),
7389
Label.parseAbsoluteUnchecked("@myrepo//pkg:thing2"),
7490
Label.parseAbsoluteUnchecked("@other_repo//pkg:thing3")));
7591
}
7692

93+
@Test
94+
public void label_withoutDefaultValue() throws Exception {
95+
TypeCheckedTag typeCheckedTag =
96+
TypeCheckedTag.create(
97+
createTagClass(
98+
attr("foo", BuildType.LABEL).allowedFileTypes(FileTypeSet.ANY_FILE).build()),
99+
buildTag("tag_name").build(),
100+
new LabelConversionContext(
101+
Label.parseAbsoluteUnchecked("@myrepo//mypkg:defs.bzl"),
102+
createRepositoryMapping(createModuleKey("test", "1.0"), "repo", "other_repo"),
103+
new HashMap<>()));
104+
assertThat(typeCheckedTag.getFieldNames()).containsExactly("foo");
105+
assertThat(getattr(typeCheckedTag, "foo")).isEqualTo(Starlark.NONE);
106+
}
107+
108+
@Test
109+
public void stringListDict_default() throws Exception {
110+
TypeCheckedTag typeCheckedTag =
111+
TypeCheckedTag.create(
112+
createTagClass(
113+
attr("foo", Type.STRING_LIST_DICT)
114+
.value(ImmutableMap.of("key", ImmutableList.of("value1", "value2")))
115+
.build()),
116+
buildTag("tag_name").build(),
117+
null);
118+
assertThat(typeCheckedTag.getFieldNames()).containsExactly("foo");
119+
assertThat(getattr(typeCheckedTag, "foo"))
120+
.isEqualTo(
121+
Dict.builder()
122+
.put("key", StarlarkList.immutableOf("value1", "value2"))
123+
.buildImmutable());
124+
}
125+
77126
@Test
78127
public void multipleAttributesAndDefaults() throws Exception {
79128
TypeCheckedTag typeCheckedTag =
@@ -88,9 +137,9 @@ public void multipleAttributesAndDefaults() throws Exception {
88137
.build(),
89138
/*labelConversionContext=*/ null);
90139
assertThat(typeCheckedTag.getFieldNames()).containsExactly("foo", "bar", "quux");
91-
assertThat(typeCheckedTag.getValue("foo")).isEqualTo("fooValue");
92-
assertThat(typeCheckedTag.getValue("bar")).isEqualTo(StarlarkInt.of(3));
93-
assertThat(typeCheckedTag.getValue("quux"))
140+
assertThat(getattr(typeCheckedTag, "foo")).isEqualTo("fooValue");
141+
assertThat(getattr(typeCheckedTag, "bar")).isEqualTo(StarlarkInt.of(3));
142+
assertThat(getattr(typeCheckedTag, "quux"))
94143
.isEqualTo(StarlarkList.immutableOf("quuxValue1", "quuxValue2"));
95144
}
96145

0 commit comments

Comments
 (0)