Skip to content

Commit

Permalink
Don't register non-union subclasses of unions as union members (#16645)
Browse files Browse the repository at this point in the history
Before:
```
>>> print(union_membership.get(BlackRequest))
FrozenOrderedSet([<class 'pants.backend.python.lint.autoflake.rules.AutoflakeRequest'>, <class 'pants.backend.python.lint.black.rules.BlackRequest'>, <class 'pants.backend.python.lint.docformatter.rules.DocformatterRequest'>, <class 'pants.backend.python.lint.isort.rules.IsortRequest'>, <class 'pants.backend.shell.lint.shfmt.rules.ShfmtRequest'>, <class 'pants.backend.go.lint.gofmt.rules.GofmtRequest'>, <class 'pants.backend.java.lint.google_java_format.rules.GoogleJavaFormatRequest'>, <class 'pants.backend.scala.lint.scalafmt.rules.ScalafmtRequest'>])
```

After:
```
>>> print(union_membership.get(BlackRequest))
FrozenOrderedSet([])
```

This was introduced in #15876 which was first in 2.14.
  • Loading branch information
thejcannon committed Aug 25, 2022
1 parent 7251de9 commit 8b03d13
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 13 deletions.
6 changes: 3 additions & 3 deletions src/python/pants/engine/target.py
Expand Up @@ -467,9 +467,9 @@ def field_types(self) -> Tuple[Type[Field], ...]:
@final
@memoized_classproperty
def _plugin_field_cls(cls) -> type:
# NB: We ensure that each Target subtype has its own `PluginField` class so that
# registering a plugin field doesn't leak across target types.

# Use the `PluginField` of the first `Target`-subclass ancestor as a base to ours, so that
# we inherit the registered fields. E.g. If I inherit from `PythonSourceTarget`, I want all
# the registered fields on `PythonSourceTarget` to also be registered for me.
baseclass = (
object
if cast("Type[Target]", cls) is Target
Expand Down
10 changes: 6 additions & 4 deletions src/python/pants/engine/unions.py
Expand Up @@ -72,14 +72,16 @@ def from_rules(cls, rules: Iterable[UnionRule]) -> UnionMembership:
mapping: DefaultDict[type, OrderedSet[type]] = defaultdict(OrderedSet)
for rule in rules:
mapping[rule.union_base].add(rule.union_member)
# Subclassed union bases should inherit the superclass's union members.

# Base union classes inherit the members of any subclasses that are also unions
bases = list(mapping.keys())
while len(bases) > 0:
union_base = bases.pop()
for sub_union in union_base.__subclasses__():
if sub_union not in mapping:
bases.append(sub_union)
mapping[sub_union].update(mapping[union_base])
if is_union(sub_union):
if sub_union not in mapping:
bases.append(sub_union)
mapping[sub_union].update(mapping[union_base])
return cls(mapping)

def __init__(self, union_rules: Mapping[type, Iterable[type]]) -> None:
Expand Down
39 changes: 33 additions & 6 deletions src/python/pants/engine/unions_test.py
Expand Up @@ -5,17 +5,44 @@
from pants.util.ordered_set import FrozenOrderedSet


def test_union_membership_from_rules() -> None:
def test_simple() -> None:
@union
class Base:
class Fruit:
pass

class A:
class Banana(Fruit):
pass

class B:
class Apple(Fruit):
pass

assert UnionMembership.from_rules([UnionRule(Base, A), UnionRule(Base, B)]) == UnionMembership(
{Base: FrozenOrderedSet([A, B])}
@union
class CitrusFruit(Fruit):
pass

class Orange(CitrusFruit):
pass

@union
class Vegetable:
pass

class Potato: # Doesn't _have_ to inherit from the union
pass

union_membership = UnionMembership.from_rules(
[
UnionRule(Fruit, Banana),
UnionRule(Fruit, Apple),
UnionRule(CitrusFruit, Orange),
UnionRule(Vegetable, Potato),
]
)

assert union_membership == UnionMembership(
{
Fruit: FrozenOrderedSet([Banana, Apple]),
CitrusFruit: FrozenOrderedSet([Orange, Banana, Apple]),
Vegetable: FrozenOrderedSet([Potato]),
}
)

0 comments on commit 8b03d13

Please sign in to comment.