Support more complex types by using generics
It's currently very hard to work with custom providers. One example of somewhere seriously lacking is if I want to do assertions on the following type:
FooInfo = provider(fields = {"name": "str")
BarInfo = provider(fields = {
"foos": "(depset[Foo]) the foos",
})
I would like to be able to write something like:
env.expect.that_provider(BarInfo).foos()
.contains_at_least_predicates(lambda foo: foo.name().equals("foo"))
I propose that we allow generics by turning generic types into functions. For example:
FooInfoSubject = subjects.struct(name = subjects.string)
BarInfoSubject = subjects.struct(
foos = subjects.depset(FooInfoSubject)
)
This would make the above predicates far more easily implemented (although the caveat is that it would require a change in matchers so that you could simply use them as a boolean predicate - at the moment, not matching simply fails the test).
We could also use this to easily solve #63 via the generic type subjects.optional (eg. subjects.optional(subjects.string)).
FWIW, I've mostly solved this by just doing the following:
# I strongly dislike how struct is defined, IMO the attrs should be curried
def struct(**attrs):
return lambda value, *, meta: subjects.struct(value, meta=meta, attrs=attrs)
def optional(factory):
def new_factory(value, *, meta):
# Optimisations could be done here to just pull all the methods from the parent and add the method is_none()
def some():
if value == None:
meta.add_failure("Wanted a value, but got None", None)
return factory(value, meta = meta)
def is_none():
if value != None:
meta.add_failure("Wanted None, but got a value", value)
return struct(some = some, is_none = is_none)
generic_subjects = struct(struct = struct, optional = optional)
You can then just write:
FooInfo = provider(fields = {"a": "(Optional[str])")
FooInfoSubject = generic_subjects.struct(a = generic_subjects.optional(subjects.str))
foo_some = FooInfo(a = "abc")
foo_none = FooInfo(a = None)
env.expect.that_value(foo_some, factory = FooInfoSubject).a().some().contains("abc")
env.expect.that_value(foo_none, factory = FooInfoSubject).a().is_none()