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

@union / UnionRule for letting the engine figure out paths to products not known in advance #7116

merged 19 commits into from Mar 6, 2019
Changes from 1 commit
Show all changes
19 commits
Select commit Hold shift + click to select a range
separate the declared type from the actual subject type of the Get
cosmicexplorer Jan 20, 2019
plumb a dict of type constraint -> list<type constraint> for unions i…
cosmicexplorer Jan 20, 2019
add testing for union rules which fails
cosmicexplorer Jan 20, 2019
union rules work now
cosmicexplorer Jan 20, 2019
introduce Get.create_statically_for_rule_graph() to avoid confusion
cosmicexplorer Jan 20, 2019
introduce a wrapper datatype for normalized_rules() for no particular…
cosmicexplorer Jan 20, 2019
flesh out docstrings
cosmicexplorer Jan 20, 2019
add union_rules to all Scheduler() constructions
cosmicexplorer Jan 21, 2019
add followup issue link for graph visualization
cosmicexplorer Jan 21, 2019
test the Get typechecking which we now do during rule execution
cosmicexplorer Jan 21, 2019
add further checking to Get() arguments to overcome ambiguity that le…
cosmicexplorer Jan 21, 2019
make bundles and sources fields use union rules
cosmicexplorer Mar 3, 2019
remove all knowledge of union rules from the engine
cosmicexplorer Mar 3, 2019
turn panics into throws and make union tests pass!
cosmicexplorer Mar 3, 2019
remove @union_rule for UnionRule() datatype and add docstrings
cosmicexplorer Mar 3, 2019
cleanup in response to review comments
cosmicexplorer Mar 3, 2019
remove now-unused native lib helper
cosmicexplorer Mar 3, 2019
flesh out the error message TODO!
cosmicexplorer Mar 5, 2019
fix error message test
cosmicexplorer Mar 5, 2019
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.


Just for now

test the Get typechecking which we now do during rule execution

also test Get.extract_constraints()!
  • Loading branch information...
cosmicexplorer committed Jan 21, 2019
commit 23ca41b3aa77e06bd43d8277ba9534bcaa4d2ee6
@@ -43,11 +43,17 @@ def extract_constraints(call_node):
:return: A tuple of product type id and subject type id.
def render_args():
return ', '.join( for a in call_node.args)
return ', '.join(
# Dump the Name's id to simplify output when available, falling back to the name of the
# node's class.
getattr(a, 'id', type(a).__name__)
for a in call_node.args)

if len(call_node.args) == 2:
product_type, subject_constructor = call_node.args
if not isinstance(product_type, ast.Name) or not isinstance(subject_constructor, ast.Call):
# TODO(#7114): describe what types of objects are expected in the get call, not just the
# argument names. After #7114 this will be easier because they will just be types!
raise ValueError(
'Two arg form of {} expected (product_type, subject_type(subject)), but '
'got: ({})'.format(Get.__name__, render_args()))
@@ -73,7 +79,8 @@ def create_statically_for_rule_graph(cls, product_type, subject_type):
return cls(product_type, subject_type, None)

def __new__(cls, *args):
# TODO: typecheck the args!
# TODO(#7114): typecheck the args (as in, ensure they are types or constraints)! After #7114, we
# can just check that they are types.
if len(args) == 2:
product, subject = args
subject_declared_type = type(subject)
@@ -94,13 +94,31 @@ def select_union_b(union_b):
return union_b.a()

# TODO: add GetMulti testing!
# TODO: add GetMulti testing for unions!
@rule(A, [Select(UnionWrapper)])
def a_union_test(union_wrapper):
union_a = yield Get(A, UnionBase, union_wrapper.inner)
yield union_a

class TypeCheckFailWrapper(object):
This object wraps another object which will be used to demonstrate a type check failure when the
engine processes a `yield Get(...)` statement.

def __init__(self, inner):
self.inner = inner

@rule(A, [Select(TypeCheckFailWrapper)])
def a_typecheck_fail_test(wrapper):
# This `yield Get(A, B, ...)` will use the `nested_raise` rule defined above, but it won't get to
# the point of raising since the type check will fail at the Get.
supposedly_a = yield Get(A, B, wrapper.inner)
yield supposedly_a

class SchedulerTest(TestBase):

@@ -121,6 +139,8 @@ def rules(cls):

def test_use_params(self):
@@ -171,6 +191,13 @@ def test_union_rules(self):
with self._assert_execution_error(expected_msg):
self.scheduler.product_request(A, [Params(UnionWrapper(A()))])

def test_get_type_match_failure(self):
"""Test that Get(...)s are now type-checked during rule execution, to allow for union types."""
expected_msg = "Exception: Declared type did not match actual type for Get { product: TypeConstraint(=A), subject_declared_type: TypeConstraint(=B), subject: <pants_test.engine.test_scheduler.A object at 0xEEEEEEEEE>"
with self._assert_execution_error(expected_msg):
# `a_typecheck_fail_test` above expects `wrapper.inner` to be a `B`.
self.scheduler.product_request(A, [Params(TypeCheckFailWrapper(A()))])

class SchedulerTraceTest(unittest.TestCase):
assert_equal_with_printing = assert_equal_with_printing
@@ -4,8 +4,9 @@

from __future__ import absolute_import, division, print_function, unicode_literals

import ast
import unittest
from builtins import object
from builtins import object, str

from pants.engine.selectors import Get, Select

@@ -32,8 +33,28 @@ def assert_repr(self, expected, selector):

class GetTest(unittest.TestCase):
def test_get(self):
sub_b = SubBClass()
with self.assertRaises(TypeError) as cm:
Get(AClass, BClass, sub_b)
self.assertIn("Declared type did not match actual type", str(cm.exception))
def _get_call_node(self, input_string):
return ast.parse(input_string).body[0].value

def test_extract_constraints(self):
parsed_two_arg_call = self._get_call_node("Get(A, B(x))")
self.assertEqual(('A', 'B'),

with self.assertRaises(ValueError) as cm:
Get.extract_constraints(self._get_call_node("Get(1, 2)"))
self.assertEqual(str(cm.exception), """\
Two arg form of Get expected (product_type, subject_type(subject)), but got: (Num, Num)""")

parsed_three_arg_call = self._get_call_node("Get(A, B, C(x))")
self.assertEqual(('A', 'B'),

with self.assertRaises(ValueError) as cm:
Get.extract_constraints(self._get_call_node("Get(A, 'asdf', C(x))"))
self.assertEqual(str(cm.exception), """\
Three arg form of Get expected (product_type, subject_declared_type, subject), but got: (A, Str, Call)""")

def test_create_statically_for_rule_graph(self):
self.assertEqual(Get(AClass, BClass, None),
Get.create_statically_for_rule_graph(AClass, BClass))
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.