
Ruff Ty 類型檢查規則解析invalid-type-checking-constant 與 TYPE_CHECKING 常量約束【免費下載鏈接】ruffAn extremely fast Python linter and code formatter, written in Rust.項目地址: https://gitcode.com/GitHub_Trending/ru/ruff導讀TYPE_CHECKING是 Python 類型系統中一個特殊變量在類型檢查器眼中它恒為True在運行時卻必須為False承擔著把僅類型檢查器可見的代碼與運行時執行的代碼隔離的職責。本文以 ruff 倉庫中 ty原生類型檢查器tycrate實現的invalid-type-checking-constant規則為核心講解該規則檢查什么、為什么必須這樣設計、ty 內部是如何在賦值與注解兩個代碼路徑上實施檢查的并給出可落地的修正實踐。規則定位檢查什么規則invalid-type-checking-constant出自 ty_python_semantic 的 lint 文檔它檢查兩類問題給TYPE_CHECKING變量賦了False以外的值給TYPE_CHECKING變量加的注解不是可以從bool賦值的類型。規則文檔中給出的兩個最小錯誤示例為TYPE_CHECKING: str # error TYPE_CHECKING # error第一行給TYPE_CHECKING標注了str類型——bool無法賦值給str注解非法第二行在無注解的裸賦值中給它賦了空字符串——不是字面量False同樣非法。從 ty 規則總覽文檔 可以看到該規則的注冊信息默認級別為error自 ty 的0.0.1-alpha.1版本起加入。為什么必須這樣做TYPE_CHECKING 的雙面語義規則文檔解釋了其背后的核心原因TYPE_CHECKING這個名字被保留用作一個標志位flag用來書寫只有類型檢查器看得到、運行時不會執行的條件代碼。正常情況下它從typing或typing_extensions導入但也可以由開發者在本模塊中自行定義。問題的關鍵在于它的雙面語義運行時本地定義時必須賦值為False這樣if TYPE_CHECKING:分支永遠不會在運行時執行類型檢查期類型檢查器會一律把它的值視為True從而進入if TYPE_CHECKING:分支分析其中的類型信息典型如import延遲導入、為類型檢查而設的引用。一旦違背這一約束——例如賦了True、空字符串、非bool可賦值的注解——類型檢查器對該名字的語義假設就會被破壞if TYPE_CHECKING:代碼塊的僅類型檢查可見這一性質也就不再成立。這種語義在 ty 的測試文檔 mdtest/known_constants.md 中被系統性地驗證。例如從typing導入的TYPE_CHECKING無論使用哪種引用方式其類型都被推導為reveal_type(TYPE_CHECKING) # revealed: Literal[True] reveal_type(typing.TYPE_CHECKING) # revealed: Literal[True]而在用戶自行定義TYPE_CHECKING False時即使字面量是False類型檢查器依然把它當作True使用TYPE_CHECKING False reveal_type(TYPE_CHECKING) # revealed: Literal[True] if TYPE_CHECKING: ... # 類型檢查期可達分支也就是說變量必須寫False、類型檢查器卻按True理解正是該規則的完整設計語義——校驗只針對寫下的源碼而類型推導結果恒定指向Literal[True]。源碼實現兩處檢查路徑規則的核心診斷函數位于 crates/ty_python_semantic/src/types/diagnostic.rs#L3010-L3017pub(super) fn report_invalid_type_checking_constant(context: InferContext, node: AnyNodeRef) { let Some(builder) context.report_lint(INVALID_TYPE_CHECKING_CONSTANT, node) else { return; }; builder.into_diagnostic( The name TYPE_CHECKING is reserved for use as a flag; only False can be assigned to it, ); }診斷消息為The name TYPE_CHECKING is reserved for use as a flag; only False can be assigned to itTYPE_CHECKING這個名字被保留用作標志位只有False可以被賦給它。從源碼結構看該函數通過report_lint框架上報并在檢查點通過行內信息或子診斷給出補充說明。真正判定是否違規的邏輯在類型推導器 crates/ty_python_semantic/src/types/infer/builder.rs 中共分兩條路徑。路徑一無注解的賦值語句在builder.rs的賦值推導分支中約 L3593-L3607源碼注釋明確指出TYPE_CHECKINGis a special variable that should only be assignedFalseat runtime, but is always consideredTruein type checking.TYPE_CHECKING是特殊變量運行時只應賦False而類型檢查期總視為True。參見 mdtest/known_constants.md 中 User-defined TYPE_CHECKING 一節。對應的檢查邏輯為當賦值目標是名字恰好為TYPE_CHECKING的名字表達式且右側值不是布爾字面量False即ExprBooleanLiteral { value: false }時調用report_invalid_type_checking_constant報告錯誤隨后無論字面量是什么都把該名字的類型綁定為Type::bool_literal(true)即Literal[True]。這解釋了開篇示例第二行TYPE_CHECKING 為何報錯。路徑二帶類型注解的聲明另一處檢查發生在處理帶注解變量聲明/注解賦值時約 L4676-L4703邏輯分為三步先校驗注解若KnownClass::Bool的實例類型無法賦值給聲明的注解類型declared.inner_type()即注解不接受bool直接報告invalid-type-checking-constant——對應文檔示例第一行TYPE_CHECKING: str否則注解可接受bool再校驗文件類型與初值若處于 stub 文件.pyi代碼內self.in_stub()為真且初值缺失或為...則視為合法stub 中只寫TYPE_CHECKING: bool或TYPE_CHECKING: bool ...是被允許的聲明方式其余情況下初值只要不是布爾字面量False就報告錯誤最后無論注解如何都把聲明的內部類型改寫為Type::bool_literal(true)。綜合兩條路徑一個合法的本地定義需要同時滿足兩個條件注解類型接受bool推薦直接寫bool初值必須是字面量False。ty 的測試文檔將這一規則以行為示例固化了下來見 mdtest/known_constants.md 中 Invalid assignment to TYPE_CHECKING 一節包括TYPE_CHECKING True # error賦值不是 False TYPE_CHECKING: bool True # error賦值不是 False TYPE_CHECKING: int 1 # errorbool 不能賦值給 int TYPE_CHECKING: str str # error注解與初值均非法 TYPE_CHECKING: str False # error注解不接受 bool TYPE_CHECKING: Literal[False] False # error注解類型不接受 bool TYPE_CHECKING: Literal[True] False # error同上注意最后兩類盡管初值是False但Literal[False]/Literal[True]這類窄化注解仍被判定為非法——因為類型檢查器會把該變量最終視為Literal[True]窄到單一字面量的注解與這一推導結果不自洽。唯一的合法注解形式是TYPE_CHECKING: bool False。實踐指引合法與非法寫法對照完全合法的定義方式# 方式一無注解、直接賦 False最常見 TYPE_CHECKING False # 方式二顯式注解為 bool初值 False TYPE_CHECKING: bool False # 方式三stub 文件中.pyi可省略初值或用省略號占位 # TYPE_CHECKING: bool # TYPE_CHECKING: bool ...當TYPE_CHECKING為False時類型檢查器依然將其視為True因此下列慣用代碼模式延遲導入可以安全通過TYPE_CHECKING False if TYPE_CHECKING: from some_heavy_module import HeavyClass # 僅類型檢查可見不產生運行時導入 def f(x: HeavyClass) - None: # 注解可解析 ...必須修正的寫法TYPE_CHECKING: str # errorbool 無法賦值給 str TYPE_CHECKING # error初值非 False TYPE_CHECKING True # error初值非 False修正建議若只是為了運行期恒假、類型期恒真的分支控制優先選擇從typing或typing_extensionsimport TYPE_CHECKING完全規避自定義帶來的約束問題必須自定義時刪去不必要注解并寫TYPE_CHECKING False若需要顯式注解使用bool并同時確保初值為字面量False在.pyistub 文件中可寫為TYPE_CHECKING: bool或TYPE_CHECKING: bool ...這是源碼中明確放行的兩種 stub 聲明形態。相關文件索引若希望深入閱讀本規則的文檔、實現與行為測試可依次查看以下倉庫文件規則 lint 文檔crates/ty_python_semantic/resources/lint_docs/invalid-type-checking-constant.md診斷上報函數crates/ty_python_semantic/src/types/diagnostic.rs#L3010-L3017類型推導與判定邏輯crates/ty_python_semantic/src/types/infer/builder.rs#L3593-L3607 與 crates/ty_python_semantic/src/types/infer/builder.rs#L4676-L4703行為測試mdtest 用例crates/ty_python_semantic/resources/mdtest/known_constants.mdty 規則注冊與默認級別crates/ty/docs/rules.md#L3275-L3294總結invalid-type-checking-constant規則守護的是 Python 類型系統中最容易被誤解的常量語義TYPE_CHECKING的源碼寫False、檢查期讀True的雙面契約。ty 在實現上分別覆蓋了裸賦值與注解聲明兩條路徑并對 stub 文件中的省略初值與...占位做了特例放行理解這套規則后你在書寫if TYPE_CHECKING:隔離塊與相關延遲導入時就能既寫出類型檢查器認可的代碼又不引入任何運行時開銷。【免費下載鏈接】ruffAn extremely fast Python linter and code formatter, written in Rust.項目地址: https://gitcode.com/GitHub_Trending/ru/ruff創作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考