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

Partially type check help, ivy, task, and backend target and subsystem folders #8635

Merged
merged 6 commits into from
Nov 18, 2019

Conversation

Eric-Arellano
Copy link
Contributor

As with prior PRs, the focus is on getting as many targets passing with partially_type_checked as possible, rather than adding comprehensive type checks at this stage.

@Eric-Arellano
Copy link
Contributor Author

See https://docs.google.com/spreadsheets/d/1MKg82Fs3uIMOZDoWeNUBD-VirVkOzHU8Tte7oY6ypP0/edit#gid=1479336346 for the current status. The biggest blocker now is that @rules are typed to return a type, but MyPy wants them to be wrapped in Iterator or Generator. I'll look more into this.

Copy link
Contributor

@cosmicexplorer cosmicexplorer left a comment

Choose a reason for hiding this comment

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

Thank you!!

return Linker(
path_entries=self.path_entries,
exe_filename=platform.match({
Platform.darwin: "ld64.lld",
Platform.linux: "lld",
}),
library_dirs=[],
Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you for fixing this!! Could you please add a TODO here pointing to #5663 and mentioning that this is currently dead code? Sorry for not doing this myself!!

@@ -59,7 +59,6 @@ def ivy_resolution_cache_dir(self):
"""Returns the ivy cache dir used by this `Ivy` instance."""
return self._ivy_resolution_cache_dir

@property
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this necessary for typechecking, or is it a style change? I'm very fine with it either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Necessary for type checking. Apparently it's bad to use @property and @contextmanager on the same function. MyPy complains about doing that.

@cosmicexplorer
Copy link
Contributor

I believe the following diff should fix the CI failure:

diff --git a/src/python/pants/backend/native/config/environment.py b/src/python/pants/backend/native/config/environment.py
index 811eeaf29..5b0d04aa2 100644
--- a/src/python/pants/backend/native/config/environment.py
+++ b/src/python/pants/backend/native/config/environment.py
@@ -47,9 +47,9 @@ class _ExtensibleAlgebraic(ABC):
     cur_value = getattr(self, field_name)

     if prepend:
-      new_value = list_value + cur_value
+      new_value = tuple(list_value) + tuple(cur_value)
     else:
-      new_value = cur_value + list_value
+      new_value = tuple(cur_value) + tuple(list_value)

     arg_dict = {field_name: new_value}
     return self.copy(**arg_dict)
@@ -93,7 +93,7 @@ class _ExtensibleAlgebraic(ABC):
     for list_field_name in shared_list_fields:
       lhs_value = getattr(self, list_field_name)
       rhs_value = getattr(other, list_field_name)
-      overwrite_kwargs[list_field_name] = lhs_value + rhs_value
+      overwrite_kwargs[list_field_name] = tuple(lhs_value) + tuple(rhs_value)

     return self.copy(**overwrite_kwargs)

@@ -212,7 +212,7 @@ class _LinkerMixin(_Executable):
   def invocation_environment_dict(self):
     ret = super(_LinkerMixin, self).invocation_environment_dict.copy()

-    full_library_path_dirs = self.linking_library_dirs + [
+    full_library_path_dirs = list(self.linking_library_dirs) + [
       os.path.dirname(f) for f in self.extra_object_files
     ]

diff --git a/src/python/pants/backend/native/tasks/link_shared_libraries.py b/src/python/pants/backend/native/tasks/link_shared_libraries.py
index 72a32d4fa..85a16c368 100644
--- a/src/python/pants/backend/native/tasks/link_shared_libraries.py
+++ b/src/python/pants/backend/native/tasks/link_shared_libraries.py
@@ -169,7 +169,7 @@ class LinkSharedLibraries(NativeTask):
       self.platform.match(
         {Platform.darwin: ['-Wl,-dylib'], Platform.linux: ['-shared']}
       ) +
-      linker.extra_args +
+      list(linker.extra_args) +
       ['-o', os.path.abspath(resulting_shared_lib_path)] +
       ['-L{}'.format(lib_dir) for lib_dir in link_request.external_lib_dirs] +
       ['-l{}'.format(lib_name) for lib_name in link_request.external_lib_names] +
diff --git a/src/python/pants/backend/native/tasks/native_compile.py b/src/python/pants/backend/native/tasks/native_compile.py
index 13abecae6..1a5665845 100644
--- a/src/python/pants/backend/native/tasks/native_compile.py
+++ b/src/python/pants/backend/native/tasks/native_compile.py
@@ -208,10 +208,10 @@ class NativeCompile(NativeTask, metaclass=ABCMeta):
     # TODO: add -v to every compiler and linker invocation!
     argv = (
       [compiler.exe_filename] +
-      compiler.extra_args +
+      list(compiler.extra_args) +
       # TODO: If we need to produce static libs, don't add -fPIC! (could use Variants -- see #5788).
       ['-c', '-fPIC'] +
-      compiler_options +
+      list(compiler_options) +
       [
         '-I{}'.format(os.path.join(buildroot, inc_dir))
         for inc_dir in compile_request.include_dirs

@cosmicexplorer
Copy link
Contributor

One last time should do the trick!! Sorry for not checking the subsystem tests too!

diff --git a/tests/python/pants_test/backend/native/subsystems/test_native_toolchain.py b/tests/python/pants_test/backend/native/subsystems/test_native_toolchain.py
index 758d4c6a3..a1308a12f 100644
--- a/tests/python/pants_test/backend/native/subsystems/test_native_toolchain.py
+++ b/tests/python/pants_test/backend/native/subsystems/test_native_toolchain.py
@@ -121,7 +121,7 @@ class TestNativeToolchain(TestBase, SchedulerTestBase):
         yield toolchain

   def _invoke_compiler(self, compiler, args):
-    cmd = [compiler.exe_filename] + compiler.extra_args + args
+    cmd = [compiler.exe_filename] + list(compiler.extra_args) + args
     env = compiler.invocation_environment_dict
     # TODO: add an `extra_args`-like field to `Executable`s which allows for overriding env vars
     # like this, but declaratively!
@@ -129,7 +129,7 @@ class TestNativeToolchain(TestBase, SchedulerTestBase):
     return self._invoke_capturing_output(cmd, env)

   def _invoke_linker(self, linker, args):
-    cmd = [linker.exe_filename] + linker.extra_args + args
+    cmd = [linker.exe_filename] + list(linker.extra_args) + args
     return self._invoke_capturing_output(
       cmd,
       linker.invocation_environment_dict)

@Eric-Arellano Eric-Arellano merged commit b909a01 into pantsbuild:master Nov 18, 2019
@Eric-Arellano Eric-Arellano deleted the type-check branch November 18, 2019 17:33
cosmicexplorer added a commit that referenced this pull request Nov 20, 2019
### Problem

Fixes #7077.

See #8635 (comment). As of #8330, we annotate all `@rule`s with a single return type, even for rules which call `yield Get(...)` within the method body. MyPy doesn't like this, and requests that rules be given a `Generator` or `Iterable` return type. This blocks type-checking (even partially) for many targets which define rules.

### Solution

- Expand `_RuleVisitor` in `rules.py` to extract `Get` calls from `async def` rules.
- Add an `__await__()` method to `Get` and create `MultiGet` to allow awaiting on multiple `Get`s in parallel (named to match the corresponding rust type).
  - Lists cannot be `await`ed in the way that it is possible to `yield [Get(...) ...]` -- the expression must be *awaitable* -- see https://www.python.org/dev/peps/pep-0492/#await-expression. In this case, `MultiGet` wrapping an iterable of `Get`s and exposing an `__await__()` method is a complete replacement for the previous `yield [Get(...) ...]` syntax.
- Edit `native.py` to allow for `@rule` coroutine methods to `return` at the end instead of `yield`.
- Convert the `@rule`s in `build_files.py` into `async` methods, and type-check the `:build_files` target!

### Result

`@rule`s can now be defined with `async`/`await` syntax:
```python
@rule
async def hydrate_struct(address_mapper: AddressMapper, address: Address) -> HydratedStruct:
  # ...
  address_family: AddressFamily = await Get(AddressFamily, Dir(address.spec_path))
  # ...
  hydrated_inline_dependencies: Tuple[HydratedStruct, ...] = await MultiGet(Get(HydratedStruct, Address, a)
                                                for a in inline_dependencies)
  # ...
  return HydratedStruct(consume_dependencies(struct, args={"address": address}))
```

As a result, plugins and backends that define `@rule`s can now be checked with MyPy!

#### Alternative Solution: Returning `Generator[Any, Any, T]`

In [the first few commits of this PR](https://github.com/pantsbuild/pants/pull/8639/files/43f73f1ac2d1e86301936a895ef08ffe8787d0f7..f7e8534c72965c5e6daa143ccefcdc7428192291), MyPy type annotations were added to `build_files.py` without adding any support for `async` methods. This worked by first adding support for `return` at the end of an `@rule` body in `native.py`, as in this PR, and annotating `@rule`s with the return type `-> Generator[Any, Any, T]`, `T` being the rule's actual return type.

This worked, but required a lot of extra effort to extract the return type `T` from the `Generator[Any, Any, T]` annotation, so this was discarded because `async def` rules require no additional work to extract the output type. For the record, this would have looked like:
```python
@rule
def hydrate_struct(address_mapper: AddressMapper, address: Address) -> Generator[Any, Any, HydratedStruct]:
  # ...
  address_family: AddressFamily
  address_family = yield Get(AddressFamily, Dir(address.spec_path))
  # ...
  hydrated_inline_dependencies: Tuple[HydratedStruct, ...]
  hydrated_inline_dependencies = yield [Get(HydratedStruct, Address, a)
                                                for a in inline_dependencies]
  # ...
  return HydratedStruct(consume_dependencies(struct, args={"address": address}))
```

Note that `x: X = yield Get(X, Y(...))` is not a valid python expression -- this is another benefit of the `async`/`await` approach.

#### Follow-Up Extension: Type-Checked `await Get[X](...)`

Another alternative extending the `await Get()` syntax was proposed in order to automatically type-check the result of the `await` call. This would have looked like:

```python
@rule
async def hydrate_struct(address_mapper: AddressMapper, address: Address) -> HydratedStruct:
  # ...
  address_family = await Get[AddressFamily](Dir(address.spec_path))
  # ...
  hydrated_inline_dependencies = await MultiGet(Get[HydratedStruct](Address, a)
                                                for a in inline_dependencies)
  # ...
  return HydratedStruct(consume_dependencies(struct, args={"address": address}))
```

**Note that the `await Get[X](...)` calls are type-checked to return the type `X`!** This means that mypy can check that later uses of the `address_family` and `hydrated_inline_dependencies` objects above are correct, which it couldn't do without a separate redundant `address_family: AddressFamily` annotation before.

The syntax extension for `Get[X](...)` was reverted in e92fecb in order to reduce complexity of the initial implementation.
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.

2 participants