Mypy checking for build.py#11740
Conversation
25e95dd to
f76b790
Compare
eli-schwartz
left a comment
There was a problem hiding this comment.
Only took a quick look, here are my initial thoughts.
| self.sources_map[f] = o | ||
|
|
||
| def is_linkable_output(self, output: str) -> bool: | ||
| return False |
There was a problem hiding this comment.
This is annoying and happens because for the sake of actually implementing it we base this off of BuildTarget, but for proper semantics / type safety we really would be better off if it was a CustomTarget.
There was a problem hiding this comment.
Should I do something about that before you merge, or are you just informing me on the state of meson.
| str, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, | ||
| build.ExtractedObjects, build.GeneratedList, ExternalProgram, | ||
| mesonlib.File]] | ||
| str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]] |
There was a problem hiding this comment.
What happens if you do pass a BuildTarget or extracted object or some such in, here? Why is it more correct to change the allowance?
There was a problem hiding this comment.
Passing ExternalProgram or ExtractedObjects results in an unhandled exception before this PR.
As to why I also removed build.BuildTarget:
The argument will be passed to msgfmt or itstool eventually.
These tools accept *.mo, *.xml and *.desktop files. (They don't check the filename, but just care about the content.) I don't see a way how a BuildTarget could also be a valid and useful *.mo or *.xml or *.desktop file.
If you pass an ELF file to msgfmt, it will print warnings, and output an empty file.
| assert len(t.install_tag) == num_out | ||
| for x in outdirs_unchecked: | ||
| assert not x is True | ||
| outdirs = T.cast(T.List[T.Union[str, T.Literal[False]]], outdirs_unchecked) |
There was a problem hiding this comment.
| outdirs = T.cast(T.List[T.Union[str, T.Literal[False]]], outdirs_unchecked) | |
| outdirs = T.cast('T.List[T.Union[str, T.Literal[False]]]', outdirs_unchecked) |
T.cast always needs to quote the type annotation being used -- otherwise it gets evaluated at runtime. It is not affected by from __future__ import annotations.
There was a problem hiding this comment.
Yes, that's what made the tests fail.
| pickle.dump(self.create_test_serialisation(tests), datafile) | ||
|
|
||
| def construct_target_rel_paths(self, t: T.Union[build.Target, build.CustomTargetIndex], workdir: T.Optional[str]) -> T.List[str]: | ||
| def construct_target_rel_paths(self, t: T.Union[build.Target, build.CustomTargetIndex], workdir: T.Optional[str]) -> T.Sequence[str]: |
There was a problem hiding this comment.
Sequences of str are bad. Like, really bad. T.List[str] will accept ['foo', 'bar'] but T.Sequence[str] will accept that and also 'foo', because a string is a sequence of single-character strings.
What exactly is wrong with it as it is right now?
There was a problem hiding this comment.
T.Sequence[str]will accept that and also'foo'
I did not know that.
What exactly is wrong with it as it is right now?
I think we are facing a dilemma:
- You cannot implicitly convert
T.List[str]toT.List[T.Union[str, other]]or mypy will complain - You can implicitly convert
T.Sequence[str]toT.Sequence[T.Union[str, other]], but as you said, sequences of str's are bad - You cannot implicitly convert
ImmutableListProtocol[str]toImmutableListProtocol[T.Union[str, other]] - You could copy the list, but that means a performance hit
- You could insert a
T.castcall, but that is ugly and disables many mypy checks.
I think I will be going with the last one.
| # TODO: if it's possible to annotate get_option or validate_option_value | ||
| # in the future we might be able to remove the cast here | ||
| return T.cast('T.Union[str, int, bool, WrapMode]', self.options[key].value) | ||
| return T.cast(T.Union[str, int, bool, 'WrapMode'], self.options[key].value) |
There was a problem hiding this comment.
Don't remove these quotes.
(Actually, it is not strictly necessary to remove any quotes. Usually it doesn't particularly hurt, it just takes a bit to look at each change.)
| # provided by typed_kwargs | ||
| if isinstance(opts, dict): | ||
| return T.cast('T.Dict[OptionKey, str]', opts) | ||
| return T.cast(T.Dict[OptionKey, str], opts) |
| sources: T.List['SourceOutputs'], | ||
| structured_sources: T.Optional[StructuredSources], | ||
| objects: T.List[ObjectTypes], | ||
| objects: T.Sequence[T.Union[str, File, 'ExtractedObjects']], |
There was a problem hiding this comment.
I would think we need a new type alias for this. Also, list > sequence.
8022b0b to
439ffe8
Compare
Feed a custom_target output to cc.preprocess, then feed that output to a generator.
…tgt.extract_objects`
191d324 to
8d65d71
Compare
`get_transitive_link_deps_mapping` has an `@lru_cache`. Therefore, we should never modify its output, only copies of its output.
8d65d71 to
decd8ab
Compare
|
I fixed everything you told me to and made the test pass. |
|
@dcbaker does your PR make mypy happy for build.py? |
|
@tristan957 no, but it does fix a lot of mypy issues that aren't apparent because it's not 100% clear what's coming in from the interpreter. There's a lot of stuff in there that's pretty broken. I'm also solving the Sequence thing, but just enforcing that all string paths are converted to files in the Interpreter, so that the backend never has to deal with them. As an added bonus that makes sequences safe to use because we don't have strings to deal with |
bruchar1
left a comment
There was a problem hiding this comment.
With annotations from __future__, you shouldn't need to quote types. I know many types are quoted for historical reasons, but if we touch them, I think it's better to correct them.
| raise InvalidArguments(f'Bad object of type {type(s).__name__!r} in target {self.name!r}.') | ||
|
|
||
| def process_sourcelist(self, sources: T.List['SourceOutputs']) -> None: | ||
| def process_sourcelist(self, sources: ImmutableListProtocol['SourceOutputs']) -> None: |
There was a problem hiding this comment.
| def process_sourcelist(self, sources: ImmutableListProtocol['SourceOutputs']) -> None: | |
| def process_sourcelist(self, sources: ImmutableListProtocol[SourceOutputs]) -> None: |
|
|
||
| def extract_all_objects_recurse(self) -> T.List[T.Union[str, 'ExtractedObjects']]: | ||
| objs = [self.extract_all_objects()] | ||
| objs: T.List[T.Union[str, 'ExtractedObjects']] = [self.extract_all_objects()] |
There was a problem hiding this comment.
| objs: T.List[T.Union[str, 'ExtractedObjects']] = [self.extract_all_objects()] | |
| objs: T.List[T.Union[str, ExtractedObjects]] = [self.extract_all_objects()] |
|
|
||
| def extract_all_objects_recurse(self) -> T.List[T.Union[str, 'ExtractedObjects']]: | ||
| return self.get_outputs() | ||
| return T.cast('T.List[T.Union[str, ExtractedObjects]]', self.get_outputs()) |
There was a problem hiding this comment.
| return T.cast('T.List[T.Union[str, ExtractedObjects]]', self.get_outputs()) | |
| return T.cast(T.List[T.Union[str, ExtractedObjects]], self.get_outputs()) |
| return CustomTargetIndex(self, self.outputs[index]) | ||
|
|
||
| def __setitem__(self, index, value): | ||
| def __setitem__(self, index: int, value: 'CustomTargetIndex') -> None: |
There was a problem hiding this comment.
| def __setitem__(self, index: int, value: 'CustomTargetIndex') -> None: | |
| def __setitem__(self, index: int, value: CustomTargetIndex) -> None: |
| # If None, self.link_args will be used | ||
| self.raw_link_args: T.Optional[T.List[str]] = None | ||
| self.sources: T.List[T.Union['FileOrString', 'CustomTarget', 'StructuredSources']] = [] | ||
| self.sources: T.List[T.Union['File', 'CustomTarget', 'StructuredSources']] = [] |
There was a problem hiding this comment.
| self.sources: T.List[T.Union['File', 'CustomTarget', 'StructuredSources']] = [] | |
| self.sources: T.List[T.Union[File, CustomTarget, StructuredSources]] = [] |
| return self.is_found | ||
|
|
||
| def get_sources(self) -> T.List[T.Union['FileOrString', 'CustomTarget', 'StructuredSources']]: | ||
| def get_sources(self) -> ImmutableListProtocol[T.Union['File', 'CustomTarget', 'StructuredSources']]: |
There was a problem hiding this comment.
| def get_sources(self) -> ImmutableListProtocol[T.Union['File', 'CustomTarget', 'StructuredSources']]: | |
| def get_sources(self) -> ImmutableListProtocol[T.Union[File, CustomTarget, StructuredSources]]: |
| environment: 'Environment' | ||
| subdir: 'str' |
There was a problem hiding this comment.
| environment: 'Environment' | |
| subdir: 'str' | |
| environment: Environment | |
| subdir: str |
| extra_args = kwargs['args'] | ||
| targets: T.List['Target'] = [] | ||
| gmotargets: T.List['build.CustomTarget'] = [] | ||
| gmotargets: T.List['build.Target'] = [] |
There was a problem hiding this comment.
| gmotargets: T.List['build.Target'] = [] | |
| gmotargets: T.List[build.Target] = [] |
|
|
||
| def typeslistify(item: 'T.Union[_T, T.Sequence[_T]]', | ||
| types: 'T.Union[T.Type[_T], T.Tuple[T.Type[_T]]]') -> T.List[_T]: | ||
| types: 'T.Union[T.Type, T.Tuple[T.Type, ...]]') -> T.List[_T]: |
There was a problem hiding this comment.
| types: 'T.Union[T.Type, T.Tuple[T.Type, ...]]') -> T.List[_T]: | |
| types: T.Union[T.Type, T.Tuple[T.Type, ...]]) -> T.List[_T]: |
run_mypy.pycurrently does not checkmesonbuild/build.py, much to my distaste. Currently, mypy would show a lot of warnings in build.py if it did.I fixed all but one mypy warning in build.py.
Some of these mypy warnings hinted at real bugs, I fixed those.
The 3 test cases I added were inspired by the mypy warnings and my experiences fixing them.
There is still one mypy warning left:
That one hints at a real bug that I can use to create a crash. See #11748.
What do you think? Should I do
ninjabackend.pynext? I suspect that #1633 is hiding in the mypy warnings of ninjabackend.pyAnd no, I'm not gaming any LOC metric here. :-)