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

improve ergonomics of checking for @union types with new @decorated_type_checkable decorator #8496

Merged

Conversation

cosmicexplorer
Copy link
Contributor

@cosmicexplorer cosmicexplorer commented Oct 18, 2019

Problem

We manually check getattr(some_type, '_is_union', False) in multiple places in order to check if some_type was declared with the @union annotation. We would like to canonicalize this check and make it type-safe in some sense.

Solution

  • Create @decorated_type_checkable in pants.util.meta, which contains two methods:
    1. define_instance_of(cls), which wraps a class cls and sets the attribute _decorated_type_checkable_type to the return value of @decorated_type_checkable.
    2. is_instance(cls), which checks whether the sentinel attribute _decorated_type_checkable_type matches the type of the return value of @decorated_type_checkable.

@cosmicexplorer cosmicexplorer force-pushed the fully-type-engine-params branch from 6786898 to b6f239a Compare October 18, 2019 04:16
@cosmicexplorer cosmicexplorer force-pushed the fully-type-engine-params branch from b6f239a to 8942d22 Compare October 20, 2019 22:03
cosmicexplorer added a commit that referenced this pull request Oct 21, 2019
### Problem

`pants.util.meta.frozen_after_init` has a couple mypy type checking errors, specifically at: https://github.com/pantsbuild/pants/blob/00c4f8cc4a997f80a34f78b6d5fd6fca746befa7/src/python/pants/util/meta.py#L140-L144

This caused a failure on master, see https://travis-ci.org/pantsbuild/pants/jobs/600466692.

### Solution

- Add `# type: ignore` to the offending lines until #8496 is merged.

### Result

CI is unbroken!
@cosmicexplorer cosmicexplorer force-pushed the fully-type-engine-params branch from eabed7c to dec7f0c Compare October 21, 2019 16:50
@stuhood
Copy link
Member

stuhood commented Oct 22, 2019

This is pretty meta-programming heavy. Is this actually worth it to avoid Any in this case? It's not a very widespread case, so it feels like maybe just removing the comment and letting it be would be preferable...

@Eric-Arellano
Copy link
Contributor

Agreed with Stu. I appreciate how much thought you put into this PR - it’s cool to see how it works.

However, I’d err on the side of keeping things as simple as possible and think Any is fine. My original comment asking for fleshed out type hints was more to go from Any to something slightly more precise like Set[Any], which you’ve done in that precursor.

@cosmicexplorer
Copy link
Contributor Author

I'd be fine reducing this to just the part that adds the @sentinel_attribute decorator in meta.py! I think that being able to do a type-checked union.is_instance(cls) instead of the non-type-checked bool(getattr(input_type, '_is_union', None)) currently in native.py is a huge win. I don't think the rest is worth much and can be tossed.

@cosmicexplorer cosmicexplorer force-pushed the fully-type-engine-params branch from dec7f0c to 0138140 Compare November 17, 2019 10:28
@cosmicexplorer cosmicexplorer changed the title Fully type-check engine Params improve ergonomics of checking for @union types with new @sentinel_attribute decorator Nov 17, 2019
Copy link
Contributor

@illicitonion illicitonion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good :) Thanks!

@@ -167,3 +169,41 @@ def __iter__(self) -> Iterator[_C]:

def __bool__(self) -> bool:
return bool(self.dependencies)


@sentinel_attribute('_is_union')
Copy link
Contributor

@illicitonion illicitonion Nov 18, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we be able to drop the argument by referring instead to the symbol itself at a fixed field name for the check?

I'm imagining something like:

@sentinel_attribute
def union(cls):
  ...


def is_instance(self, obj: type) -> bool:
  return obj._sentinel_attribute_type == self.__class__._sentinel_attribute_type

or something along those lines (depending on exactly what self/cls/metavariables exist in the context where is_instance is actually defined). Where self.__class__._sentinel_attribute_type == union

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!!! Implemented!!!

@@ -23,7 +24,7 @@ def __call__(cls, *args: Any, **kwargs: Any) -> Any:
return cls.instance


T = TypeVar("T")
_T = TypeVar("_T")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good change! The main reason I started making type vars private is to avoid autocomplete suggesting it in other files. Other files shouldn't really use this type var.

"""

@abstractmethod
def __call__(self, cls: type) -> type: ...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically should be typing.Type for the same reason it's typing.List instead of list

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is that reason?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To work with generics. You can't say list[int] or type[Foo], but you can say List[int] and Type[Foo].

However, PEP 585 is proposed to allow the former! https://www.python.org/dev/peps/pep-0585/ This is currently one of the most awkward parts of Python type hints.

Comment on lines 124 to 139
def define_instance_of(self, obj: type, **kwargs) -> type:
return type(obj.__name__, (obj,), {
'_sentinel_attribute_type': type(self),
**kwargs
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy is cool with this?

Copy link
Contributor Author

@cosmicexplorer cosmicexplorer Nov 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!! I thought it was just returning a type -- is there something suspicious about it?

Copy link
Contributor

@illicitonion illicitonion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks!

Now that it's not specifically relating to an attribute, I'm not 100% sure on the name... Something more declarative could be nice, but I don't have a good name... Something like @meta_type_checkable or @can_check_is_subclass or something... Except good?

@cosmicexplorer cosmicexplorer force-pushed the fully-type-engine-params branch from f7847f1 to 6c0c8b3 Compare November 25, 2019 03:37
@cosmicexplorer cosmicexplorer changed the title improve ergonomics of checking for @union types with new @sentinel_attribute decorator improve ergonomics of checking for @union types with new @decorated_type_checkable decorator Nov 25, 2019
@cosmicexplorer cosmicexplorer force-pushed the fully-type-engine-params branch from 6c0c8b3 to 304fa92 Compare November 25, 2019 07:30
@cosmicexplorer cosmicexplorer force-pushed the fully-type-engine-params branch from 304fa92 to 0b207a4 Compare December 24, 2019 19:45
@cosmicexplorer cosmicexplorer force-pushed the fully-type-engine-params branch from 0b207a4 to 431c42a Compare December 24, 2019 20:59
@cosmicexplorer cosmicexplorer force-pushed the fully-type-engine-params branch from 431c42a to abb2a59 Compare December 25, 2019 01:36
@cosmicexplorer cosmicexplorer merged commit e500eb8 into pantsbuild:master Dec 25, 2019
cosmicexplorer added a commit that referenced this pull request May 16, 2020
[ci skip-rust-tests]
[ci skip-jvm-tests]

### Problem

`@decorated_type_checkable` was added in #8496, and it makes checking for certain decorator types a little cleaner.

### Solution

- Use `@decorated_type_checkable` for `@side_effecting`.
- Update the error message to avoid referring to console rules.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants