Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attribute inheritance causes analysis failures for certain built-in attributes #24319

Closed
brandjon opened this issue Nov 13, 2024 · 4 comments
Closed
Assignees
Labels
P1 I'll work on this now. (Assignee required) team-Loading-API BUILD file and macro processing: labels, package(), visibility, glob type: bug

Comments

@brandjon
Copy link
Member

brandjon commented Nov 13, 2024

The new attribute inheritance system handles attributes with computed defaults by setting their value to None within a symbolic macro. That way, when they're passed through to the wrapped rule, the computed-default logic considers it to be missing and computes its value as intended.

But some attributes that are morally computed defaults are not in fact implemented that way. The timeout attribute of test rules is one example. restricted_to is another (that one only fails at analysis time).

A quick solution is to hardcode a list of these badly behaved builtin attributes and None-ify them all. Though this might require a backwards incompatible change to fix their behavior in the future.

Edit: Correction: restricted_to causes it to fail at loading time, not analysis. timeout works for starlark-defined rules but not native ones, because it's specified as a computed default only for starlark rules. Yet we also found a crash bug here, when inheriting attributes of an unexported rule. We think a good fix is to hardcode problematic attribute names in MacroClass#forceDefaultToNone, and only in cases where the Attribute does not have the property flag STARLARK_DEFINED (to avoid triggering on user-defined attrs of the same name). Alternatively, we could give some of these problematic attributes trivial computed defaults, but that seems like it could subtly break things (query output or native.existing_rules perhaps).

@brandjon brandjon added type: bug P1 I'll work on this now. (Assignee required) team-Loading-API BUILD file and macro processing: labels, package(), visibility, glob labels Nov 13, 2024
@brandjon
Copy link
Member Author

@bazel-io fork 8.0.0

copybara-service bot pushed a commit that referenced this issue Nov 14, 2024
…al_enable_macro_inherit_attrs flag

We still have open questions about how macro attribute inheritance ought to
interact with the tracking of whether rule attributes were or were not
explicitly provided.

In effect, this re-opens #24066
Part of addressing #24319

RELNOTES: symbolic macro attribute inheritance is now marked experimental; set --experimental_enable_macro_inherit_attrs flag to enable it.
PiperOrigin-RevId: 696582223
Change-Id: I3d7cb434bf8fe2da9cd10019e6075990205e7153
@brandjon
Copy link
Member Author

Our discussion of this issue turned into a tangled web of confusion around the meaning of None and default values in macros, but we think we have a reasonably elegant solution now:

  1. Within macros, but not rules, attribute descriptors (attr.string(), etc.) are now allowed to pass default=None. (Previously the only commonly used attr type that could do this was attr.label().) This lets a macro opt into declaring that it can see when one of its attributes was not explicitly set. (This is already possible for attr.label(), since my_label_attr = None counts as not passing anything in at all.) Having this feature is conceptually important if macros are ever going to help deprecate the concept of computed defaults.

  2. For inherited attributes, the default of the descriptor is always overridden to be None. Doesn't matter if you're inheriting a computed default, or a simple default="abc" -- the macro will see None if the caller didn't pass anything in.

  3. For configurable attributes, None gets promoted to a trivial select, select({"//conditions:default": None}). (We really need a more concise stringification syntax for that -- maybe select(None)?). When passing this to either a rule or a macro, this gets unwrapped as my_attr = select({"//conditions:default": None}) -> my_attr = None -> dropped. So you see a select value inside your macro, but the rule or macro you're instantiating doesn't get passed anything, and it's not considered explicitly set. Thus, computed defaults execute as intended, it's omitted from bazel query inspection, etc.

This would seem to solve the direct bugs raised in this issue about crashes when inheriting built-in attributes. It also maintains the nice property that macro authors don't need to worry about None being a distinct value, at least for non-inherited attributes, unless they specifically opt into that as their default or they are trying to manipulate an inherited attribute. And when they do see a None in a configurable attribute, it's still a select() just like anything else. Furthermore, when migrating legacy macros to symbolic, we avoid a regression where all the **kwargs attributes seem to be explicitly set on the rule.

github-merge-queue bot pushed a commit that referenced this issue Nov 15, 2024
…perimental_enable_macro_inherit_attrs flag (#24336)

We still have open questions about how macro attribute inheritance ought
to interact with the tracking of whether rule attributes were or were
not explicitly provided.

In effect, this re-opens
#24066 Part of addressing
#24319

RELNOTES: symbolic macro attribute inheritance is now marked
experimental; set --experimental_enable_macro_inherit_attrs flag to
enable it.

Commit

08beb21

PiperOrigin-RevId: 696582223
Change-Id: I3d7cb434bf8fe2da9cd10019e6075990205e7153

Working towards #24335
@brandjon
Copy link
Member Author

We discussed again and are backtracking on the part about promoting None to select({"//conditions:default": None}). The rest is good. Rationale:

  • Testing x == None is common, and would break if we select-promoted None.
  • Seeing select-wrapped Nones all over the place in debugging and stringification isn't aesthetically pleasing.
  • Select promotion is generally important so that a param type is Select[T] rather than Union[T, Select[T]] -- you can't usually do many useful operations on the latter without figuring out which one of those two cases it is. The same argument doesn't apply quite so well to Union[T, None] or Union[Select[T], None] -- it's very common to have Noneable types and second nature to test via == None.
  • A select() can pop up totally unexpectedly from the macro author's POV, so it makes sense to normalize to select()s to fail fast. But if a None shows up, there's a better case to be made that the macro author should've expected it, because they probably wrote default=None in their attr schema. That's not true for inherited attrs of course, but normally macro authors aren't directly manipulating inherited attrs anyway. Furthermore, you're much more likely (IMO) to have a test case where you don't pass an attr in, than a test case where you pass a select() in.

copybara-service bot pushed a commit that referenced this issue Nov 15, 2024
Previously it was thought that the default value of attr.label(), None, should be promoted to select({"//conditions:default": None}) inside a symbolic macro, for symmetry with all other attribute values. But during the design of attribute inheritance, it was discovered that it's simpler to leave Nones unpromoted, and this is likely the lesser of two evils. See GH issue for more context: #24319 (comment)

Drive-by change in test case labelVisitation(): Add extra branch to select() to ensure it isn't eliminated by automatic select unwrapping, which would defeat the test's coverage.

Work toward #24319. Blocks Bazel 8.0.

PiperOrigin-RevId: 696969888
Change-Id: I53b011d80aa0255e2e5882044423a746a4ca46c1
brandjon added a commit to brandjon/bazel that referenced this issue Nov 18, 2024
Previously it was thought that the default value of attr.label(), None, should be promoted to select({"//conditions:default": None}) inside a symbolic macro, for symmetry with all other attribute values. But during the design of attribute inheritance, it was discovered that it's simpler to leave Nones unpromoted, and this is likely the lesser of two evils. See GH issue for more context: bazelbuild#24319 (comment)

Drive-by change in test case labelVisitation(): Add extra branch to select() to ensure it isn't eliminated by automatic select unwrapping, which would defeat the test's coverage.

Work toward bazelbuild#24319. Blocks Bazel 8.0.

PiperOrigin-RevId: 696969888
Change-Id: I53b011d80aa0255e2e5882044423a746a4ca46c1
tetromino added a commit to tetromino/bazel that referenced this issue Nov 19, 2024
…d attributes to None

This fixes two problems:

* Various attributes (e.g. compatible_with, restricted_to, shard_count,
  genrule's cmd and cmd_bat, etc.) have hard-coded analysis-time behavior in
  Bazel which differs depending on whether the attribute was unset (or set to
  None) or set to any other value (including the attribute's non-None
  default!). Using the original default value for such an inherited attribute
  and passing it to the wrapped rule would in many cases break the build.
* The fact that an attribute was set explicitly is reflected in query proto
  and xml output. In a legacy macro that wraps a rule and passes the bulk of
  them via **kwargs, it is expected that most of the **kwargs will be empty
  and most of the wrapped rule's attributes will thus be not explicitly set.
  We want to preserve the same behavior in symbolic macros.

Working towards bazelbuild#24066

Fixes bazelbuild#24319

Cherry-pick of
bazelbuild@a2f1f58

PiperOrigin-RevId: 697301269
Change-Id: I47563898c511a1f065d117f51a7a3a227e23260e
keertk pushed a commit that referenced this issue Nov 19, 2024
…al_enable_macro_inherit_attrs flag

We still have open questions about how macro attribute inheritance ought to
interact with the tracking of whether rule attributes were or were not
explicitly provided.

In effect, this re-opens #24066
Part of addressing #24319

RELNOTES: symbolic macro attribute inheritance is now marked experimental; set --experimental_enable_macro_inherit_attrs flag to enable it.
PiperOrigin-RevId: 696582223
Change-Id: I3d7cb434bf8fe2da9cd10019e6075990205e7153
@iancha1992
Copy link
Member

A fix for this issue has been included in Bazel 8.0.0 RC3. Please test out the release candidate and report any issues as soon as possible.
If you're using Bazelisk, you can point to the latest RC by setting USE_BAZEL_VERSION=8.0.0rc3. Thanks!

ramil-bitrise pushed a commit to bitrise-io/bazel that referenced this issue Dec 18, 2024
…al_enable_macro_inherit_attrs flag

We still have open questions about how macro attribute inheritance ought to
interact with the tracking of whether rule attributes were or were not
explicitly provided.

In effect, this re-opens bazelbuild#24066
Part of addressing bazelbuild#24319

RELNOTES: symbolic macro attribute inheritance is now marked experimental; set --experimental_enable_macro_inherit_attrs flag to enable it.
PiperOrigin-RevId: 696582223
Change-Id: I3d7cb434bf8fe2da9cd10019e6075990205e7153
ramil-bitrise pushed a commit to bitrise-io/bazel that referenced this issue Dec 18, 2024
Previously it was thought that the default value of attr.label(), None, should be promoted to select({"//conditions:default": None}) inside a symbolic macro, for symmetry with all other attribute values. But during the design of attribute inheritance, it was discovered that it's simpler to leave Nones unpromoted, and this is likely the lesser of two evils. See GH issue for more context: bazelbuild#24319 (comment)

Drive-by change in test case labelVisitation(): Add extra branch to select() to ensure it isn't eliminated by automatic select unwrapping, which would defeat the test's coverage.

Work toward bazelbuild#24319. Blocks Bazel 8.0.

PiperOrigin-RevId: 696969888
Change-Id: I53b011d80aa0255e2e5882044423a746a4ca46c1
ramil-bitrise pushed a commit to bitrise-io/bazel that referenced this issue Dec 18, 2024
…utes to None

This fixes two problems:

* Various attributes (e.g. compatible_with, restricted_to, shard_count,
  genrule's cmd and cmd_bat, etc.) have hard-coded analysis-time behavior in
  Bazel which differs depending on whether the attribute was unset (or set to
  None) or set to any other value (including the attribute's non-None
  default!). Using the original default value for such an inherited attribute
  and passing it to the wrapped rule would in many cases break the build.
* The fact that an attribute was set explicitly is reflected in query proto
  and xml output. In a legacy macro that wraps a rule and passes the bulk of
  them via **kwargs, it is expected that most of the **kwargs will be empty
  and most of the wrapped rule's attributes will thus be not explicitly set.
  We want to preserve the same behavior in symbolic macros.

Working towards bazelbuild#24066

Fixes bazelbuild#24319

PiperOrigin-RevId: 697301269
Change-Id: I47563898c511a1f065d117f51a7a3a227e23260e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
P1 I'll work on this now. (Assignee required) team-Loading-API BUILD file and macro processing: labels, package(), visibility, glob type: bug
Projects
None yet
Development

No branches or pull requests

3 participants