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

Make coredata and environment fully type safe #13737

Open
wants to merge 19 commits into
base: master
Choose a base branch
from

Conversation

dcbaker
Copy link
Member

@dcbaker dcbaker commented Oct 1, 2024

I give you a mypy compliant environment and coredata. There isn't actually that much here once we get the options module safe. With this in place the next big roadblock to full type safety is the build module.

This is built on #13620, and thus this is a draft merged, ready for review.

@dcbaker dcbaker force-pushed the submit/type-safe-coredata branch 3 times, most recently from e350d38 to bd136fd Compare October 1, 2024 20:31
@dcbaker dcbaker force-pushed the submit/type-safe-coredata branch from bd136fd to 4ae1ed4 Compare January 29, 2025 22:15
@dcbaker dcbaker force-pushed the submit/type-safe-coredata branch 3 times, most recently from fe33d21 to 876e4ec Compare January 30, 2025 19:54
These include things like not narrowing unions and boolean error
handling
This gives us a simpler way to annotate things returning an option
value, and gives us an easy single place to change that will affect
everywhere if the option value types are changed.
We don't actually allow anything except elementry types (`str`, `bool`,
`int`, `array[str]`) here, so we can tighten the typing. This in turn
helps us to simplify the typing in environment.py
This as much as anything is to stop lying to envconfig about the
potential types it will be given.
There were two issues, one was that we were calling from an interpreter
method to an interpreter method, and second that we needed to narrow
some option values.
The closure could not be made type safe because of Python's scoping
rules. Plus, it didn't add give us anything the loop doesn't.
Since they need to be strings, but repr() currently gives us what we
want.
BaseOption needs more work to be fully type safe, and that work is not
trivial or obvious to me ATM on how to do, so we'll cast. This will
require more work later.
It must implement an `__iter__` method to meet it's ABC contract. So,
lets just teach `OptionStore` to do the thing it's supposed to
@dcbaker dcbaker force-pushed the submit/type-safe-coredata branch from 876e4ec to 0b098bd Compare February 5, 2025 15:50
@dcbaker dcbaker marked this pull request as ready for review February 5, 2025 15:51
This final bit requires more code than anything before because we need
to deal with getting option values, which we don't have tight contraints
for. To address this I've added a `get_value_safe` method, which takes a
type as well as a key, and does the necessary type checking to ensure
that we do in fact have what we expect
This allows us to get rid of some `if key in optstore
optstore.get_value` and some wrapper functions around the optstore
…e_unsafe

Because using the safe variant should be the default option, and the
unsafe should be a "I really know what I'm doing"
Which can return any of `ElementaryOptionValues`, but is currently typed
as if it only returns `str | int | bool`.
@dcbaker dcbaker force-pushed the submit/type-safe-coredata branch from 0b098bd to 1147816 Compare February 6, 2025 17:48
Copy link
Member

@bruchar1 bruchar1 left a comment

Choose a reason for hiding this comment

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

Does the extra type checks and indirection on get_option / get_value have a performance impact? On my benchmark last week, I realized that get_option was already having an impact when generating the targets.

@@ -940,25 +952,25 @@ def get_static_lib_dir(self) -> str:
return self.get_libdir()

def get_prefix(self) -> str:
return self.coredata.get_option(OptionKey('prefix'))
return _as_str(self.coredata.get_option(OptionKey('prefix')))
Copy link
Member

Choose a reason for hiding this comment

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

Why can't you simply use cast here?

Copy link
Member Author

Choose a reason for hiding this comment

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

We generally use runtime type checks with options because we want to catch cases where a type changes. We know what this is supposed to be, not what it actually is going to be. Really, I'd like do more of the option validation up front so that we do, in fact, know what the type is. But then annotating them gets tricky

@@ -123,10 +123,13 @@ def _args_to_info(self, tgt: str) -> T.Dict[str, str]:
assert all(x in res for x in ['inc', 'src', 'dep', 'tgt', 'func'])
return res

def _get_variable(self, name: str, fallback: T.Optional[str] = None) -> T.Union[TYPE_var, InterpreterObject]:
Copy link
Member

Choose a reason for hiding this comment

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

I think you should call it from dependency() function as well

if isinstance(key, str):
key = OptionKey(key)
return self[key].value

def get_value_safe(self, key: T.Union[OptionKey, str], type_: T.Type[ElementaryOptionTypes]) -> ElementaryOptionTypes:
Copy link
Member

Choose a reason for hiding this comment

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

Isn't it possible to tell that the returned type is given by type_ ?

Copy link
Member Author

Choose a reason for hiding this comment

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

That is exactly what's happening here, however the unsafe get inside could potentially return anything, so we need to do the assert to assure the type checker that it's getting what we told it it would get.

@bruchar1 bruchar1 added the typing label Feb 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants