fix More expressive sys.platform gating #795#2579
fix More expressive sys.platform gating #795#2579asukaminato0721 wants to merge 2 commits intofacebook:mainfrom
Conversation
7d7d640 to
b849e5e
Compare
There was a problem hiding this comment.
Pull request overview
This PR extends Pyrefly’s platform-guard handling so that modules with top-level sys.platform guards (e.g., assert sys.platform == "win32" or if sys.platform != "win32": raise ...) are type-checked using an overridden SysInfo, allowing their imports (e.g., winreg) to resolve under the correct platform.
Changes:
- Add detection of module-level
sys.platformguards and derive an effective per-module platform override. - Apply the overridden
SysInfoduring module analysis and when creating Handles for that module’s imports. - Add a regression test ensuring a
win32-guarded module can importwinregwithout errors.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
pyrefly/lib/test/sys_info.rs |
Adds a regression test for platform-guarded module imports (winreg under win32 guard). |
pyrefly/lib/state/state.rs |
Introduces module SysInfo override plumbing and uses it for import Handle creation / analysis context. |
pyrefly/lib/binding/bindings.rs |
Applies module-level platform guard override to the Bindings builder’s sys_info. |
crates/pyrefly_python/src/sys_info.rs |
Adds SysInfo::with_platform and implements module_platform_guard extraction from module statements. |
Comments suppressed due to low confidence (1)
pyrefly/lib/state/state.rs:1041
stdlibis fetched before computing the module’ssys_infooverride, and it’s still fetched based onmodule_data.handle.sys_info()rather than the overriddensys_info. For platform-guarded modules this can cause analysis to use a stdlib built for the wrong platform/version. Computesys_infofirst, then fetch stdlib viaget_stdlib_for_sys_info(sys_info)(and use that inContext).
let stdlib = self.get_stdlib(&module_data.handle);
let config = module_data.config.read();
let sys_info_override = module_sys_info_override(
module_data.handle.sys_info(),
module_data.state.get_ast().as_deref(),
);
let sys_info = sys_info_override
.as_ref()
.unwrap_or(module_data.handle.sys_info());
let ctx = Context {
require,
module: module_data.handle.module(),
path: module_data.handle.path(),
sys_info,
memory: &self.memory_lookup(),
uniques: &self.data.state.uniques,
stdlib: &stdlib,
lookup: &self.lookup(module_data.dupe()),
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub fn get_stdlib_for_sys_info(&self, sys_info: &SysInfo) -> Arc<Stdlib> { | ||
| if self.data.stdlib.len() == 1 { | ||
| // Since we know our one must exist, we can shortcut | ||
| return self.data.stdlib.first().unwrap().1.dupe(); | ||
| } | ||
|
|
||
| self.data.stdlib.get(handle.sys_info()).unwrap().dupe() | ||
| self.data.stdlib.get(sys_info).unwrap().dupe() | ||
| } |
There was a problem hiding this comment.
get_stdlib_for_sys_info returns the only cached stdlib whenever self.data.stdlib.len() == 1, even if the requested sys_info is different. With platform-guarded modules you can now create Handles with a different platform than the initial run, so this shortcut can silently return a stdlib for the wrong platform (or mask missing entries). Consider removing the shortcut or only using it when the sole key matches sys_info (and otherwise ensure the stdlib for sys_info is computed/available).
| } | ||
|
|
||
| pub fn module_platform_guard(body: &[Stmt]) -> Option<PythonPlatform> { | ||
| body.iter().find_map(platform_guard_from_stmt) |
There was a problem hiding this comment.
module_platform_guard scans the entire module body (iter().find_map(...)) and will treat any matching assert sys.platform == ... / if sys.platform != ...: raise anywhere in the file as a module-level guard. That can incorrectly override SysInfo for modules that merely perform a later runtime check. Consider restricting the scan to the leading top-level statements (e.g., after an optional module docstring and possibly import sys) so only true module-guard patterns trigger an override.
| body.iter().find_map(platform_guard_from_stmt) | |
| for stmt in body { | |
| if let Some(platform) = platform_guard_from_stmt(stmt) { | |
| return Some(platform); | |
| } | |
| match stmt { | |
| // Allow leading trivial statements (e.g. module docstring and imports) | |
| Stmt::Expr(_) | Stmt::Import(_) | Stmt::ImportFrom(_) => continue, | |
| // Any other non-guard statement means there is no module-level platform guard. | |
| _ => break, | |
| } | |
| } | |
| None |
|
There are panics from this change, you can see it in the mypy primer CI job Example
|
Added a cached effective_sys_info to module state so once a platform guard is discovered it persists even if the AST is cleared. Updated import/look‑up and step context construction to use the cached effective sys_info, eliminating the mismatch that led to missing builtins.WindowsError and the LookupAnswer::get panic.
|
Diff from mypy_primer, showing the effect of this PR on open source code: AutoSplit (https://github.com/Toufool/AutoSplit)
- ERROR src/capture_method/BitBltCaptureMethod.py:57:17-30: No attribute `windll` in module `ctypes` [missing-attribute]
+ ERROR src/capture_method/BitBltCaptureMethod.py:81:27-62: Argument `tuple[int, int, Literal[4]]` is not assignable to parameter `value` with type `Sequence[SupportsIndex] | SupportsIndex` in function `numpy.ndarray.shape` [bad-argument-type]
- ERROR src/capture_method/DesktopDuplicationCaptureMethod.py:8:21-29: Could not import `COMError` from `_ctypes` [missing-module-attribute]
+ ERROR src/capture_method/DesktopDuplicationCaptureMethod.py:59:45-63:10: `_YieldT_co` is not assignable to attribute `display` with type `Display` [bad-assignment]
+ ERROR src/capture_method/WindowsGraphicsCaptureMethod.py:158:23-78: Argument `tuple[Unknown, Unknown, Literal[4]]` is not assignable to parameter `value` with type `Sequence[SupportsIndex] | SupportsIndex` in function `numpy.ndarray.shape` [bad-argument-type]
+ ERROR src/d3d11.py:26:16-31:6: `tuple[tuple[Literal['Data1'], type[c_ulong]], tuple[Literal['Data2'], type[c_ushort]], tuple[Literal['Data3'], type[c_ushort]], tuple[Literal['Data4'], type[Array[c_ubyte]]]]` is not assignable to attribute `_fields_` with type `Sequence[tuple[str, type[_CDataType]] | tuple[str, type[_CDataType], int]]` [bad-assignment]
- ERROR src/d3d11.py:35:22-40: No attribute `WINFUNCTYPE` in module `ctypes` [missing-attribute]
- ERROR src/d3d11.py:41:14-32: No attribute `WINFUNCTYPE` in module `ctypes` [missing-attribute]
- ERROR src/d3d11.py:42:15-33: No attribute `WINFUNCTYPE` in module `ctypes` [missing-attribute]
- ERROR src/d3d11.py:53:19-34: No attribute `WinError` in module `ctypes` [missing-attribute]
- ERROR src/d3d11.py:194:15-30: No attribute `WinError` in module `ctypes` [missing-attribute]
- ERROR src/d3d11.py:199:21-39: No attribute `WINFUNCTYPE` in module `ctypes` [missing-attribute]
- ERROR src/d3d11.py:213:27-40: No attribute `windll` in module `ctypes` [missing-attribute]
PyWinCtl (https://github.com/Kalmat/PyWinCtl)
+ ERROR src/pywinctl/_pywinctl_macos.py:62:28-107: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:91:28-107: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:309:24-41: Type `enumerate[str]` is not iterable [not-iterable]
+ ERROR src/pywinctl/_pywinctl_macos.py:351:23-38: Type `enumerate[@_]` is not iterable [not-iterable]
+ ERROR src/pywinctl/_pywinctl_macos.py:356:29-46: Type `enumerate[@_]` is not iterable [not-iterable]
+ ERROR src/pywinctl/_pywinctl_macos.py:391:20-33: Type `_YieldT_co` is not iterable [not-iterable]
+ ERROR src/pywinctl/_pywinctl_macos.py:434:28-435:92: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:457:23-40: Type `enumerate[@_]` is not iterable [not-iterable]
+ ERROR src/pywinctl/_pywinctl_macos.py:523:32-524:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:617:32-618:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:643:32-644:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:684:36-685:100: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:715:40-716:104: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:728:40-729:104: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:741:36-742:100: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:773:32-774:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:808:32-809:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:847:32-848:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:991:32-992:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1017:32-1018:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1073:32-1074:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1119:32-1120:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1218:32-1219:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1258:32-1259:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1306:50-80: No matching overload found for function `difflib.get_close_matches` called with arguments: (str, list[Any] | Any, n=Literal[1]) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1347:32-1348:96: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1463:48-1464:112: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1499:32-44: No matching overload found for function `list.__init__` called with arguments: (Sequence[int] | list[@_]) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1506:36-58: Type `enumerate[str]` is not iterable [not-iterable]
+ ERROR src/pywinctl/_pywinctl_macos.py:1509:55-119: `list[@_] | Unknown` is not assignable to `Sequence[tuple[str, str, bool, str]] | str` [bad-assignment]
+ ERROR src/pywinctl/_pywinctl_macos.py:1533:40-56: `>` is not supported between `int` and `_T` [unsupported-operation]
+ ERROR src/pywinctl/_pywinctl_macos.py:1543:73-80: Argument `list[Unknown] | Unknown` is not assignable to parameter `subAttrList` with type `Sequence[Sequence[tuple[str, str, bool, str]]]` in function `subfillit` [bad-argument-type]
+ ERROR src/pywinctl/_pywinctl_macos.py:1545:90-121: Argument `list[int | _T]` is not assignable to parameter `path` with type `Sequence[int] | None` in function `subfillit` [bad-argument-type]
+ ERROR src/pywinctl/_pywinctl_macos.py:1550:32-73: Type `enumerate[str]` is not iterable [not-iterable]
+ ERROR src/pywinctl/_pywinctl_macos.py:1553:87-127: Argument `list[@_] | Unknown` is not assignable to parameter `subAttrList` with type `Sequence[Sequence[tuple[str, str, bool, str]]]` in function `subfillit` [bad-argument-type]
+ ERROR src/pywinctl/_pywinctl_macos.py:1554:87-96: Argument `list[int | Unknown]` is not assignable to parameter `path` with type `Sequence[int] | None` in function `subfillit` [bad-argument-type]
+ ERROR src/pywinctl/_pywinctl_macos.py:1577:32-57: `list[Unknown] | list[str]` is not assignable to variable `itemPath` with type `Sequence[str] | None` [bad-assignment]
+ ERROR src/pywinctl/_pywinctl_macos.py:1582:36-61: Type `enumerate[str]` is not iterable [not-iterable]
+ ERROR src/pywinctl/_pywinctl_macos.py:1603:44-1604:108: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1643:36-60: Type `enumerate[str]` is not iterable [not-iterable]
+ ERROR src/pywinctl/_pywinctl_macos.py:1643:46-59: Argument `list[str] | list[_T]` is not assignable to parameter `iterable` with type `Iterable[str]` in function `enumerate.__new__` [bad-argument-type]
+ ERROR src/pywinctl/_pywinctl_macos.py:1666:44-1667:108: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1704:38-62: Type `enumerate[str]` is not iterable [not-iterable]
+ ERROR src/pywinctl/_pywinctl_macos.py:1704:48-61: Argument `list[str] | list[_T]` is not assignable to parameter `iterable` with type `Iterable[str]` in function `enumerate.__new__` [bad-argument-type]
+ ERROR src/pywinctl/_pywinctl_macos.py:1727:44-1728:108: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1749:36-61: Type `enumerate[str]` is not iterable [not-iterable]
+ ERROR src/pywinctl/_pywinctl_macos.py:1749:46-60: Argument `list[str] | list[_T]` is not assignable to parameter `iterable` with type `Iterable[str]` in function `enumerate.__new__` [bad-argument-type]
+ ERROR src/pywinctl/_pywinctl_macos.py:1772:44-1773:108: No matching overload found for function `subprocess.Popen.__init__` called with arguments: (list[str], stdin=int, stdout=int, encoding=Literal['utf8']) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1842:33-51: No matching overload found for function `dict.get` called with arguments: (str, dict[@_, @_]) [no-matching-overload]
+ ERROR src/pywinctl/_pywinctl_macos.py:1910:20-40: `+` is not supported between `_T` and `str` [unsupported-operation]
- ERROR src/pywinctl/_pywinctl_win.py:314:16-29: No attribute `windll` in module `ctypes` [missing-attribute]
- ERROR src/pywinctl/_pywinctl_win.py:366:9-22: No attribute `windll` in module `ctypes` [missing-attribute]
- ERROR src/pywinctl/_pywinctl_win.py:371:9-22: No attribute `windll` in module `ctypes` [missing-attribute]
- ERROR src/pywinctl/_pywinctl_win.py:461:9-22: No attribute `windll` in module `ctypes` [missing-attribute]
- ERROR src/pywinctl/_pywinctl_win.py:521:27-40: No attribute `windll` in module `ctypes` [missing-attribute]
- ERROR src/pywinctl/_pywinctl_win.py:522:27-40: No attribute `windll` in module `ctypes` [missing-attribute]
+ ERROR src/pywinctl/_pywinctl_win.py:82:12-77: Returned type `list[_T]` is not assignable to declared return type `list[Win32Window]` [bad-return]
+ ERROR src/pywinctl/_pywinctl_win.py:94:32-51: Argument `Win32Window` is not assignable to parameter `object` with type `_T` in function `list.append` [bad-argument-type]
+ ERROR src/pywinctl/_pywinctl_win.py:296:20-33: Type `_YieldT_co` is not iterable [not-iterable]
+ ERROR src/pywinctl/_pywinctl_win.py:353:20-357:10: `list[tuple[str, type[RECT]] | tuple[str, type[c_ulong]] | tuple[str, type[Array[c_ulong]]]]` is not assignable to attribute `_fields_` with type `Sequence[tuple[str, type[_CDataType]] | tuple[str, type[_CDataType], int]]` [bad-assignment]
+ ERROR src/pywinctl/_pywinctl_win.py:439:16-450:6: `list[tuple[str, type[RECT]] | tuple[str, type[c_uint]] | tuple[str, type[c_ulong]] | tuple[str, type[c_ushort]]]` is not assignable to attribute `_fields_` with type `Sequence[tuple[str, type[_CDataType]] | tuple[str, type[_CDataType], int]]` [bad-assignment]
+ ERROR src/pywinctl/_pywinctl_win.py:1183:44-71: `_VT` is not subscriptable [unsupported-operation]
+ ERROR src/pywinctl/_pywinctl_win.py:1183:51-63: Cannot index into `dict[Unknown, Unknown]` [bad-index]
|
Primer Diff Classification❌ 2 regression(s) | 2 project(s) total 2 regression(s) across AutoSplit, PyWinCtl. error kinds:
Detailed analysis❌ Regression (2)AutoSplit (+4, -9)
However, the NEW errors appear to be regressions:
The improvements (fixing false positive import errors) are valuable, but the new type inference failures create noise on valid code patterns.
PyWinCtl (+57, -6)
Suggested FixSummary: Platform guard detection improvements fixed import resolution but introduced type inference failures, causing Never types and generator inference regressions. 1. In
2. In the Context creation in Transaction::
Was this helpful? React with 👍 or 👎 Classification by primer-classifier (2 LLM) |
|
The primer delta looks unfavorable, so I think this needs some work still. LMK if you want me to help dig into it |
Summary
Fixes #795
completes the platform-guarded import behavior so modules guarded by assert sys.platform == ... are type-checked and their imports are resolved using the correct platform SysInfo.
Test Plan
introduces a regression test that a win32-guarded module can import winreg without errors.