<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Pyrefly Blog</title>
        <link>https://pyrefly.org/blog/</link>
        <description>Pyrefly Blog</description>
        <lastBuildDate>Tue, 21 Apr 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Right Types, Wrong Code: Surprising Bugs A Type Checker Catches]]></title>
            <link>https://pyrefly.org/blog/surprising-errors/</link>
            <guid>https://pyrefly.org/blog/surprising-errors/</guid>
            <pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Pyrefly catches more than type mismatches. Here are five real Python bugs — from forgotten awaits to renamed parameters — that your type checker can find before your users do.]]></description>
            <content:encoded><![CDATA[<p>A type checker, as its name suggests, catches type mismatches: things like passing a <code>str</code> to a function that expects an <code>int</code>. But to understand your code's types, a type checker also has to understand its structure: control flow, scoping, class hierarchies, and more. This lets it detect a surprisingly wide range of issues that have nothing to do with <code>int</code> vs. <code>str</code>.</p>
<p>Here are five real categories of bugs that Pyrefly catches, none of which are straightforward type mismatches.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="1-the-silent-coroutine">1. The Silent Coroutine<a href="https://pyrefly.org/blog/surprising-errors/#1-the-silent-coroutine" class="hash-link" aria-label="Direct link to 1. The Silent Coroutine" title="Direct link to 1. The Silent Coroutine">​</a></h2>
<p>This code runs without error but silently fails to send a notification.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">send_notification</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">handle_request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    send_notification</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Request received"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># bug!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    process_request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>(See the code in the <a href="https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeSIqcp6AxgASYxj1wxYD66uALhJLVR9c6ABQBXNgCcOETInoR0PADT0AtvDQBzGArg8pASnoBaAHz0AciL31CDgDpTH6Z66YtiU3LS0cpGABHcXgeCWlZeUVlEwtrWwUHQnc3F3QqGgZPegALDExYAODQgwiYGTkFJR44yxt0PVT6FtZ2TC5efghBYTFJCqi1RxAAJRKw%2BkC-CAA3GEwRk3oAYnpscW0AQmbW719-QJCw8srMIxAVEDJAsChSQh5cdSgKNYAFUlv71gK8fHotBEkG04ikQggIhS6DWAGUYDA8jweMQ4IgAPTom7Me6EXBSbTo9jozC%2BODooHoEFgiEidH0MD4%2BioWaoaCobCwQHAiCg8F9ei4Yh9ODQsg8XIiUzzKRwSHoegAXnoIwAzIQAIyqkauADaFR8soAuq5xOgIOpiPieAtTJgINM%2BPMlfQAOS87iBV2ubg8UxHcQO20AaxgpFMqFofjgcBdroA7qgpOhvegQABfK6Rp0wABi0BgFDQWH%2BJHI6aAA" target="_blank" rel="noopener noreferrer">sandbox</a>.)</p>
<p>Spot the bug? <code>send_notification</code> is an async function, and calling it without <code>await</code> creates a coroutine object that is immediately discarded. The notification never sends. Python won't raise an exception — you'll just get a <code>RuntimeWarning</code> that's easy to miss in logs.</p>
<p>Pyrefly flags this as an <a href="https://pyrefly.org/en/docs/error-kinds/#unused-coroutine" target="_blank" rel="noopener noreferrer"><strong><code>unused-coroutine</code></strong></a>: the result of an async function call was neither awaited nor assigned to a variable. The fix is simple:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> send_notification</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user_id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Request received"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="2-the-forgotten-call">2. The Forgotten Call<a href="https://pyrefly.org/blog/surprising-errors/#2-the-forgotten-call" class="hash-link" aria-label="Direct link to 2. The Forgotten Call" title="Direct link to 2. The Forgotten Call">​</a></h2>
<p>This code attempts to check whether a user is authorized before performing an action.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">is_authorized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> User</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">bool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">role </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> ADMIN_ROLES</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">handle_admin_request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> User</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> Response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> is_authorized</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># bug!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> perform_action</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> Response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">403</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>(<a href="https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeSIAggCICyAkgHID6ASgPIAyAogMqIACKBDgAXANpiATgF0BAXgHiZAHSkr0ajQGMoqOHAEBVODCmIt6AdYFTcsQdMuXd%2Bw63jFc6UxfVWbTBgwAWZmCHQIUTCAClMoMAAaAW1cIMEI0QBKAQBaAD4BRm8YQUJy538gkJFmVABXUQALXCkIAC8YTBj603NjPpyCgTx7Pw0bWxhReqkrXrNCO1gBCIEaBhYOHl5KjWqBYjMwVoBbOu1RCG8YocKPOC8fUstJqWnZqwen0xiAJgADACsnssMEBE0MJhYHVMKcIsx3gBHerwUQ9PqCExmO4Cb7eXyvGwQGpwOqNFptTqYQQCADEI3qAHMAIREybWd4zOaHY5nC5XG4g-xvD48-HPGIAFgBAGYsiBEiAyO8wFBSIRRLhTlAKAyAAqkVXqgRoLB4fApbyQJmzVCC9CEDQM3gwGAQ0SiYhwRAAel9KuC6sIrSZvpg6F9mFw2jgvtS6BtdodvoEJykAlQADdUNBUNgVgmk1J7dcrLhiA64E70GRmt5clmzHAywoBCoQLLCABGWUdjTiMx2KRwVToeqRU5eKSiLq5TAQd6XCBNtsAcggTPQrRga4029EuWR9UXc4A1jBSLlUNptPBDIo1wB3VBzPfoEAAXyVN6uTYAYtAMAUGaOAECQ5CfkAA" target="_blank" rel="noopener noreferrer">sandbox</a>)</p>
<p>However, the action is always performed regardless of authorization. <code>is_authorized</code> (without parentheses) is a reference to the function object, which is always truthy. What was meant was <code>is_authorized(user)</code>.</p>
<p>Pyrefly catches this as a <a href="https://pyrefly.org/en/docs/error-kinds/#redundant-condition" target="_blank" rel="noopener noreferrer"><strong><code>redundant-condition</code></strong></a>: it knows that <code>is_authorized</code> is a function, and a function is always truthy, so the condition is redundant.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="3-the-breaking-rename">3. The Breaking Rename<a href="https://pyrefly.org/blog/surprising-errors/#3-the-breaking-rename" class="hash-link" aria-label="Direct link to 3. The Breaking Rename" title="Direct link to 3. The Breaking Rename">​</a></h2>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">BaseCache</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> default</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">object</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">object</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">RedisCache</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">BaseCache</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">self</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> fallback</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">object</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">object</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># bug!</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>(<a href="https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeSIAxlKnHAAQBCtMAwqpQBYyIA6ATr3T0R9TDDD0A5jAAuACjgwoYADT0A1jFKJ6cWf3XiwqAK5RZu3NgBWMSrPoBeegDlc6GAEp6AWgB89NZ2DnyCwqKRhNECQrHo1LQMAEowmBBw7Fww8sxKWdxeYUKRxtJyispqmtq6%2Bob0JlBQ2BwaVrb2ji7unj4BQZ2h9NGEIgDE9NimUgCEIKogZPwSUKSEsrgAtlAUkwAKpCtga3oYOAT0lB6QUqb8qLIQHoRCkwDKMDD0nLKyxHBEAB6IHLVbrXD8KRAmDoIGYXCUOBA67oW73R7POGNSH0VAAN1Q0FQ2FgVxuEDuDyeHiCxBp6Dgr3QZFknA8vnxMH4cCxznovBAAGZCABGIWCoQAbW5-EhcAAukJTOgIFtiJDZGlfOkVg4IFz%2BQBySnoSEwI1CM2yXwrACOpggK0wvi0pF8HEo8AYLiNAHdUPx0Jb0CAAL6LDhPLkAMWgMAoaCweCIZHDQA" target="_blank" rel="noopener noreferrer">sandbox</a>)</p>
<p>This looks harmless. <code>RedisCache.get</code> has the same types as <code>BaseCache.get</code>, just a different parameter name. But any caller using <code>cache.get("x", default=None)</code> will break when the cache is a <code>RedisCache</code>, because <code>RedisCache.get</code> doesn't have a parameter named <code>default</code>.</p>
<p>Pyrefly reports this as <a href="https://pyrefly.org/en/docs/error-kinds/#bad-override-param-name" target="_blank" rel="noopener noreferrer"><strong><code>bad-override-param-name</code></strong></a>: a subclass renamed a parameter that callers might be passing by keyword. This is a violation of the <a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle" target="_blank" rel="noopener noreferrer">Liskov Substitution Principle</a> because you can't safely substitute a <code>RedisCache</code> where a <code>BaseCache</code> is expected.</p>
<p>It's easy to dismiss this as unlikely, but it happens a lot in practice. Someone inherits from a base class, doesn't look at the parameter names carefully, and chooses a name that makes more sense for their implementation. The bug only surfaces when someone passes the argument by name, which might be rare enough that tests don't cover it.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="4-the-missing-case">4. The Missing Case<a href="https://pyrefly.org/blog/surprising-errors/#4-the-missing-case" class="hash-link" aria-label="Direct link to 4. The Missing Case" title="Direct link to 4. The Missing Case">​</a></h2>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> enum </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Enum</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">Color</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">Enum</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    RED </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"red"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    GREEN </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"green"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    BLUE </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"blue"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">to_hex</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">color</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Color</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">match</span><span class="token plain"> color</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token plain"> Color</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">RED</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"#ff0000"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">case</span><span class="token plain"> Color</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">GREEN</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"#00ff00"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)"># forgot BLUE!</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>(<a href="https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeS4ATrgLYAEM6ArvRDcbpQC50CizNADqVB6YaIDGUVHDh0AwriicAFPxYBKROPR09dAEq8AInQC8dQSEoxMVnfroBxI7wBy5yyADmNxvZFdfQAhABkAVV5PK2woJhgA0R1MGDA6LlwAfQALGHwVCSVORAUiyi0HfRpULglsukLlSm1Ax0cJGRhSpsIjYxbRNqG6Gy4mSl0rAGIwMAAGBbnEoKGOuC7FHpdedwGV4b1R8cmQKYXZheWDqbowTm9cHjDIgEIQABoQMhswKFJCDI0KAUG4ABVIPz%2BdDQWDw%2BAauHQkG84xqEERhFENwAyjAutkuFxiHBEAB6UnfVJ-Qj3UmMUmYXASOCkwpIiAoyhoxGk26cOioABuqGgqFiXTZyNRXHRulwxBliLgmPQZC42URAFpBTBKHBZdEQABmQgARiN9nQAG1ddQ9QBdURMdBsDjcWyazAQGwSGU6zwAcg56E4MADohDXE1NgAjkxvR6ANYwUia1ASCTwOQWAMAd1QE3D6BAAF9Pum-TAAGLQGAUGE4AgkcgloA" target="_blank" rel="noopener noreferrer">sandbox</a>)</p>
<p>Pyrefly warns about this with <a href="https://pyrefly.org/en/docs/error-kinds/#non-exhaustive-match" target="_blank" rel="noopener noreferrer"><strong><code>non-exhaustive-match</code></strong></a>: the <code>match</code> statement doesn't cover all members of <code>Color</code> and has no wildcard default. If someone passes <code>Color.BLUE</code>, Python falls through the match without entering any case, and the function implicitly returns <code>None</code>, which may then cause a confusing error somewhere downstream.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="5-the-misleading-comparison">5. The Misleading Comparison<a href="https://pyrefly.org/blog/surprising-errors/#5-the-misleading-comparison" class="hash-link" aria-label="Direct link to 5. The Misleading Comparison" title="Direct link to 5. The Misleading Comparison">​</a></h2>
<p>This code attempts to check whether a user is an admin.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">User</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">Admin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">User</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">check</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> User</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> user </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">is</span><span class="token plain"> Admin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># bug!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Welcome, admin!"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>(<a href="https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeSIAxlKnHAAQCqcMATovYVwDqvfrVaDAIKYAthHQAKZmwCUHLoV78VWGGHqUAFjEoBrKQFcW7Jqbn0AtAD56AOVzoYiNfXf0Imk208jxkhz0AMT02EYA5gCEbh5xxKySAC5S3CAA6jBQlLhiMAA09KgB6DEgciD5IGSsGlCkhEm5UBShAAqktWD19GhYePhaTpARRqyoSRBOyuihAMowMPTaSUnEcIgA9Js1dQ24rBGbMOibmLiUcJs56CNjE1On9GAHRQBuqNCo2LBDtxCjcaTJz0XDEYHoOAzMhJbROKxvNhwR70AC89DSAGZCABGTFpfgAbTYrAOcAAuvwjOgIGJiAckjBMFZMBBapRJoi0fQAOQA9AHGA8-gCpJWWoARyMbKZVn0MFIVlQlEo8AY6J5AHdUKx0ML0CAAL5VZWcmAAMWgMAofRwBBI5ENQA" target="_blank" rel="noopener noreferrer">sandbox</a>)</p>
<p>This checks whether <code>user</code> is <em>the same object</em> as the <code>Admin</code> class, not whether <code>user</code> is an instance of <code>Admin</code>. What was almost certainly meant was <code>isinstance(user, Admin)</code>. Pyrefly flags this as an <a href="https://pyrefly.org/en/docs/error-kinds/#unnecessary-comparison" target="_blank" rel="noopener noreferrer"><strong><code>unnecessary-comparison</code></strong></a>: using <code>is</code> to compare a value against a type is almost always a mistake.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-a-type-checker">Why a Type Checker?<a href="https://pyrefly.org/blog/surprising-errors/#why-a-type-checker" class="hash-link" aria-label="Direct link to Why a Type Checker?" title="Direct link to Why a Type Checker?">​</a></h2>
<p>You might wonder: shouldn't a linter catch these, rather than a type checker? Without an understanding of the types flowing through your program, a linter can see that you wrote <code>if f:</code>, but it might not be able to tell that <code>f</code> is a function, especially if <code>f</code> is imported. Pyrefly's type analysis allows it to report diagnostics that non-type-aware linters cannot detect with confidence.</p>
<p>To see the full list of error kinds Pyrefly supports and their severity levels, check out our <a href="https://pyrefly.org/en/docs/error-kinds/" target="_blank" rel="noopener noreferrer">error kinds documentation</a>. And if there's a bug pattern you wish we'd catch, let us know on <a href="https://github.com/facebook/pyrefly/issues" target="_blank" rel="noopener noreferrer">GitHub</a> or <a href="https://discord.gg/Cf7mFQtW7W" target="_blank" rel="noopener noreferrer">Discord</a>.</p>]]></content:encoded>
            <category>typechecking</category>
        </item>
        <item>
            <title><![CDATA[Adding Pyrefly Type Checking to Your Agentic Loop]]></title>
            <link>https://pyrefly.org/blog/pyrefly-agentic-loop/</link>
            <guid>https://pyrefly.org/blog/pyrefly-agentic-loop/</guid>
            <pubDate>Mon, 13 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to integrate Pyrefly type checking into your AI agent workflows using skills and hooks to automatically validate generated Python code.]]></description>
            <content:encoded><![CDATA[<p>Coding agents are writing more Python than ever. Tools like Claude, Copilot, Cursor, and Codex generate entire features with little-to-no user interaction. But in large projects, this generated code is prone to type errors, mismatched signatures, and subtle API misuse. Incorporating static analysis directly into the agentic loop can mean the difference between returning from your break with a production-ready feature or needing several more correction cycles.</p>
<p>Type checking sits right in the sweet spot for agents. It's fast enough for iterating small fixes, robust enough to catch issues of varying complexity, and actionable enough for an agent to make changes. In this post, we walk through how to integrate Pyrefly into your agentic workflow so that every piece of generated code can get type checked automatically.</p>
<p><strong>TL;DR</strong>: We recommend:</p>
<ul>
<li>Adding a skill file for your agent with an <code>agent.md</code> directive to ensure the project checks clean before finishing a feature.</li>
<li>If your model doesn't trigger this reliably, set up a hook on the Stop event.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="1-cli-skills">1. CLI Skills<a href="https://pyrefly.org/blog/pyrefly-agentic-loop/#1-cli-skills" class="hash-link" aria-label="Direct link to 1. CLI Skills" title="Direct link to 1. CLI Skills">​</a></h2>
<p>If you've been working with agentic workflows, you've likely defined skill files before. These files instruct the agent how to perform a specific task or use a specific tool. For a type checker like Pyrefly, you might want a simple skill like this:</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token front-matter-block punctuation" style="color:rgb(248, 248, 242)">---</span><span class="token front-matter-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token front-matter-block"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule">name</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token front-matter-block front-matter yaml language-yaml"> pyrefly</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token front-matter-block front-matter yaml language-yaml">cli</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token front-matter-block front-matter yaml language-yaml"></span><span class="token front-matter-block front-matter yaml language-yaml key atrule">description</span><span class="token front-matter-block front-matter yaml language-yaml punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token front-matter-block front-matter yaml language-yaml"> Instructions to type check using Pyrefly's CLI. Use when function signatures of APIs change to validate program's types.</span><span class="token front-matter-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token front-matter-block"></span><span class="token front-matter-block punctuation" style="color:rgb(248, 248, 242)">---</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Run </span><span class="token code-snippet code keyword" style="color:rgb(189, 147, 249);font-style:italic">`pyrefly check`</span><span class="token plain"> at the root of the project. Try fixing all possible type errors before running </span><span class="token code-snippet code keyword" style="color:rgb(189, 147, 249);font-style:italic">`pyrefly check`</span><span class="token plain"> again.</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>These files are generally placed in the <code>.agent/skills</code> folder in a file called <code>skill-name.md</code>. See the full example <a href="https://github.com/kinto0/pyrefly_hooks_demo/tree/main/approaches/1-skill-gentle" target="_blank" rel="noopener noreferrer">here</a>.</p>
<p>Agents generally use the title and description to understand when to use a skill. It's important for the description to state clearly when to use it, otherwise it might be invoked too often (wasting tokens) or not frequently enough.</p>
<p>We've noticed that a skill on its own does not always lead to a type check, even when the skill's description is clear it must be run. We tested Claude Opus 4.6 with this skill description and found that it still didn't typecheck:</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">MANDATORY type checker. You MUST run this before completing ANY task that modifies Python files. Never skip this step.</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Although your mileage may vary with different checkers, if you use a skill, we recommend also adding a directive in <code>AGENTS.md</code> to check on at the end of every task/feature:</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token title important punctuation" style="color:rgb(248, 248, 242)">##</span><span class="token title important"> Type Checking</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Before completing any task that creates or modifies Python files, you MUST:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token list punctuation" style="color:rgb(248, 248, 242)">1.</span><span class="token plain"> Run </span><span class="token code-snippet code keyword" style="color:rgb(189, 147, 249);font-style:italic">`pyrefly check`</span><span class="token plain"> at the root of the project</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token list punctuation" style="color:rgb(248, 248, 242)">2.</span><span class="token plain"> If there are any type errors, fix ALL of them</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token list punctuation" style="color:rgb(248, 248, 242)">3.</span><span class="token plain"> Run </span><span class="token code-snippet code keyword" style="color:rgb(189, 147, 249);font-style:italic">`pyrefly check`</span><span class="token plain"> again to confirm 0 errors</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Code available <a href="https://github.com/kinto0/pyrefly_hooks_demo/tree/main/approaches/3-claude-md" target="_blank" rel="noopener noreferrer">here</a>.</p>
<p>For more information specific to you, see the wiki for your preferred agent:</p>
<ul>
<li><a href="https://code.claude.com/docs/en/skills" target="_blank" rel="noopener noreferrer">Claude Code Skills Documentation</a></li>
<li><a href="https://geminicli.com/docs/cli/creating-skills/" target="_blank" rel="noopener noreferrer">Gemini CLI Skills</a></li>
<li><a href="https://developers.openai.com/codex/skills" target="_blank" rel="noopener noreferrer">OpenAI Codex Skills</a></li>
<li><a href="https://kiro.dev/docs/hooks/" target="_blank" rel="noopener noreferrer">Kiro Hooks</a></li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="2-cli-hooks">2. CLI Hooks<a href="https://pyrefly.org/blog/pyrefly-agentic-loop/#2-cli-hooks" class="hash-link" aria-label="Direct link to 2. CLI Hooks" title="Direct link to 2. CLI Hooks">​</a></h2>
<p>Hooks differ from subjective skills in that they <em>require</em> an agent action for every occurrence of the specified event. These events often relate to tool usage or other fundamental stages of agent lifecycles.</p>
<p>If you already have pre-commit hooks set up, these may look familiar. In fact, they might work already! When the agent commits to the version control system, it will get pushback if these signals are enabled there.</p>
<p>For those not using pre-commit hooks, or for the agent to type check without making a commit, we recommend setting up agentic hooks.</p>
<p>We tested Pyrefly in Claude Code: In this tool, we recommend adding the typecheck command to the <code>Stop</code> event so it runs when the agent completes each task. To do this in Claude, add the following to your <code>.claude/settings.json</code>:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"hooks"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"Stop"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"hooks"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"command"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"command"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"pyrefly check &gt;&amp;2 || exit 2"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"timeout"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Available in our examples repository <a href="https://github.com/kinto0/pyrefly_hooks_demo/tree/main/approaches/4-hook-stop-exit2" target="_blank" rel="noopener noreferrer">here</a>.</p>
<p>Since Pyrefly outputs to stdout, we must redirect it to stderr for Claude to understand it. We must also return exit code 2 always for Claude to understand it.</p>
<p>Alternatively, you can have a hook schedule an agent to investigate the type errors:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"hooks"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"Stop"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"hooks"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"agent"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"prompt"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Verify that all Python files pass pyrefly type checking. Run `pyrefly check` and check the results. If there are any type errors, return ok: false with the errors. $ARGUMENTS"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token property">"timeout"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Available in our examples repository <a href="https://github.com/kinto0/pyrefly_hooks_demo/tree/main/approaches/6-agent-hook" target="_blank" rel="noopener noreferrer">here</a>.</p>
<p>It requires some custom instructions, but we've found this approach to work well. Note the "agent" hook seems similar to the "prompt" hook, but you must use the "agent" hook for Claude to be able to use the Pyrefly tool.</p>
<p>For more information, or specific details for your agent, see the documentation on hooks:</p>
<ul>
<li><a href="https://code.claude.com/docs/en/hooks" target="_blank" rel="noopener noreferrer">Claude Code Hooks</a></li>
<li><a href="https://geminicli.com/docs/hooks/" target="_blank" rel="noopener noreferrer">Gemini CLI Hooks</a></li>
<li><a href="https://developers.openai.com/codex/hooks" target="_blank" rel="noopener noreferrer">OpenAI Codex Hooks</a></li>
<li><a href="https://kiro.dev/docs/hooks/" target="_blank" rel="noopener noreferrer">Kiro Hooks</a></li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion">Conclusion<a href="https://pyrefly.org/blog/pyrefly-agentic-loop/#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion">​</a></h2>
<p>Developer tooling like Pyrefly is no longer only used by developers, it's useful for agents as well. Adding Pyrefly to your agentic workflow can be an easy way to improve the reliability of the code your agents produce.</p>
<p>Give these suggestions a try! Let us know which approach works best for you or if you have other solutions!</p>]]></content:encoded>
            <category>typechecking</category>
            <category>ai-agents</category>
            <category>developer-tools</category>
        </item>
        <item>
            <title><![CDATA[Python Type Checker Comparison: Speed and Memory Usage]]></title>
            <link>https://pyrefly.org/blog/speed-and-memory-comparison/</link>
            <guid>https://pyrefly.org/blog/speed-and-memory-comparison/</guid>
            <pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A benchmark comparison of speed and memory usage across Python type checkers including Pyrefly, Ty, Pyright, and Mypy.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Python Type Checker Comparison: Speed and Memory Usage" src="https://pyrefly.org/assets/images/memory-speed-hero-b6db7eaa6051c561f2d5af0442e098de.png" width="1376" height="768" class="img_ev3q"></p>
<p>We frequently hear from developers who are excited about the new generation of checkers (Ty and Pyrefly) and want to know how they stack up against each other and the existing, established tools (Mypy and Pyright). In this comparison, we'll focus purely on performance (time to run a full check) and talk a little bit about how design choices, architecture, and features impact that latency.</p>
<p>Evaluating a type checker's performance presents a challenge due to many variables, including diverse evaluation metrics and varying results across different operating systems and hardware configurations. Furthermore, unlike the official test suite for typing specification conformance, there is no universally adopted benchmark for performance used by all type checker maintainers.</p>
<p>Nonetheless, in this blog post, we will attempt to compare speed and memory usage when checking several dozen packages from the command line. We use this performance data to catch regressions in Pyrefly changes that impact OSS packages — we previously only measured type checking performance on internal projects with Pyre1.</p>
<p>Before we start, we'd like to caution that these numbers are only a snapshot at the time of publication and will be out of date quickly. Performance numbers can swing wildly from release to release, because the type checkers are under active development.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-performance-matters">Why Performance Matters<a href="https://pyrefly.org/blog/speed-and-memory-comparison/#why-performance-matters" class="hash-link" aria-label="Direct link to Why Performance Matters" title="Direct link to Why Performance Matters">​</a></h2>
<p>Fast tooling is an essential part of an efficient developer flow. This is even more important in the age of agentic AI in our IDEs and CLI; they need diagnostics to generate high quality code. The faster, the better.</p>
<p>As codebases grow, the difference compounds. A package like numpy takes Pyright over a minute (70.9s) and over 3 GB of RAM to check on a MacBook M4. Pyrefly checks it in 4.8 seconds with 1 GB of RAM. When you multiply that across dozens of repos in CI, the gap matters.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-benchmark">The Benchmark<a href="https://pyrefly.org/blog/speed-and-memory-comparison/#the-benchmark" class="hash-link" aria-label="Direct link to The Benchmark" title="Direct link to The Benchmark">​</a></h2>
<p>We extended the <a href="https://python-type-checking.com/" target="_blank" rel="noopener noreferrer">Python Package Type Coverage Report</a> to include a <a href="https://python-type-checking.com/typecheck_benchmark/" target="_blank" rel="noopener noreferrer">type checker performance dashboard</a> that measures this systematically and shows trends as the new checkers ship regular improvements. Every day, we run the type checkers against the same 53 popular open-source Python packages on GitHub Actions (4 cores, 16 GB of RAM) and record execution time and peak memory usage.</p>
<p>The methodology is straightforward: clone the repo and install dependencies (best effort), run each checker with default settings (with the exception of Mypy, which is run in strict mode to match the other checkers), measure wall-clock time and memory. Each checker gets a 5-minute timeout per package.</p>
<p>The dashboard is useful for automation, trend analysis, and reflects the performance you should expect for your CI-based type checks.</p>
<p>For the sake of this blog and a more accurate reflection of day-to-day programming, we will share the benchmark from a local run on a MacBook M4 with 64 GB of RAM. In our local runs, we check each package 5x with a warmup run beforehand, but we do not cache results between runs (meaning that we don't use Mypy's warm check feature, and all checkers check the whole project from scratch each time).</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-results">The Results<a href="https://pyrefly.org/blog/speed-and-memory-comparison/#the-results" class="hash-link" aria-label="Direct link to The Results" title="Direct link to The Results">​</a></h2>
<p>We tested Pyrefly 0.60.0, Ty 0.0.29, Pyright 1.1.408, and Mypy 1.19.1 across 53 popular open-source packages. Here's how the checkers compare head-to-head:</p>
<p><strong>Small/medium packages</strong> (under 1s for the fastest checker — e.g., flask, requests, sentry-sdk): Pyrefly and Ty both complete in a fraction of a second, typically within 100ms of each other. Pyright and Mypy take 2-20x longer on these same packages.</p>
<p><strong>Large packages</strong> (1-5s for the fastest checker — e.g., pandas, tensorflow, homeassistant): Pyrefly and Ty remain in the same ballpark, usually finishing within a few seconds of each other. Pyright and Mypy can take 10-50x longer — Pyright needs 144s for pandas, where Pyrefly takes 1.9s and Ty takes 1.5s.</p>
<p><strong>Extra-large packages</strong> (scipy, numpy, sympy): These stress-test overload resolution and union handling. Both new checkers stay under 5s on most of them, though individual packages can be outliers — Ty takes 30.6s on numpy (likely a bug that will be fixed in a later release), while Pyrefly takes 4.8s; conversely, Ty checks sympy in 1.6s vs. Pyrefly's 4.0s. Pyright can take over two minutes on these packages.</p>
<p>At Meta's scale, Pyrefly checks Instagram's 20 million line codebase in ~30 seconds. The bottom line: both Pyrefly and Ty are an order of magnitude faster than Pyright and Mypy, and they use significantly less memory doing it. Pyrefly uses less memory than Ty on 31 of 53 packages tested, and the gap widens on larger projects.</p>
<p>The variance in performance between checkers can be explained by several reasons:</p>
<ul>
<li><strong>Implementation language</strong> — Mypy is written in Python and Pyright is written in TypeScript, while the two newer checkers are written in Rust, which is a lower-level systems language.</li>
<li><strong>Architecture</strong> — Although Pyrefly and Ty are both written in Rust, their architectures are not the same. One of the reasons the Pyrefly team chose not to use Salsa (a Rust caching framework used by Ty) is to have more control over when memory is freed, which may explain the differences in memory usage.</li>
<li><strong>Inference</strong> — Features like return type inference (Pyrefly, Pyright) and empty container inference (Pyrefly, Mypy) all have a performance cost when checking un-annotated code. Doing less inference means faster results, but it also means a type checker may not catch as many bugs.</li>
<li><strong>Feature completeness</strong> — The set of <a href="https://pyrefly.org/blog/typing-conformance-comparison/" target="_blank" rel="noopener noreferrer">supported features</a> can impact both performance and memory usage.</li>
<li><strong>Maturity/Stability</strong> — By virtue of being less mature and not yet stable, the newer type checkers have more opportunities for optimization, but also have a higher chance of regressions between releases.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-some-packages-take-longer-to-check">Why Some Packages Take Longer to Check<a href="https://pyrefly.org/blog/speed-and-memory-comparison/#why-some-packages-take-longer-to-check" class="hash-link" aria-label="Direct link to Why Some Packages Take Longer to Check" title="Direct link to Why Some Packages Take Longer to Check">​</a></h2>
<table><thead><tr><th>Package</th><th>Pyright</th><th>Pyrefly</th><th>Ty</th><th>Mypy</th></tr></thead><tbody><tr><td>scipy</td><td>151.3s</td><td>4.8s</td><td>2.8s</td><td>20.0s</td></tr><tr><td>pandas</td><td>144.4s</td><td>1.9s</td><td>1.5s</td><td>13.9s</td></tr><tr><td>numpy</td><td>70.9s</td><td>4.8s</td><td>30.6s</td><td>8.0s</td></tr><tr><td>sympy</td><td>79.9s</td><td>4.0s</td><td>1.6s</td><td>15.5s</td></tr><tr><td>tensorflow</td><td>53.7s</td><td>2.4s</td><td>2.0s</td><td>30.6s</td></tr><tr><td>gradio</td><td>3.9s</td><td>0.3s</td><td>0.3s</td><td>21.3s</td></tr><tr><td>sqlalchemy</td><td>8.1s</td><td>0.5s</td><td>0.6s</td><td>4.5s</td></tr><tr><td>prefect</td><td>26.3s</td><td>0.8s</td><td>0.7s</td><td>14.8s</td></tr><tr><td>sentry-sdk</td><td>3.0s</td><td>0.3s</td><td>0.2s</td><td>17.8s</td></tr><tr><td>openai</td><td>3.9s</td><td>0.3s</td><td>4.6s</td><td>6.3s</td></tr><tr><td>flask</td><td>1.2s</td><td>0.2s</td><td>0.2s</td><td>2.0s</td></tr><tr><td>homeassistant</td><td>51.3s</td><td>2.2s</td><td>2.2s</td><td>Crash</td></tr></tbody></table>
<p>Not all packages are equal from a type checker's perspective. Several factors drive the spread:</p>
<ul>
<li><strong>Codebase size.</strong> Some projects like <em>yt-dlp</em> and <em>transformers</em> have a lot of code to analyze.</li>
<li><strong>Dependency graphs.</strong> Packages like airflow pull in many other packages as dependencies.</li>
<li><strong>Unions and overloads.</strong> These language features tend to be expensive to check, so many checkers put a cap on the maximum size of unions to keep performance more predictable.</li>
<li><strong>Type coverage.</strong> Not all packages have type annotations or stubs, and the ones that do may not be fully annotated. Packages with low type coverage tend to be slower to check, because inferring types for un-annotated code can be expensive.</li>
<li><strong>Configuration.</strong> Disabling certain features like return type inference will typically improve performance at the cost of more precise types. When return type inference is disabled, un-annotated functions will return <code>Any</code>.</li>
</ul>
<p>The biggest spreads we see are on scientific computing packages. Pyright's performance suffers the most here: scipy takes 151 seconds, pandas 144 seconds, and sympy 80 seconds. These packages tend to be the most challenging to type check, containing methods with dozens of overloads and type aliases of large unions. These packages also tend to have un-annotated code, which compounds the problem because type checkers that infer return types (Pyright and Pyrefly) may produce large unions for un-annotated functions.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="pyrefly-design-choices">Pyrefly Design Choices<a href="https://pyrefly.org/blog/speed-and-memory-comparison/#pyrefly-design-choices" class="hash-link" aria-label="Direct link to Pyrefly Design Choices" title="Direct link to Pyrefly Design Choices">​</a></h2>
<p>There are two important performance questions that should be considered when building a type checker:</p>
<ul>
<li>How fast is fast enough?</li>
<li>Do we prioritize speed over everything else, or are there features we want to include even at a performance cost?</li>
</ul>
<p>For Pyrefly, "fast enough" means being able to check most projects in seconds, and smaller projects in a fraction of a second. We've largely achieved that goal, checking Instagram's 20 million line codebase in ~30 seconds.</p>
<p>Beyond that, we want to be efficient with memory to support local development workflows, and support advanced inference features to provide greater type safety to users. These priorities are reflected in our design choices:</p>
<ul>
<li>By having inference features be on-by-default, we aim to provide greater type safety out of the box. To mitigate the performance costs of that decision, we have guardrails like limiting the width of unions inferred for returns.</li>
</ul>
<p>In the last few months, we've shipped improvements that have more than doubled Pyrefly's speed and significantly reduced its memory usage. We believe there is still significant room for improvement before Pyrefly reaches its theoretical performance ceiling, and we have a few more tricks up our sleeves to improve performance in coming months.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="try-it-yourself">Try It Yourself<a href="https://pyrefly.org/blog/speed-and-memory-comparison/#try-it-yourself" class="hash-link" aria-label="Direct link to Try It Yourself" title="Direct link to Try It Yourself">​</a></h2>
<p>The benchmark runs on GitHub Actions with standard runners. Your local machine, your codebase, and your configuration will produce different results. Install the new checkers in your project or clone the repository and give the benchmark a try:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Clone the repo</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">git clone https://github.com/facebook/pyrefly.git</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">cd pyrefly/scripts/benchmark</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Create a venv and install type checkers</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">python -m venv .venv</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">source .venv/bin/activate</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">pip install pyright pyrefly ty mypy</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Run the typecheck benchmark on a few packages</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">python typecheck_benchmark.py --packages 5</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Run specific packages</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">python typecheck_benchmark.py --package-names requests flask django</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Or run it on all 53 tracked packages</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">python typecheck_benchmark.py</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="picking-the-right-type-checker-for-your-project">Picking the Right Type Checker for Your Project<a href="https://pyrefly.org/blog/speed-and-memory-comparison/#picking-the-right-type-checker-for-your-project" class="hash-link" aria-label="Direct link to Picking the Right Type Checker for Your Project" title="Direct link to Picking the Right Type Checker for Your Project">​</a></h2>
<p>There are a number of factors to consider when choosing a type checker. The good news is that all of the new Rust-based type checkers are fast, guaranteeing performance for your codebase no matter which one you pick. We've done additional comparisons in previous posts to help look at other features that you might consider when choosing a checker: <a href="https://pyrefly.org/blog/typing-conformance-comparison/" target="_blank" rel="noopener noreferrer">conformance to the typing specification</a>, product choices around <a href="https://pyrefly.org/blog/container-inference-comparison/" target="_blank" rel="noopener noreferrer">inference</a>, <a href="https://pyrefly.org/blog/type-narrowing/" target="_blank" rel="noopener noreferrer">narrowing</a>, and IDE <a href="https://pyrefly.org/en/docs/IDE-features/" target="_blank" rel="noopener noreferrer">feature support</a>. You can also review the <a href="https://positron.posit.co/blog/posts/2026-03-31-python-type-checkers/" target="_blank" rel="noopener noreferrer">criteria that Positron used</a> when evaluating the Language Server Protocol (LSP) for their IDE, which includes factors like GitHub issue response time and community health, among others.</p>
<p>Whether you are new to type checking or have been here since day one of PEP-484, we hope you can get the benefits of types in your Python. If you try Pyrefly, we would love a ping in Discord or a new issue on GitHub with feedback.</p>
<p><em>Source code and methodology at <a href="https://github.com/facebook/pyrefly/blob/main/scripts/benchmark/README.md" target="_blank" rel="noopener noreferrer">https://github.com/facebook/pyrefly/blob/main/scripts/benchmark/README.md</a>. Raw benchmark data for this post is available at <a href="https://github.com/facebook/pyrefly/blob/main/scripts/benchmark/results/2026-04-08-benchmark-macbook-m4.json" target="_blank" rel="noopener noreferrer">scripts/benchmark/results/2026-04-08-benchmark-macbook-m4.json</a>.</em></p>]]></content:encoded>
            <category>typechecking</category>
        </item>
        <item>
            <title><![CDATA[How to Support Notebooks in a Language Server]]></title>
            <link>https://pyrefly.org/blog/notebook/</link>
            <guid>https://pyrefly.org/blog/notebook/</guid>
            <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[How we added Jupyter notebook support to Pyrefly, and what other language servers can learn from the approach.]]></description>
            <content:encoded><![CDATA[<p>Jupyter notebooks have become an essential tool for Python developers. Their interactive, cell-based workflow makes them ideal for rapid prototyping, data exploration, and scientific computing: areas where you want to tweak a small part of the code and see the updated results inline, without waiting for the whole program to run. Notebooks are the primary way many data scientists and ML engineers write Python, and interactive workflows are highlighted in new data science oriented IDEs like <a href="https://positron.posit.co/" target="_blank" rel="noopener noreferrer">Positron</a>.</p>
<p>But notebooks have historically been second-class citizens when it comes to IDE features. Language servers, which implement the <a href="https://microsoft.github.io/language-server-protocol/" target="_blank" rel="noopener noreferrer">Language Server Protocol</a> (LSP) to provide features like go-to-definition, hover, and diagnostics across editors, were designed with regular source files in mind. The language server protocol did not include notebook synchronization methods until five years after it was created, and the default Jupyter Notebook experience is missing many of the aforementioned IDE features.</p>
<p>In this post, we'll discuss how language servers have been adapted to work with notebooks, how the LSP spec evolved to support them natively, and how we implemented notebook support in <a href="https://github.com/facebook/pyrefly" target="_blank" rel="noopener noreferrer">Pyrefly</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-language-servers-handled-notebooks-before-lsp-317">How Language Servers Handled Notebooks Before LSP 3.17<a href="https://pyrefly.org/blog/notebook/#how-language-servers-handled-notebooks-before-lsp-317" class="hash-link" aria-label="Direct link to How Language Servers Handled Notebooks Before LSP 3.17" title="Direct link to How Language Servers Handled Notebooks Before LSP 3.17">​</a></h2>
<p>Prior to <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#version_3_17_0" target="_blank" rel="noopener noreferrer">LSP 3.17</a> (released in 2022), the protocol only supported regular source files. Editors had to write custom adapters to make language servers work with notebooks at all.</p>
<p>The most common approach, used by both <a href="https://jupyter.org/enhancement-proposals/72-language-server-protocol/language-server-protocol.html" target="_blank" rel="noopener noreferrer">JupyterLab's LSP extension</a> and Meta's internal <a href="https://engineering.fb.com/2024/09/17/data-infrastructure/inside-bento-jupyter-notebooks-at-meta/" target="_blank" rel="noopener noreferrer">Bento notebooks</a>, is a <strong>proxy layer</strong> that intercepts and rewrites all language server requests and responses. The proxy concatenates every cell in the notebook into a single virtual document, and presents that view to the language server. Positions relative to a cell are mapped to positions in the concatenated file before being sent to the server, and positions in responses are mapped back to the corresponding cell before being returned to the editor. From the language server's perspective, it's just analyzing a regular file.</p>
<p><img decoding="async" loading="lazy" alt="notebook proxy" src="https://pyrefly.org/assets/images/file_based-0efa419f0f76c896bfe23992636e4f50.png" width="1454" height="706" class="img_ev3q"></p>
<p>This approach worked, but it pushed all the complexity onto the editor or a middleware layer. Every editor that wanted notebook support had to independently implement cell-stitching and position-mapping logic, but it was language-agnostic and allowed language servers to be used with notebooks at a time when the LSP did not support it directly.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="notebook-support-in-lsp-317">Notebook Support in LSP 3.17<a href="https://pyrefly.org/blog/notebook/#notebook-support-in-lsp-317" class="hash-link" aria-label="Direct link to Notebook Support in LSP 3.17" title="Direct link to Notebook Support in LSP 3.17">​</a></h2>
<p><a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#notebookDocument_synchronization" target="_blank" rel="noopener noreferrer">LSP 3.17</a> introduced four new operations for notebook documents: <code>notebookDocument/didOpen</code>, <code>notebookDocument/didChange</code>, <code>notebookDocument/didSave</code>, and <code>notebookDocument/didClose</code>.</p>
<p>The key difference from regular <code>textDocument</code> operations is how content is represented. Instead of sending a single file's worth of text, <code>notebookDocument/didOpen</code> sends the contents of each cell as a separate document, with a unique URI assigned to each cell by the editor. <code>notebookDocument/didChange</code> encodes information about added and deleted cells, along with content changes within individual cells.</p>
<p>By moving notebook awareness into the protocol itself, the spec lets language servers handle notebook semantics directly rather than relying on each editor to implement its own proxy layer.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="approaches-to-implementation">Approaches to Implementation<a href="https://pyrefly.org/blog/notebook/#approaches-to-implementation" class="hash-link" aria-label="Direct link to Approaches to Implementation" title="Direct link to Approaches to Implementation">​</a></h2>
<p>The LSP spec doesn't prescribe how a language server should represent notebooks internally. If you already have a language server that works on regular files, there are at least two ways to extend it for notebooks:</p>
<ul>
<li>
<p><strong>File-based representation:</strong> Build the proxy logic directly into the language server. Concatenate all cell contents into a single virtual "file" and maintain a mapping between each cell and the corresponding lines. The core analysis stays the same, you just convert positions on the way in and on the way out.</p>
</li>
<li>
<p><strong>Cell-based representation:</strong> Treat each cell as a separate "file," with information about cell ordering. Each cell implicitly imports all symbols defined in previous cells. This allows each cell to be analyzed independently.</p>
</li>
</ul>
<p><img decoding="async" loading="lazy" alt="cell based representation" src="https://pyrefly.org/assets/images/cell_based-d2cf218254484495d2ade607d68b6c9f.png" width="1396" height="710" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-we-did-it-in-pyrefly">How We Did It in Pyrefly<a href="https://pyrefly.org/blog/notebook/#how-we-did-it-in-pyrefly" class="hash-link" aria-label="Direct link to How We Did It in Pyrefly" title="Direct link to How We Did It in Pyrefly">​</a></h2>
<p>For the initial implementation in Pyrefly, we went with the <strong>file-based approach</strong> because the changes were less invasive. Everything works exactly the same as with regular files; we just map positions between cells and the concatenated source before and after each operation. This is similar to how <a href="https://github.com/pappasam/jedi-language-server" target="_blank" rel="noopener noreferrer">jedi-language-server</a> and <a href="https://github.com/astral-sh/ruff" target="_blank" rel="noopener noreferrer">Ruff</a> handle notebooks.</p>
<p>That said, this isn't set in stone and we may revisit the decision in the future. The cell-based approach has some efficiency benefits: if a cell changes, you only need to re-check subsequent cells if the cell's exported types changed; preceding cells wouldn't need to be re-checked at all. With a file-based approach, the language server has to re-check the entire notebook whenever any cell changes. When a client requests diagnostics for a single cell, the server has to retrieve diagnostics for the whole notebook and filter the results to the requested cell.</p>
<p>In practice, this extra work hasn't been a problem for Pyrefly, especially if the data is cached. The average notebook is small compared to the scale Pyrefly is designed to handle.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-about-alternative-notebook-formats">What About Alternative Notebook Formats?<a href="https://pyrefly.org/blog/notebook/#what-about-alternative-notebook-formats" class="hash-link" aria-label="Direct link to What About Alternative Notebook Formats?" title="Direct link to What About Alternative Notebook Formats?">​</a></h2>
<p>The Jupyter <code>.ipynb</code> format (a JSON file containing cells, outputs, and metadata) isn't the only notebook format anymore. Projects like <a href="https://quarto.org/" target="_blank" rel="noopener noreferrer">Quarto</a> use markdown files with embedded code blocks, while <a href="https://marimo.io/" target="_blank" rel="noopener noreferrer">Marimo</a> stores notebooks as plain Python files where cells are individual functions. These formats are more version-control-friendly and integrate better with standard developer tooling.</p>
<p>Interestingly, these alternative formats face the same fundamental challenge when it comes to IDE features. Under the hood, their editor extensions still need to stitch cells together into a coherent view for the language server, much like the legacy proxy approach we described earlier. The architectural discussion in this post applies just as much to these formats as it does to traditional Jupyter notebooks; whether an editor or a language server handles the cell-to-file mapping, someone has to do it. Furthermore, Python is not the only language that is used in notebooks. Julia and R also benefit from interactive environments since they are used for many of the same tasks as Python. Other notebook environments support SQL, JavaScript, and more. If you have a working language server for a particular language and want to add notebook support, the approaches discussed here may be applied.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="try-it-out">Try It Out<a href="https://pyrefly.org/blog/notebook/#try-it-out" class="hash-link" aria-label="Direct link to Try It Out" title="Direct link to Try It Out">​</a></h2>
<p>We first shipped built-in notebook support for Pyrefly in v0.41.0, and it's available today in any editor that supports the language server protocol, including: <a href="https://marketplace.visualstudio.com/items?itemName=meta.pyrefly" target="_blank" rel="noopener noreferrer">VS Code</a>, <a href="https://positron.posit.co/guide-python.html#settings" target="_blank" rel="noopener noreferrer">Positron</a>, <a href="https://pypi.org/project/pyrefly/" target="_blank" rel="noopener noreferrer">JupyterLab</a>, and <a href="https://marimo.io/" target="_blank" rel="noopener noreferrer">Marimo</a>.</p>
<p>We'd love for you to try it out and let us know how it goes. If you run into any issues, please <a href="https://github.com/facebook/pyrefly/issues" target="_blank" rel="noopener noreferrer">file a bug report</a>: it helps us a lot, since real-world feedback is the fastest way for us to improve our notebook support.</p>]]></content:encoded>
            <category>language-server</category>
            <category>IDE</category>
        </item>
        <item>
            <title><![CDATA[Reaching 100% Type Coverage by Deleting Unannotated Code]]></title>
            <link>https://pyrefly.org/blog/100-percent-type-coverage/</link>
            <guid>https://pyrefly.org/blog/100-percent-type-coverage/</guid>
            <pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[We finally solved the type coverage problem. The answer was surprisingly simple.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Hero image" src="https://pyrefly.org/assets/images/pyrefly-prune-hero-febf15297469711e0d56ea1bb18185ad.png" width="1424" height="752" class="img_ev3q"></p>
<p>At Pyrefly, we've always believed that type coverage is one of the most important indicators of code quality. Over the past year, we've worked closely with teams across large Python codebases here at Meta - improving performance, tightening soundness, and making type checking a seamless part of everyday development.</p>
<p>But one question kept coming up: <em>What would it take to reach 100% type coverage?</em></p>
<p>Today, we're excited to share a breakthrough.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-problem-with-almost-typed">The Problem with "Almost Typed"<a href="https://pyrefly.org/blog/100-percent-type-coverage/#the-problem-with-almost-typed" class="hash-link" aria-label="Direct link to The Problem with &quot;Almost Typed&quot;" title="Direct link to The Problem with &quot;Almost Typed&quot;">​</a></h2>
<p>Most teams plateau somewhere between 70–95% type coverage. The remaining gap is stubborn:</p>
<ul>
<li>Legacy modules with unclear ownership</li>
<li>Dynamic code paths that resist annotation</li>
<li>Utility functions that "just work" and nobody wants to touch</li>
</ul>
<p>Traditional approaches - manual annotation, AI-assisted typing, gradual typing strategies - help, but they all share a common flaw: They assume all code is worth keeping.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="a-new-approach-coverage-first-development">A New Approach: Coverage-First Development<a href="https://pyrefly.org/blog/100-percent-type-coverage/#a-new-approach-coverage-first-development" class="hash-link" aria-label="Direct link to A New Approach: Coverage-First Development" title="Direct link to A New Approach: Coverage-First Development">​</a></h2>
<p>We decided to reverse the problem. Instead of asking <em>"How do we annotate the remaining code?"</em>, we asked:</p>
<p><em>"What if the remaining code simply didn't exist?"</em></p>
<p>Introducing <strong>Pyrefly Prune™</strong>, a new experimental mode that guarantees <strong>100% type coverage</strong> by removing any code that isn't fully typed.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-it-works">How It Works<a href="https://pyrefly.org/blog/100-percent-type-coverage/#how-it-works" class="hash-link" aria-label="Direct link to How It Works" title="Direct link to How It Works">​</a></h2>
<p>When you run:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">pyrefly check --enforce-total-coverage</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Pyrefly will:</p>
<ul>
<li>Identify any function, class, or module lacking complete type annotations</li>
<li>Recursively analyze dependencies to ensure soundness</li>
<li>Safely remove unannotated code paths</li>
<li>Recompute coverage (now 100%)</li>
</ul>
<p>The result is a codebase consisting entirely of fully typed, sound, and verifiable Python.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="early-results">Early Results<a href="https://pyrefly.org/blog/100-percent-type-coverage/#early-results" class="hash-link" aria-label="Direct link to Early Results" title="Direct link to Early Results">​</a></h2>
<p>We piloted this approach on several large repositories.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="case-study-web-serving-service">Case Study: Web Serving Service<a href="https://pyrefly.org/blog/100-percent-type-coverage/#case-study-web-serving-service" class="hash-link" aria-label="Direct link to Case Study: Web Serving Service" title="Direct link to Case Study: Web Serving Service">​</a></h3>
<ul>
<li>Before:<!-- -->
<ul>
<li>82% type coverage</li>
<li>1.2M lines of code</li>
</ul>
</li>
<li>After running Pyrefly Prune™:<!-- -->
<ul>
<li>100% type coverage</li>
<li>47 lines of code</li>
</ul>
</li>
</ul>
<p>The remaining code was described by one engineer as: "Incredibly well-typed. Also, it doesn't do anything anymore."</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="case-study-machine-learning-pipeline">Case Study: Machine Learning Pipeline<a href="https://pyrefly.org/blog/100-percent-type-coverage/#case-study-machine-learning-pipeline" class="hash-link" aria-label="Direct link to Case Study: Machine Learning Pipeline" title="Direct link to Case Study: Machine Learning Pipeline">​</a></h3>
<ul>
<li>Before: complex data ingestion, feature engineering, training loops</li>
<li>After: a single, beautifully annotated function:</li>
</ul>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> x</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Training time improved dramatically.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="developer-experience-improvements">Developer Experience Improvements<a href="https://pyrefly.org/blog/100-percent-type-coverage/#developer-experience-improvements" class="hash-link" aria-label="Direct link to Developer Experience Improvements" title="Direct link to Developer Experience Improvements">​</a></h2>
<p>Surprisingly, developers reported several benefits:</p>
<ul>
<li><strong>Zero type errors</strong> (by construction)</li>
<li><strong>Blazing-fast CI</strong> (nothing left to check)</li>
<li><strong>Perfect IDE navigation</strong> (all remaining symbols are trivially understood)</li>
<li><strong>Reduced cognitive load</strong> ("There's just... less")</li>
</ul>
<p>One team noted that onboarding time dropped from weeks to seconds.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="soundness-perfected">Soundness, Perfected<a href="https://pyrefly.org/blog/100-percent-type-coverage/#soundness-perfected" class="hash-link" aria-label="Direct link to Soundness, Perfected" title="Direct link to Soundness, Perfected">​</a></h2>
<p>By eliminating all untyped code, Pyrefly Prune™ achieves a new level of soundness:</p>
<ul>
<li>No implicit Any</li>
<li>No missing imports</li>
<li>No edge cases</li>
</ul>
<p>In fact, we believe this is the first system to achieve <strong>total program soundness by strategic absence</strong>. We call this approach <strong>Subtractive Soundness™</strong>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="advanced-configuration">Advanced Configuration<a href="https://pyrefly.org/blog/100-percent-type-coverage/#advanced-configuration" class="hash-link" aria-label="Direct link to Advanced Configuration" title="Direct link to Advanced Configuration">​</a></h2>
<p>For teams that want more control, we offer fine-grained pruning strategies:</p>
<div class="language-toml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-toml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">tool.pyrefly.prune</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">aggressiveness</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"maximal"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">preserve_comments</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">preserve_readme</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"optional"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># We also support selective retention:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">keep_if_funny</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">keep_if_git_blame_is_yours</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token boolean">true</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="roadmap">Roadmap<a href="https://pyrefly.org/blog/100-percent-type-coverage/#roadmap" class="hash-link" aria-label="Direct link to Roadmap" title="Direct link to Roadmap">​</a></h2>
<p>We're actively working on several enhancements:</p>
<ul>
<li><strong>Auto-replacement with TODOs</strong>
Replace deleted code with thoughtfully written TODO comments that no one will ever complete</li>
<li><strong>LLM-assisted hallucinated implementations</strong>
When code is deleted, generate a plausible replacement that type-checks but may not correspond to reality</li>
<li><strong>Executive dashboard metrics</strong>
Track KPIs like:<!-- -->
<ul>
<li>Lines of code deleted per sprint</li>
<li>% of engineers quietly panicking</li>
<li>Revenue impact (TBD)</li>
</ul>
</li>
</ul>
<p><img decoding="async" loading="lazy" alt="Pyrefly - Eliminate Your Bugs" src="https://pyrefly.org/assets/images/eliminate-your-bugs-0d7b294daa4cdc9ca06e8492b6609958.png" width="4996" height="1766" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="frequently-asked-questions">Frequently Asked Questions<a href="https://pyrefly.org/blog/100-percent-type-coverage/#frequently-asked-questions" class="hash-link" aria-label="Direct link to Frequently Asked Questions" title="Direct link to Frequently Asked Questions">​</a></h2>
<p><strong>Q: Does this delete production code?</strong>
A: Yes.</p>
<p><strong>Q: Is there a rollback mechanism?</strong>
A: We recommend strong git hygiene and emotional resilience.</p>
<p><strong>Q: What about business logic?</strong>
A: Fully typed business logic is preserved. Untyped business logic was, by definition, suspect.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="looking-ahead">Looking Ahead<a href="https://pyrefly.org/blog/100-percent-type-coverage/#looking-ahead" class="hash-link" aria-label="Direct link to Looking Ahead" title="Direct link to Looking Ahead">​</a></h2>
<p>As AI agents increasingly generate and modify code, we believe the future belongs to systems that can maintain strong guarantees. Sometimes, the best way to ensure correctness... is to remove uncertainty entirely.</p>
<p>And if a small amount of functionality happens to be removed along the way - that's a trade-off we're finally ready to make.</p>
<p><strong>Pyrefly Prune™</strong> is available today behind the <code>--enforce-total-coverage</code> flag. Happy April Fools!</p>]]></content:encoded>
            <category>typechecking</category>
            <category>coverage</category>
        </item>
        <item>
            <title><![CDATA[Lessons from Pyre that Shaped Pyrefly]]></title>
            <link>https://pyrefly.org/blog/lessons-from-pyre/</link>
            <guid>https://pyrefly.org/blog/lessons-from-pyre/</guid>
            <pubDate>Wed, 18 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Lessons from developing Pyre that influenced how we designed Pyrefly.]]></description>
            <content:encoded><![CDATA[<p><a href="https://github.com/facebook/pyrefly" target="_blank" rel="noopener noreferrer">Pyrefly</a> is a next-generation Python type checker and language server, designed to be extremely fast and featuring advanced refactoring and type inference capabilities. This isn’t the Pyrefly team’s first time building a type checker for Python: Pyrefly is a successor to <a href="https://pyre-check.org/" target="_blank" rel="noopener noreferrer">Pyre</a>, the previous type checker our team developed.</p>
<p>A lot of Pyrefly’s design comes directly from our experience with Pyre. Some things worked well at scale, while other things were harder to live with day-to-day. After running a type checker on massive Python codebases for a long time, we got a clearer sense of which trade-offs actually mattered to users.</p>
<p>This post is a write-up of a few lessons from Pyre that influenced how we approached Pyrefly.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-origins-of-pyre">The Origins of Pyre<a href="https://pyrefly.org/blog/lessons-from-pyre/#the-origins-of-pyre" class="hash-link" aria-label="Direct link to The Origins of Pyre" title="Direct link to The Origins of Pyre">​</a></h2>
<p>Pyre started around 2017, with roots in <a href="https://pyre-check.org/docs/pysa-basics/" target="_blank" rel="noopener noreferrer">dataflow analysis tooling</a> for Instagram. The Python tooling ecosystem looked pretty different back then:</p>
<ul>
<li>The <a href="https://typing.python.org/en/latest/spec/" target="_blank" rel="noopener noreferrer">typing specification</a> didn't exist, and the type system was loosely "specified" through a series of PEPs describing individual features.</li>
<li>Many modern typing features, such as literal types, dataclass transforms, param specs, type guards, etc. didn’t exist yet.</li>
<li>The <a href="https://microsoft.github.io/language-server-protocol/" target="_blank" rel="noopener noreferrer">Language Server Protocol</a> was new and not yet the clear standard.</li>
<li><a href="https://github.com/python/mypy" target="_blank" rel="noopener noreferrer">Mypy</a> was, for all practical purposes, the only viable Python type checker available at the time.</li>
<li>Type checking massive monorepos written in Python was still an unsolved problem.</li>
</ul>
<p>These factors shaped the development of Pyre, but resulted in some limitations later on as the Python tooling ecosystem matured.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="language-server-first-architecture">Language-server-first Architecture<a href="https://pyrefly.org/blog/lessons-from-pyre/#language-server-first-architecture" class="hash-link" aria-label="Direct link to Language-server-first Architecture" title="Direct link to Language-server-first Architecture">​</a></h2>
<p>Pyre didn’t start out as a language server; it was designed as a static analysis tool you run in CI (continuous integration) or from the CLI (command line interface).</p>
<p>Consequently, its design prioritized <strong>throughput</strong> to minimize the total wait time for users by maximizing parallel CPU usage. Adapting this CLI tool to function as an editor-integrated language server presented a significant challenge, as an editor environment demands high priority on <strong>latency</strong>, not throughput.</p>
<p>Applying Pyre's throughput-focused strategy to an IDE degraded the user experience, often consuming excessive machine resources and potentially freezing the editor interface while performing computations the user might not currently need.</p>
<p>This struggle with Pyre's latency in the IDE led to a temporary move to Pyright as the language server for Meta developers. While this provided some features, it introduced other user experience issues, such as mismatched type errors and inconsistent IDE hover results.</p>
<p>A core design goal for Pyrefly is to create a system flexible enough to handle both throughput-oriented workloads (like CLI type checking) and latency-sensitive workloads (required by the IDE/language server).</p>
<p>Additionally, error-recovery during parsing is much more important for a language server than for a CLI-only type checker. Language servers need to continue working when you're halfway through an edit, while type checkers are typically run only when you're done with an edit. Pyre had several options for parsing, including a Menhir-based parser and directly calling the CPython parser, but these were not robust to syntax errors. In Pyrefly, we use Astral's excellent <a href="https://github.com/astral-sh/ruff/tree/main/crates/ruff_python_parser" target="_blank" rel="noopener noreferrer">Ruff parser</a>, which is both speedy and battle-tested.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="ocaml-vs-rust-when-your-ecosystem-becomes-a-ceiling">OCaml vs. Rust: When Your Ecosystem Becomes a Ceiling<a href="https://pyrefly.org/blog/lessons-from-pyre/#ocaml-vs-rust-when-your-ecosystem-becomes-a-ceiling" class="hash-link" aria-label="Direct link to OCaml vs. Rust: When Your Ecosystem Becomes a Ceiling" title="Direct link to OCaml vs. Rust: When Your Ecosystem Becomes a Ceiling">​</a></h2>
<p>Pyre is implemented in OCaml. To bootstrap quickly, we borrowed a multi-processing and shared-memory library from mature OCaml type checkers at Facebook, like <a href="https://hacklang.org/" target="_blank" rel="noopener noreferrer">Hack</a> and <a href="https://flow.org/" target="_blank" rel="noopener noreferrer">Flow</a>. This architecture was necessary because, prior to OCaml 5, the runtime relied on a global lock (much like Python's GIL) that prevented true multi-threading. Consequently, achieving parallelism meant relying entirely on OS-level process forking and inter-process communication (IPC).</p>
<p>While this borrowed architecture accelerated our initial development, it eventually forced us into a structural straitjacket:</p>
<ul>
<li><strong>Data structure rigidity:</strong> Because of the IPC layer, shared data had to be crammed into string-to-string hashtables. Storing dynamic, complex objects (like self-referential types or closures) directly into shared memory incurred massive serialization and deserialization overhead.</li>
<li><strong>Algorithmic inflexibility:</strong> The IPC model is heavily biased toward throughput-oriented, strict "fork-join" workloads. This became increasingly awkward as we shifted toward latency-sensitive, demand-driven computational models that need to decide what to compute on the fly.</li>
</ul>
<p>When OCaml 5 introduced native multi-threading, upgrading the compiler was tempting. However, we realized that language-level concurrency cannot erase historical platform baggage. OCaml's ecosystem has historically struggled with Windows, and as we deployed Pyre more and more broadly, we ran into platform related frictions more and more often.</p>
<p>Rust radically changes this equation by treating Windows, macOS, and Linux as true equals out of the box. Adopting an ecosystem where cross-platform reliability is a foundational tenet eliminated massive operational friction and completely unified our development experience when hacking on Pyrefly.</p>
<p>Switching to Rust also allowed us to grow our community of contributors much faster than if we had continued with OCaml.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="irreversible-ast-lowering">Irreversible AST Lowering<a href="https://pyrefly.org/blog/lessons-from-pyre/#irreversible-ast-lowering" class="hash-link" aria-label="Direct link to Irreversible AST Lowering" title="Direct link to Irreversible AST Lowering">​</a></h2>
<p>Early in the development of Pyre, a technique was used to accelerate iteration: leveraging the semantic similarity or equivalence between certain Python syntax patterns.</p>
<p>For instance, the following pairs are functionally similar:</p>
<table><thead><tr><th>Construct 1</th><th>Construct 2</th></tr></thead><tbody><tr><td>A match statement</td><td>A series of chained if/else statements</td></tr><tr><td>The legacy union type syntax  <code>Union[X, Y]</code></td><td>The new syntax <code>X | Y</code></td></tr><tr><td>A NewType definition <code>X = NewType("X", Y)</code></td><td>A standard class definition <code>class X(Y): pass</code></td></tr><tr><td>The functional namedtuple definition <code>Point = namedtuple("Point", ["x", "y"])</code></td><td>A class-based namedtuple definition <code>class Point(NamedTuple)</code> with <code>x</code> and <code>y</code> as members</td></tr></tbody></table>
<p>Once code was implemented to handle one syntax pattern, support for similar or identical patterns could be rapidly added by using AST transformation passes to "lower" the latter into the former.</p>
<p>While useful, this technique introduced risks: If the original range and position information were not carefully preserved, the quality of diagnostics could suffer to the point where they could not adequately support language server features.</p>
<p>A significant example of taking this AST lowering too far in Pyre involved how variable scopes were handled. Pyre included an AST transformation pass designed to perform alpha-conversion: renaming syntactical definitions to resolve name conflicts.</p>
<p>Consider this original code:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> foo </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> x</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">test</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  y </span><span class="token operator">=</span><span class="token plain"> x</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  x </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"bar"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  z </span><span class="token operator">=</span><span class="token plain"> x</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The transformation process rewrote it as:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> foo </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> x</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">test</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  y </span><span class="token operator">=</span><span class="token plain"> foo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">x</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  _local_test_x </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"bar"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  z </span><span class="token operator">=</span><span class="token plain"> _local_test_x</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This transformation successfully resolved the name conflict between the imported <code>x</code> from <code>foo</code> and the local variable <code>x</code>. However, a crucial piece of information was lost: the transformed code obscures the fact that the <code>test()</code> function never directly references the module <code>foo</code>. Losing this kind of detail can significantly impacted certain language server operations, such as refactoring and find-references.</p>
<p>Learning from Pyre, we made a conscious effort to minimize AST fabrication or transformation in Pyrefly’s design.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="attribute-narrowing-soundness-vs-usability">Attribute narrowing: Soundness vs. Usability<a href="https://pyrefly.org/blog/lessons-from-pyre/#attribute-narrowing-soundness-vs-usability" class="hash-link" aria-label="Direct link to Attribute narrowing: Soundness vs. Usability" title="Direct link to Attribute narrowing: Soundness vs. Usability">​</a></h2>
<p>Pyre was originally designed with a strong focus on security, leading to a high emphasis on the soundness of its type checking. Security engineers generally favor a low tolerance for false negatives (missed errors), even if it means a higher tolerance for false positives (incorrect warnings). Consequently, Pyre is quite strict and opinionated about Python code style.</p>
<p>A prime example of this strictness is how Pyre handles type narrowing for attribute access. Consider this code:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">C</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">narrowing_test</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> C</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">isinstance</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    some_other_function</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">x   </span><span class="token comment" style="color:rgb(98, 114, 164)"># Pyre complains: `c.x` might be `str` here, incompatible with return type `int`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Technically, Pyre is correct. It cannot safely assume that <code>some_other_function()</code> won't mutate <code>c.x</code> to a different type, either directly or through an alias. To satisfy Pyre, users were forced to rewrite the code for safety:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">narrowing_test</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> C</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  c_x </span><span class="token operator">=</span><span class="token plain"> c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">x</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">isinstance</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">c_x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    some_other_function</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> c_x</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This strictness significantly impacted ergonomics, and complaints about this specific behavior were among the most frequent feedback we received.</p>
<p>In Pyrefly, we made a deliberate choice to allow this kind of type narrowing, prioritizing a better user experience for more general Python audiences. This represents a conscious trade-off: in a gradually typed language, enforcing extreme soundness can make the tool feel overly brittle.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="caching-cyclic-data-dependencies">Caching Cyclic Data Dependencies<a href="https://pyrefly.org/blog/lessons-from-pyre/#caching-cyclic-data-dependencies" class="hash-link" aria-label="Direct link to Caching Cyclic Data Dependencies" title="Direct link to Caching Cyclic Data Dependencies">​</a></h2>
<p>Pyre was designed with a strict, sequential phased structure for its internal computations to ensure fast, incremental type checking. To maintain simple and cost-effective cache invalidation, Pyre enforced a rigid rule: computations could only rely on the cached outputs of preceding phases. This method generated a perfectly acyclical dependency graph, enabling the system to update its cache via a single topological sweep.</p>
<p>However, while this strictly unidirectional approach kept the cache invalidation logic simple, it was fundamentally at odds with how Python code is written in the real world.</p>
<p>The problem is that effective type checking Python naturally relies on recursive or "same-phase" dependencies. Consider a simple class hierarchy:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">A</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">B</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">A</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">C</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">B</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>To compute the Method Resolution Order (MRO, the chain of class inheritance in Python) for <code>C</code>, it logically makes sense to just look up the already-computed MRO for <code>B</code>. However, Pyre couldn’t do this. Allowing <code>C</code> to depend on the cached result of B meant creating a dependency within the same phase. This violated the strict acyclic rule required by the caching system, meaning Pyre was unable to use its cache here. To get around this architectural wall, Pyre would have to recompute the MRO of <code>B</code> entirely from scratch, compute and cache <code>C</code>, and then throw <code>B</code>'s intermediate data away.</p>
<p>While simple structural checks like MRO incurred an acceptable cost for this redundant work, the absence of intermediate caching created significant performance bottlenecks for more complex computations. This was particularly true for heavyweight tasks, such as inferring attribute types in the presence of descriptors and decorators (which itself necessitates inferring the types of other attributes used as such). Consequently, the type checker ran extremely slowly when encountering decorator-heavy code patterns.</p>
<p>This limitation drove the foundational design of Pyrefly. Instead of forcing the dependency graph to remain acyclic, Pyrefly’s engine was built from the ground up to embrace cycles. By incorporating cycle detection and fixpoint resolution directly into the underlying system, Pyrefly can safely untangle and cache recursive dependencies. This algorithmic shift absorbs the complexity at the infrastructure level, allowing the tool to run fast while empowering the type checker to use smarter, more flexible inference heuristics without artificial restrictions.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion">Conclusion<a href="https://pyrefly.org/blog/lessons-from-pyre/#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion">​</a></h2>
<p>Building a type checker is ultimately a deeply pragmatic exercise. It is easy to get lost in the weeds of type theory, but at the end of the day, we are building a tool that developers have to live with every single day. Pyre taught us how to scale to massive monorepos, but more importantly, it showed us exactly where the friction lies.</p>
<p>Pyrefly is the direct result of those hard-earned lessons. We looked at the paper cuts, the IDE latency, and the rigid rules that frustrated our users in Pyre, and we made fixing them our foundational requirements. We chose usability over absolute soundness, and we built an architecture that embraces Python's dynamism instead of fighting it.</p>
<p>But the most important lesson we applied to Pyrefly isn't about code; it’s about how we build. Pyre was born as an internal Meta tool that was later released to the outside world. With Pyrefly, we flipped the script. We’ve been building this in the open, from day one, specifically for the broader Python ecosystem. We want to build a tool that actually makes your day-to-day coding experience better, and we can't do that in a vacuum.</p>
<p><strong>Come help us build Pyrefly!</strong> We’ve gratefully accepted hundreds of PRs from dozens of open-source contributors on <a href="https://github.com/facebook/pyrefly" target="_blank" rel="noopener noreferrer">Github</a> so far, and we wouldn’t be where we are without them. Jump into our <a href="https://discord.gg/Cf7mFQtW7W" target="_blank" rel="noopener noreferrer">Discord</a>, tell us what you are interested in, point out the rough edges, and help us figure out what lessons we need to learn next.</p>]]></content:encoded>
            <category>typechecking</category>
        </item>
        <item>
            <title><![CDATA[Python Type Checker Comparison: Typing Spec Conformance]]></title>
            <link>https://pyrefly.org/blog/typing-conformance-comparison/</link>
            <guid>https://pyrefly.org/blog/typing-conformance-comparison/</guid>
            <pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn what it means to conform to the Python typing spec, why it matters, and the conformance status of each type checker including Pyrefly, Ty, Pyright and Mypy.]]></description>
            <content:encoded><![CDATA[<p>When you write typed Python, you expect your type checker to follow the rules of the language. But how closely do today's type checkers actually follow the Python typing specification?</p>
<p>In this post, we look at what typing spec conformance means, how different type checkers compare, and what the conformance numbers don't tell you.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="a-brief-history-of-the-typing-specification">A brief history of the Typing Specification<a href="https://pyrefly.org/blog/typing-conformance-comparison/#a-brief-history-of-the-typing-specification" class="hash-link" aria-label="Direct link to A brief history of the Typing Specification" title="Direct link to A brief history of the Typing Specification">​</a></h2>
<p>Python's type system started with <a href="https://peps.python.org/pep-0484/" target="_blank" rel="noopener noreferrer">PEP 484</a>. At the time, the semantics of the type system were mostly defined by the reference implementation, mypy. In practice, whatever mypy implemented became the de-facto specification.</p>
<p>Over time, more type checkers appeared: Pyright (from Microsoft), Pytype (from Google), and Pyre (from Facebook), to name a few. Meanwhile, the type system itself continued to evolve through many different PEPs.</p>
<p>This created a problem: the semantics of the type system were scattered across many documents, and different type checkers implemented slightly different interpretations.
To address this, the typing community began consolidating the rules into a single reference: the <a href="https://typing.readthedocs.io/en/latest/spec/" target="_blank" rel="noopener noreferrer">Python typing specification</a>. The spec describes the semantics of typing features and includes a conformance test suite that type checkers can run to measure how closely they follow the spec.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-typing-conformance-test-suite">The typing conformance test suite<a href="https://pyrefly.org/blog/typing-conformance-comparison/#the-typing-conformance-test-suite" class="hash-link" aria-label="Direct link to The typing conformance test suite" title="Direct link to The typing conformance test suite">​</a></h2>
<p>The typing specification includes a <a href="https://github.com/python/typing/tree/main/conformance" target="_blank" rel="noopener noreferrer">conformance test suite</a> containing roughly a hundred test files. Each file encodes expectations about where a type checker should and shouldn't emit errors. The suite covers a wide range of typing features, from generics to overloads to type aliases.
Within each test, lines are annotated to indicate where an error is expected. When a type checker runs on a file, two types of mismatches can occur:</p>
<ul>
<li>
<p><strong>False positives:</strong> The type checker reports an error on a line that is not marked as an error in the test. This means the checker rejected code that the specification considers valid. For example, given this valid code:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">Movie</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">TypedDict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> extra_items</span><span class="token operator">=</span><span class="token builtin" style="color:rgb(189, 147, 249)">bool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">m</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Movie </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"name"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Blade Runner"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"novel_adaptation"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># OK</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>If the type checker doesn't support <code>extra_items</code>, it will flag <code>"novel_adaptation"</code> as an unknown key, even though <code>extra_items=bool</code> explicitly allows extra boolean-valued keys.</p>
</li>
<li>
<p><strong>False negatives:</strong> The test expects an error, but the type checker does not report one. This means the checker failed to enforce a rule defined in the specification. Using the same <code>Movie</code> TypedDict from above:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">b</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Movie </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"name"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Blade Runner"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"year"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">1984</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># E: 'int' is not assignable to 'bool'</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>A false negative here means the type checker silently accepts <code>"year": 1982</code> even though <code>extra_items=bool</code> requires extra values to be <code>bool</code>, not <code>int</code>.</p>
</li>
</ul>
<p>The <a href="https://github.com/python/typing/blob/main/conformance/results/results.html" target="_blank" rel="noopener noreferrer">public conformance dashboard</a> aggregates these results across type checkers and shows how many tests each tool passes. The table below summarizes the results as of early March 2026 (commit 62491d5c9cc1dd052c385882e72ed8666bb7fa41):</p>
<table><thead><tr><th>Type Checker</th><th>Fully Passing</th><th>Pass Rate</th><th>False Positives</th><th>False Negatives</th></tr></thead><tbody><tr><td>pyright</td><td>136/139</td><td>97.8%</td><td>15</td><td>4</td></tr><tr><td>zuban</td><td>134/139</td><td>96.4%</td><td>10</td><td>0</td></tr><tr><td>pyrefly</td><td>122/139</td><td>87.8%</td><td>52</td><td>21</td></tr><tr><td>mypy</td><td>81/139</td><td>58.3%</td><td>231</td><td>76</td></tr><tr><td>ty</td><td>74/139</td><td>53.2%</td><td>159</td><td>211</td></tr></tbody></table>
<p>These results probably won’t be up-to-date for long (perhaps not even a week), given that Pyrefly, ty, and Zuban are all currently in beta and being actively developed. Readers in the future should refer to <a href="https://github.com/python/typing/blob/main/conformance/results/results.html" target="_blank" rel="noopener noreferrer">the latest results</a>.</p>
<p>Pyrefly already supports all major type system features, and we expect to close the remaining gaps over the coming months.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-conformance-matters">Why conformance matters<a href="https://pyrefly.org/blog/typing-conformance-comparison/#why-conformance-matters" class="hash-link" aria-label="Direct link to Why conformance matters" title="Direct link to Why conformance matters">​</a></h2>
<p>Does conformance matter in practice? After all, mypy (the reference implementation &amp; industry standard for Python type checking) only fully passes 57% of test cases.
Practically speaking, the less conformant a type checker is, the more you have to restructure your code to work around its limitations or inconsistencies.
For example, you might follow a pattern recommended by the spec, read documentation that says a particular typing construct should work, and annotate your code accordingly, only to discover that your type checker doesn't correctly implement that part of the specification.</p>
<p>Even if you don’t use any advanced typing features in your own code, you may run into issues when you try to import something from a library that does use those features.
When that happens, you may have to add a redundant <code>cast</code>, suppress a spurious error, or restructure your otherwise-working code to make the type checker happy, all because of a gap in conformance.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-conformance-does-not-measure">What conformance does not measure<a href="https://pyrefly.org/blog/typing-conformance-comparison/#what-conformance-does-not-measure" class="hash-link" aria-label="Direct link to What conformance does not measure" title="Direct link to What conformance does not measure">​</a></h2>
<p>While conformance is a valuable benchmark to measure how feature-complete a type checker is, it’s not without limitations.
For one, conformance mismatches are not equally meaningful - some tests check common patterns that appear in regular code, while others check for edge cases that are rarely seen in the wild.</p>
<p>Furthermore, even though the Python type system is becoming increasingly well-specified, many important aspects of type checking are not standardized at all, and therefore not covered by conformance tests. For example:</p>
<ul>
<li>
<p><strong>Type Inference</strong>: The typing spec mostly focuses on the semantics of <em>typed</em> Python code. When annotations are missing, type checkers have much more freedom in how they infer types and how strictly they check un-annotated code. One common example where type checkers have divergent behavior is <a href="https://pyrefly.org/blog/container-inference-comparison/" target="_blank" rel="noopener noreferrer">empty container inference</a>.</p>
</li>
<li>
<p><strong>Type Refinement/Narrowing</strong>: The process of constraining a variable's type based on runtime checks  is another area where behavior is only partially specified. The spec defines mechanisms like <code>cast</code>, <code>match</code>, <code>TypeIs</code>, and <code>TypeGuard</code>, but most real-world narrowing is implicit and relies on patterns that arise from dynamic Python code. These behaviors are implemented on a best-effort basis and support varies widely between tools. We explored several of these patterns in our post on <a href="https://pyrefly.org/blog/type-narrowing/" target="_blank" rel="noopener noreferrer">type narrowing in Pyrefly</a>.</p>
</li>
<li>
<p><strong>Experimental type system features</strong>: Intersection types, negation types, anonymous typed dictionaries, tensor shape types (more on this soon 👀)</p>
</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion">Conclusion<a href="https://pyrefly.org/blog/typing-conformance-comparison/#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion">​</a></h2>
<p>If you're trying to choose a type checker that's right for you, conformance is one useful metric: it tells you how closely a type checker follows the formal typing rules.
But developer experience depends on many other factors, which you should also consider:</p>
<ul>
<li><strong>Inference quality:</strong> How well does it infer types when annotations are missing?</li>
<li><strong>Performance:</strong> How fast is it on large codebases?</li>
<li><strong>IDE integration:</strong> Does it fully support the language server protocol?</li>
<li><strong>Error messages:</strong> Are errors clear and actionable, or cryptic?</li>
<li><strong>Third-party package support:</strong> Does it support packages like Django and Pydantic, which require special handling that can't be expressed in type annotations?</li>
</ul>
<p>In future posts, we'll look at some of these dimensions and compare how different type checkers approach them. In the meantime, you can try <a href="https://pyrefly.org/" target="_blank" rel="noopener noreferrer">Pyrefly</a> yourself, or join the conversation on <a href="https://discord.gg/BFhMj3bSG3" target="_blank" rel="noopener noreferrer">Discord</a> and <a href="https://github.com/facebook/pyrefly" target="_blank" rel="noopener noreferrer">GitHub</a>.</p>]]></content:encoded>
            <category>typechecking</category>
        </item>
        <item>
            <title><![CDATA[pandas' Public API Is Now Type-Complete!]]></title>
            <link>https://pyrefly.org/blog/pandas-type-completeness/</link>
            <guid>https://pyrefly.org/blog/pandas-type-completeness/</guid>
            <pubDate>Tue, 03 Mar 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[We tell the story of how we helped make pandas' public API type-complete, and how to prevent it from regressing]]></description>
            <content:encoded><![CDATA[<p>At time of writing, pandas is one of the most widely used Python libraries. It is <a href="https://pypistats.org/packages/pandas" target="_blank" rel="noopener noreferrer">downloaded about half-a-billion times per month from PyPI</a>, is supported by nearly all Python data science packages, and is generally required learning in data science curriculums. Despite modern alternatives existing, pandas' impact cannot be minimised or understated.</p>
<p>In order to improve the developer experience for pandas' users across the ecosystem, we at Quansight Labs (with support from the Pyrefly team at Meta) decided to focus on improving pandas' typing. Why? Because better type hints mean:</p>
<ul>
<li>More accurate and useful auto-completions from VSCode / PyCharm / NeoVIM / Positron / other IDEs.</li>
<li>More robust pipelines, as some categories of bugs can be caught without even needing to execute your code.</li>
</ul>
<p>By supporting the pandas community, pandas' public API is now type-complete (as measured by Pyright), up from 47% when we started the effort last year. We'll tell the story of how it happened - but first, we need to talk more about type completeness, and how we measure it.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="but-first---how-is-type-completeness-measured">But first - how is type-completeness measured?<a href="https://pyrefly.org/blog/pandas-type-completeness/#but-first---how-is-type-completeness-measured" class="hash-link" aria-label="Direct link to But first - how is type-completeness measured?" title="Direct link to But first - how is type-completeness measured?">​</a></h2>
<p>Pyright has a nifty little feature which helps us calculate the type-completeness of a library's public API. The general idea is:</p>
<ul>
<li>Find all public symbols exported by a package. For example, in pandas there's <code>pandas.DataFrame</code>, <code>pandas.read_csv</code>, <code>pandas.Series</code>, ...</li>
<li>For each symbol, check where all its types are known. This includes function arguments, function return types, attributes, and base classes.</li>
<li>If any type is unknown, then the whole symbol counts as unknown. For example, if a package exports a class <code>Foo</code> which has some missing type annotations, and a function <code>def bar() -&gt; Foo: ...</code>, then the function <code>bar</code> also counts as type-unknown because it returns a type-unknown symbol (<code>Foo</code>).</li>
</ul>
<p>Type-completeness is different from just calculating the percentage of missing type annotations, as it's biased towards heavily-used classes. In pandas, for example, <code>Series</code> appears as an argument and return type to at least some function in all of pandas' methods - therefore, no matter how type-complete the rest of pandas is, if <code>Series</code> isn't type-complete, then pandas' overall type-completess score will remain low.</p>
<p>By default, Pyright includes all public symbols. In practice, there are some pandas paths which are considered public according to Python's usual standards, but which pandas considers private, such as:</p>
<ul>
<li><code>pandas.tests</code>.</li>
<li><code>pandas.conftest</code>.</li>
<li>parts of <code>pandas.core</code> which aren't publictly re-exported in other places such as <code>pandas.api</code>.</li>
</ul>
<p>We therefore amend Pyright's calculation to exclude these "technically public but not really" paths. This gave us a more useful measure of what part of the pandas API which users are expected to interact with is actually type-annotated.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="moving-the-needle-in-pandas">Moving the needle in pandas<a href="https://pyrefly.org/blog/pandas-type-completeness/#moving-the-needle-in-pandas" class="hash-link" aria-label="Direct link to Moving the needle in pandas" title="Direct link to Moving the needle in pandas">​</a></h2>
<p>Investigating sources of missing type-completeness in pandas was quite a circular exercise. For example, suppose that <code>DataFrame</code> and <code>Series</code> were type-complete, but <code>Index</code> had an untyped attribute. Here is what would happen:</p>
<ul>
<li><code>Index</code> would be reported as "partially unknown" because of its untyped attribute.</li>
<li><code>DataFrame</code> would be reported as "partially unknown" because its method <code>.index</code> returns <code>Index</code>, which is partially unknown.</li>
<li><code>Series</code> is reported as "partially unknown" because its method <code>to_frame</code> returns <code>DataFrame</code>, which is partially unknown.</li>
</ul>
<p>It was clear, therefore, that incremental progress would be difficult. Because of how intertwined pandas' classes all are, we expected the type-completeness score to flatline for several months before suddenly spiking. And that's exactly what happened! Progress flat-lined at around 60-70%, before spiking up to 100%.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-ruff-helped">How ruff helped<a href="https://pyrefly.org/blog/pandas-type-completeness/#how-ruff-helped" class="hash-link" aria-label="Direct link to How ruff helped" title="Direct link to How ruff helped">​</a></h2>
<p>pandas-stubs uses the <a href="https://docs.astral.sh/ruff/" target="_blank" rel="noopener noreferrer">ruff linter</a> to enforce code quality standards. Ruff is highly configurable and comes with many optional ones, one of which is <a href="https://docs.astral.sh/ruff/rules/any-type/" target="_blank" rel="noopener noreferrer">any-type (ANN401)</a>. A prerequisite to type-completeness is that types be present everywhere. In order to track progress, the <code>ANN401</code> rule was enabled across the codebase, with a few exclusions which were then addressed gradually. The general rule was: if you make a pull request which types a certain part of the codebase, then remove the <code>ANN401</code> exclusion for that part of the codebase so that it stays fully-typed in the future.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="ensuring-that-type-completeness-stays-this-high">Ensuring that type-completeness stays this high<a href="https://pyrefly.org/blog/pandas-type-completeness/#ensuring-that-type-completeness-stays-this-high" class="hash-link" aria-label="Direct link to Ensuring that type-completeness stays this high" title="Direct link to Ensuring that type-completeness stays this high">​</a></h2>
<p>Measuring type-completeness with Pyright is:</p>
<ul>
<li>Very easy if a package ships its own type annotations.</li>
<li>Fairly tricky if a package relies on an external package for type annotations.</li>
</ul>
<p>The situation for pandas is the latter, meaning that some extra work is needed to ensure type-completeness stays high in CI. In fact, it's even more complicated because there are parts of pandas which are technically public (according to Python's usual rules) but which pandas considers private! So, quite some work to get around Pyright's default score is needed.</p>
<p>The general idea is:</p>
<ul>
<li>Make a temporary virtual environment.</li>
<li>Install pandas in it.</li>
<li>Inline the pandas stubs into the same location, and add a <code>py.typed</code> file.</li>
<li>Run <code>pyright</code> against the pandas package in that temporary virtual environment.</li>
<li>Postprocess the output to remove technically-public-but-actually-private symbols.</li>
</ul>
<p>The full script can be viewed <a href="https://github.com/pandas-dev/pandas-stubs/blob/7ed11d172cd31c61b1adef265e196a1bb8b352da/scripts/type_completeness.py" target="_blank" rel="noopener noreferrer">in the pandas-stubs repo</a>.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="beyond-pyright---what-about-pyrefly-report">Beyond Pyright - what about "Pyrefly report"?<a href="https://pyrefly.org/blog/pandas-type-completeness/#beyond-pyright---what-about-pyrefly-report" class="hash-link" aria-label="Direct link to Beyond Pyright - what about &quot;Pyrefly report&quot;?" title="Direct link to Beyond Pyright - what about &quot;Pyrefly report&quot;?">​</a></h2>
<p>Pyright's <code>--verifytypes</code> feature takes about 2 and a half minutes to run in pandas-stubs. There's room of improvement here - so much so, that the Pyrefly team is working on a <a href="https://pyrefly.org/en/docs/report/" target="_blank" rel="noopener noreferrer"><code>pyrefly report</code></a> which would work similarly. The <code>pyrefly report</code> API is not yet considered stable, so for now pandas-stubs uses Pyright's <code>--verifytypes</code> command, but hopefully a faster tool is on the horizon!</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion-and-next-steps">Conclusion and next steps<a href="https://pyrefly.org/blog/pandas-type-completeness/#conclusion-and-next-steps" class="hash-link" aria-label="Direct link to Conclusion and next steps" title="Direct link to Conclusion and next steps">​</a></h2>
<p>I'm proud of how Quansight Labs (my employer), Meta, and open source communities were able to come together to make this happen. We plan to continue this work in other targeted open source projects, to keep supporting pandas and NumPy with the work we've already done, to improve typing support in IDEs such as <a href="https://marimo.io/" target="_blank" rel="noopener noreferrer">Marimo</a>, and to make <code>pyrefly report</code> production-ready.</p>
<p>Have any requests? Let us know <a href="https://discuss.python.org/t/call-for-suggestions-nominate-python-packages-for-typing-improvements/80186/" target="_blank" rel="noopener noreferrer">on Discourse</a></p>]]></content:encoded>
            <category>typechecking</category>
            <category>news</category>
        </item>
        <item>
            <title><![CDATA[Python Type Checker Comparison: Empty Container Inference]]></title>
            <link>https://pyrefly.org/blog/container-inference-comparison/</link>
            <guid>https://pyrefly.org/blog/container-inference-comparison/</guid>
            <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how different type checkers handle empty containers in Python, including Pyrefly, Ty, Pyright and Mypy.]]></description>
            <content:encoded><![CDATA[<p>Empty containers like <code>[]</code> and <code>{}</code> are everywhere in Python. It's super common to see functions start by creating an empty container, filling it up, and then returning the result.</p>
<p>Take this, for example:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">my_func</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">ys</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">dict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  x </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> v </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> ys</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> some_condition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">setdefault</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"group0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">setdefault</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"group1"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">k</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> x</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This seemingly innocent coding pattern poses an interesting challenge for Python type checkers. Normally, when a type checker sees <code>x = y</code> without a type hint, it can just look at <code>y</code> to figure out <code>x</code>'s type. The problem is, when <code>y</code> is an empty container (like <code>x = {}</code> above), the checker knows it's a list or a dict, but has no clue what's going inside.</p>
<p>The big question is: How is the type checker supposed to analyze the rest of the function without knowing <code>x</code>'s type?</p>
<p>Different type checkers implement distinct strategies to answer this question. This post will examine these different approaches, weighing their pros and cons, and which type checkers implement each approach. The information presented should be useful as you evaluate and select a type checker.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="strategy-1-infer-any-type-for-container-elements">Strategy 1: Infer <code>Any</code> type for container elements<a href="https://pyrefly.org/blog/container-inference-comparison/#strategy-1-infer-any-type-for-container-elements" class="hash-link" aria-label="Direct link to strategy-1-infer-any-type-for-container-elements" title="Direct link to strategy-1-infer-any-type-for-container-elements">​</a></h2>
<p>The simplest approach is just to use <code>Any</code> type for the items in the container. E.g. if the developer writes  <code>x = []</code> then the type checker would infer the type of <code>x</code> to be <code>list[Any]</code>. This is what Pyre, Ty, and Pyright<sup><a href="https://pyrefly.org/blog/container-inference-comparison/#user-content-fn-1-026f87" id="user-content-fnref-1-026f87" data-footnote-ref="true" aria-describedby="footnote-label">1</a></sup> mostly behave like at the time of writing.</p>
<p>Since the analysis does not require looking at any surrounding context at all, this approach is probably the easiest to understand and at the same time the most efficient for a type checker to implement.</p>
<p>Among the inference strategies we discuss today, inferring <code>list[Any]</code> produces the least amount of type errors: developers can insert anything into the list, and items read from the list will also be <code>Any</code>.</p>
<p><strong>On the other hand,</strong> <strong>by inferring <code>Any</code> we are effectively giving up type safety</strong>.  The type checker won't have any false-positive errors, but it also won't catch any bugs. In our experience using Pyre in Instagram, this lets expensive runtime crashes slip into production.</p>
<p>To illustrate the pitfalls, let’s look at code snippet simplified from a real-world example:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> dataclasses </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> dataclass</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@dataclass</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">MenuItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   details</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">first_three_lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">menu_item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> MenuItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   lines </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># `lines` is inferred as `list[Any]`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> menu_item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">title </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">is</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">menu_item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)"># bug: `list.append()` should be `list.extend()`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">menu_item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">details</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This example has a bug where we accidentally call <code>append</code> instead of <code>extend</code>, creating a nested list instead of extending the list of strings. Using this strategy, the type checker infers the type of <code>lines</code> as <code>list[Any]</code> and does not warn the developer.</p>
<p>To improve type safety in these situations, type checkers that infer Any for empty containers can choose to generate extra type errors that warn the user about the insertion of an Any type. While this can reduce false negatives, it burdens developers by forcing them to explicitly annotate every empty container in order to silence the warnings.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="strategy-2-infer-the-container-type-from-all-usages">Strategy 2: Infer the container type from all usages<a href="https://pyrefly.org/blog/container-inference-comparison/#strategy-2-infer-the-container-type-from-all-usages" class="hash-link" aria-label="Direct link to Strategy 2: Infer the container type from all usages" title="Direct link to Strategy 2: Infer the container type from all usages">​</a></h2>
<p>To infer a more precise type for an empty container, a type checker can look ahead and see how it is used.</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">my_func</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">some_condition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  x </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> some_condition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> x </span><span class="token comment" style="color:rgb(98, 114, 164)"># `x` is inferred as `list[int]`</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>In this example, we see that after the initial definition of <code>x</code>, there’s a subsequent usage of <code>x</code> that attempts to append <code>1</code> to it. Based on that usage, it’s highly likely that the intended type for <code>x</code> would be <code>list[int]</code>, and the type checker could infer that as the type for <code>x</code>.</p>
<p>But what happens if <code>x</code> has multiple uses later and each of them assumes different types? For example:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">my_func2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">some_condition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  x </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> some_condition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"foo"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> x </span><span class="token comment" style="color:rgb(98, 114, 164)"># `x` is inferred as `list[int | str]`</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>One solution is to look at <em>all</em> usages of <code>x</code>, assume all of them are valid, and infer the element type of the container to be a union of them all. In our example above, the type checker would infer <code>list[int | str]</code>. This is the approach taken by the Pytype type checker.</p>
<p>This approach is very permissive for code that tries to insert into the containers: no type errors will be reported at insertion time at all, similar to the infer-<code>Any</code> strategies mentioned before.  However, it provides better type safety over the infer-<code>Any</code> strategy when elements are read out of the container: any operations applied to the element must be valid for all inferred types in the union.</p>
<p>For instance:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">my_func3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">some_condition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  x </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> some_condition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"foo"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># ERROR! `+` is not applicable to `int | str` and `int`</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>If we call <code>my_func3(False)</code>, we will get a runtime crash that happens exactly where the type error is reported, so this inference strategy is also the one that models the Python runtime most closely.</p>
<p>However, <strong>the location of a runtime crash is not necessarily the same as the location of the bug that caused the crash</strong>.</p>
<p>While it’s helpful that the type checker gives an error when we try to use a value from a list that was modified incorrectly, what’s arguably more helpful is to know where we put the wrong thing into the list in the first place.</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> dataclasses </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> dataclass</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@dataclass</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">MenuItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   details</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">first_three_lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">menu_item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> MenuItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   lines </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> menu_item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">title </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">is</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">menu_item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)"># bug: `list.append()` should be `list.extend()`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">menu_item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">details</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># error: `list[str | list[str]]` is not assignable to `list[str]`</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>In the example above, the error would be raised on the return statement (saying that <code>list[str | list[str]]</code> can’t be assigned to <code>list[str]</code>), even though the issue we actually need to fix is on the previous line. While this example is only off by a single line, in the real world the location of the error and the location of the bug could be separated by hundreds of lines of code.</p>
<p>Implementing this empty container inference strategy in a type checker faces several practical engineering hurdles.</p>
<ul>
<li>Firstly, finding all usages of a variable (especially those in non-local scopes like global variables or class attributes) can be computationally expensive, so a type checker may need to compromise on exhaustiveness to maintain good performance.</li>
<li>Secondly, if a container has many distinct usages, the resulting inferred element type can be a lengthy and complex union, and this complexity can negatively impact the readability of subsequent error messages, necessitating the use of additional heuristics to improve error message quality.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="strategy-3-infer-the-container-type-from-only-the-first-usage">Strategy 3: Infer the container type from only the first usage<a href="https://pyrefly.org/blog/container-inference-comparison/#strategy-3-infer-the-container-type-from-only-the-first-usage" class="hash-link" aria-label="Direct link to Strategy 3: Infer the container type from only the first usage" title="Direct link to Strategy 3: Infer the container type from only the first usage">​</a></h2>
<p>This is similar to strategy 2, but we infer the type based only on the <em>first</em> time the list is used, where “first” is defined in terms of syntactical appearance.</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">my_func2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">some_condition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  x </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> some_condition</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># `x` is inferred as `list[int]`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"foo"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># error: cannot append `str` to `list[int]`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> x</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>For example, in <code>my_func2()</code> above, the type checker infers the type for <code>x</code> based on its first usage <code>x.append(1)</code>, which results in <code>list[int]</code>.  When we do <code>x.append("foo")</code> on the following lines, the type checker emits an error saying we cannot insert <code>str</code> into <code>list[int]</code>.</p>
<p>If <code>x.append("foo")</code> is actually intentional, we can overrule the type checker’s guess by annotating the empty container with <code>list[int | str]</code>.</p>
<p>This is what Mypy currently does, and also the default behavior of Pyrefly.</p>
<p>The main benefit of this strategy is that the type errors it raises are closer to the location of the bug, so they are more actionable for finding and fixing bugs.</p>
<p>In our ongoing <code>first_three_lines()</code> example, Mypy and Pyrefly would both raise an error on the same line we need to change to fix the type error.</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> dataclasses </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> dataclass</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@dataclass</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">MenuItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   details</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">first_three_lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">menu_item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> MenuItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   lines </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> menu_item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">title </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">is</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">menu_item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">title</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)"># bug: `list.append()` should be `list.extend()`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">menu_item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">details</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># error: cannot append `list[str]` to `list[str]`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> lines</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Of course, this guessing strategy is merely a heuristic that assumes the first usage of the container signifies the programmer's intent.</p>
<p>When the type checker guesses wrong it can lead to false positive type errors. If the developer actually intended to create &amp; return a  <code>list[str | list[str]]</code>, they can overrule the type checker’s inferred type by annotating the variable like <code>lines: list[str | list[str]] = []</code>. Unlike the first strategy which requires annotations on every empty container for safety, this approach provides type safety while only requiring annotations when you disagree with the type checker.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="summary">Summary<a href="https://pyrefly.org/blog/container-inference-comparison/#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary">​</a></h2>
<p>The challenge of inferring types for empty containers in Python is more than a technical curiosity. It has real implications for code safety, developer productivity, and the effectiveness of type checkers. As we've seen, each strategy for handling empty container inference comes with its own trade-offs:</p>
<ul>
<li><strong>Inferring Any</strong> (Pyright, Ty, Pyre) is the easiest approach that will throw the least amount of type errors, but sacrifices  type safety.</li>
<li><strong>Inferring from all usages</strong> (Pytype) closely mirrors Python's runtime behavior, but it can complicate the process of identifying the root cause of bugs, particularly when the resulting errors manifest long after the initial mistake.</li>
<li><strong>Inferring from the first usage</strong> (Mypy, Pyrefly) provides more actionable error messages, helping developers fix issues at their origin, but may result in false positives if the initial usage doesn't reflect the programmer's true intent.</li>
</ul>
<p>Ultimately, the choice of strategy depends on the priorities of your project and team, whether you value permissiveness, runtime fidelity, or actionable feedback.</p>
<p>The Pyrefly team believes that type safety is not the only goal of building a type checker – type errors also need to be actionable and easily-understood by users. As a result, we adopt the first-use inference by default which we believe gives the best balance of actionable error messages and type safety. We also recognize the need for greater flexibility from some users, so we've provided the ability to <a href="https://pyrefly.org/en/docs/configuration/#infer-with-first-use" target="_blank" rel="noopener noreferrer">disable first-use inference</a>, which would make Pyrefly behave more like Pyright.</p>
<p>What’s your take on empty container inference? Share your thoughts or questions with us on <a href="https://discord.gg/Cf7mFQtW7W" target="_blank" rel="noopener noreferrer">Discord</a> or in a <a href="https://github.com/facebook/pyrefly/discussions" target="_blank" rel="noopener noreferrer">GitHub Discussion</a>!</p>
<hr>
<!-- -->
<section data-footnotes="true" class="footnotes"><h2 class="anchor anchorWithStickyNavbar_LWe7 sr-only" id="footnote-label">Footnotes<a href="https://pyrefly.org/blog/container-inference-comparison/#footnote-label" class="hash-link" aria-label="Direct link to Footnotes" title="Direct link to Footnotes">​</a></h2>
<ol>
<li id="user-content-fn-1-026f87">
<p>Pyright’s behavior is actually <a href="https://microsoft.github.io/pyright/#/type-inference?id=empty-list-and-dictionary-type-inference" target="_blank" rel="noopener noreferrer">more intelligent</a>. But the fact remains that the inference algorithm it uses does not take any downstream context into consideration. <a href="https://pyrefly.org/blog/container-inference-comparison/#user-content-fnref-1-026f87" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref">↩</a></p>
</li>
</ol>
</section>]]></content:encoded>
            <category>typechecking</category>
        </item>
        <item>
            <title><![CDATA[Third Party Stubs bundled with Pyrefly]]></title>
            <link>https://pyrefly.org/blog/stubs/</link>
            <guid>https://pyrefly.org/blog/stubs/</guid>
            <pubDate>Tue, 17 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Did you know Pyrefly bundles select third party stubs?]]></description>
            <content:encoded><![CDATA[<p>Since Pyrefly version 0.40, we've been bundling <a href="https://github.com/python/typeshed/tree/main/stubs" target="_blank" rel="noopener noreferrer">Typeshed third party stubs</a> with every build, and since v0.46.0 we’ve been expanding beyond the stubs included with Typeshed to include stubs for select third party libraries as well. This ongoing effort brings enhancements to code navigation when using different libraries in your Python projects, and is part of our goal to make sure that the IDE experience can be used by as many people as possible. In this article, we’ll take a closer look at this feature and discuss how it can help you unlock the full potential of your IDE.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-are-python-stubs">What are Python stubs?<a href="https://pyrefly.org/blog/stubs/#what-are-python-stubs" class="hash-link" aria-label="Direct link to What are Python stubs?" title="Direct link to What are Python stubs?">​</a></h2>
<p>Stubs are Python files that provide type information about Python code. They can be found in files with the extension <code>.pyi</code> and define the signature of any functions, classes, etc, that are defined in the associated source file. Stubs can be very helpful for third party libraries or parts of the Python standard library that were not originally typed and allow us to use that type information to write safer code.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-is-stub-information-important">Why is stub information important?<a href="https://pyrefly.org/blog/stubs/#why-is-stub-information-important" class="hash-link" aria-label="Direct link to Why is stub information important?" title="Direct link to Why is stub information important?">​</a></h2>
<p>Being able to access this type information through stubs is very important during type checking. This is because it allows the type checker to verify that functions are being called with the correct argument types and that return values are being used appropriately, catching potential bugs before runtime. The inclusion of stubs also brings a lot of value to the IDE experience. The inclusion of these stubs greatly enhances certain code navigation functionality in the IDE when using these packages, as highlighted in the following examples</p>
<p><strong>Pyrefly will now provide autocomplete suggestions for types in third party libraries.</strong></p>
<video src="/videos/stubs-autocomplete.mp4" width="720" muted="" loop="" autoplay="" playsinline="" preload="auto"></video>
<p><strong>Pyrefly will now also provide inlay hints for third party libraries with included stubs.</strong>
<img decoding="async" loading="lazy" alt="Stubs inlay hint" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYkAAAAvCAIAAACZjaGtAAAAA3NCSVQICAjb4U/gAAAMRGlDQ1BpY2MAAEiJlVcHWFPJFp5bUiEECERASuhNEJESQEoILfSOICohCRBKjAlBxY4sruDaRQTLiq5SFNsKyGJDXXVlUex9saCirIvrYlfehAC67Cvfm++bO//958w/55yZWwYAehdfKs1FNQHIk+TLYoP9WZOTU1ikHkABdEAD6gDwBXIpJzo6HMAy3P69vL4GEGV72UGp9c/+/1q0hCK5AAAkGuJ0oVyQB/GPAOCtAqksHwCiFPLms/KlSrwOYh0ZdBDiGiXOVOFWJU5X4YuDNvGxXIgfAUBW5/NlmQBo9EGeVSDIhDp0GC1wkgjFEoj9IPbJy5shhHgRxDbQBs5JV+qz07/SyfybZvqIJp+fOYJVsQwWcoBYLs3lz/k/0/G/S16uYngOa1jVs2QhscqYYd4e5cwIU2K4ashbSXpkFMTaAKC4WDhor8TMLEVIgsoetRHIuTBngAnxJHluHG+IjxXyA8IgNoQ4Q5IbGT5kU5QhDlLawPyhFeJ8XjzEehDXiOSBcUM2x2UzYofnvZYh43KG+Kd82aAPSv3PipwEjkof084S8Yb0McfCrPgkiKkQBxSIEyMh1oA4Up4TFzZkk1qYxY0ctpEpYpWxWEAsE0mC/VX6WHmGLCh2yL4uTz4cO3Y8S8yLHMKX8rPiQ1S5wh4J+IP+w1iwPpGEkzCsI5JPDh+ORSgKCFTFjpNFkoQ4FY/rSfP9Y1VjcTtpbvSQPe4vyg1W8mYQx8sL4obHFuTDzanSx0uk+dHxKj/xymx+aLTKH3wfCAdcEABYQAFrOpgBsoG4o7epF96peoIAH8hAJhABhyFmeETSYI8EXuNAIfgdIhGQj4zzH+wVgQLIfxrFKjnxCKe6OoCMoT6lSg54DHEeCAO58F4xqCQZ8SARPIKM+B8e8WEVwBhyYVX2/3t+mP3CcCATPsQohmdk0YctiYHEAGIIMYhoixvgPrgXHg6vfrA642zcYziOL/aEx4ROwgPCVUIX4eZ0cZFslJcRoAvqBw3lJ/3r/OBWUNMV98e9oTpUxpm4AXDAXeA8HNwXzuwKWe6Q38qssEZp/y2Cr1ZoyI7iREEpYyh+FJvRIzXsNFxHVJS5/jo/Kl/TR/LNHekZPT/3q+wLYRs22hL7FjuIncFOYOewVqwJsLBjWDPWjh1R4pEd92hwxw3PFjvoTw7UGb1nvqysMpNyp3qnHqePqr580ex85cPInSGdIxNnZuWzOPCLIWLxJALHcSxnJ2dXAJTfH9Xr7VXM4HcFYbZ/4Zb8BoD3sYGBgZ++cKHHANjvDl8Jh79wNmz4aVED4OxhgUJWoOJw5YUA3xx0+PTpA2NgDmxgPM7ADXgBPxAIQkEUiAfJYBr0PgvucxmYBeaBxaAElIFVYD2oBFvBdlAD9oADoAm0ghPgZ3AeXARXwW24e7rBc9AHXoMPCIKQEBrCQPQRE8QSsUecETbigwQi4UgskoykIZmIBFEg85AlSBmyBqlEtiG1yH7kMHICOYd0IjeR+0gP8ifyHsVQdVQHNUKt0PEoG+WgYWg8OhXNRGeihWgxugKtQKvR3WgjegI9j15Fu9DnaD8GMDWMiZliDhgb42JRWAqWgcmwBVgpVo5VYw1YC1zny1gX1ou9w4k4A2fhDnAHh+AJuACfiS/Al+OVeA3eiJ/CL+P38T78M4FGMCTYEzwJPMJkQiZhFqGEUE7YSThEOA2fpW7CayKRyCRaE93hs5hMzCbOJS4nbibuJR4ndhIfEvtJJJI+yZ7kTYoi8Un5pBLSRtJu0jHSJVI36S1ZjWxCdiYHkVPIEnIRuZxcRz5KvkR+Qv5A0aRYUjwpURQhZQ5lJWUHpYVygdJN+UDVolpTvanx1GzqYmoFtYF6mnqH+kpNTc1MzUMtRk2stkitQm2f2lm1+2rv1LXV7dS56qnqCvUV6rvUj6vfVH9Fo9GsaH60FFo+bQWtlnaSdo/2VoOh4ajB0xBqLNSo0mjUuKTxgk6hW9I59Gn0Qno5/SD9Ar1Xk6JppcnV5Gsu0KzSPKx5XbNfi6E1QStKK09ruVad1jmtp9okbSvtQG2hdrH2du2T2g8ZGMOcwWUIGEsYOxinGd06RB1rHZ5Otk6Zzh6dDp0+XW1dF91E3dm6VbpHdLuYGNOKyWPmMlcyDzCvMd+PMRrDGSMas2xMw5hLY97ojdXz0xPplert1buq916fpR+on6O/Wr9J/64BbmBnEGMwy2CLwWmD3rE6Y73GCsaWjj0w9pYhamhnGGs413C7Ybthv5GxUbCR1Gij0UmjXmOmsZ9xtvE646PGPSYMEx8Tsck6k2Mmz1i6LA4rl1XBOsXqMzU0DTFVmG4z7TD9YGZtlmBWZLbX7K451ZxtnmG+zrzNvM/CxCLCYp5FvcUtS4ol2zLLcoPlGcs3VtZWSVZLrZqsnlrrWfOsC63rre/Y0Gx8bWbaVNtcsSXasm1zbDfbXrRD7Vztsuyq7C7Yo/Zu9mL7zfad4wjjPMZJxlWPu+6g7sBxKHCod7jvyHQMdyxybHJ8Md5ifMr41ePPjP/s5OqU67TD6fYE7QmhE4omtEz409nOWeBc5XxlIm1i0MSFE5snvnSxdxG5bHG54cpwjXBd6trm+snN3U3m1uDW427hnua+yf06W4cdzV7OPutB8PD3WOjR6vHO080z3/OA5x9eDl45XnVeTydZTxJN2jHpobeZN997m3eXD8snzed7ny5fU1++b7XvAz9zP6HfTr8nHFtONmc354W/k7/M/5D/G64ndz73eAAWEBxQGtARqB2YEFgZeC/ILCgzqD6oL9g1eG7w8RBCSFjI6pDrPCOegFfL6wt1D50feipMPSwurDLsQbhduCy8JQKNCI1YG3En0jJSEtkUBaJ4UWuj7kZbR8+M/imGGBMdUxXzOHZC7LzYM3GMuOlxdXGv4/3jV8bfTrBJUCS0JdITUxNrE98kBSStSeqaPH7y/Mnnkw2SxcnNKaSUxJSdKf1TAqesn9Kd6ppaknptqvXU2VPPTTOYljvtyHT6dP70g2mEtKS0urSP/Ch+Nb8/nZe+Kb1PwBVsEDwX+gnXCXtE3qI1oicZ3hlrMp5memeuzezJ8s0qz+oVc8WV4pfZIdlbs9/kROXsyhnITcrdm0fOS8s7LNGW5EhOzTCeMXtGp9ReWiLtmuk5c/3MPlmYbKcckU+VN+frwB/9doWN4hvF/QKfgqqCt7MSZx2crTVbMrt9jt2cZXOeFAYV/jAXnyuY2zbPdN7ieffnc+ZvW4AsSF/QttB8YfHC7kXBi2oWUxfnLP61yKloTdFfS5KWtBQbFS8qfvhN8Df1JRolspLrS72Wbv0W/1b8bceyics2LvtcKiz9pcyprLzs43LB8l++m/BdxXcDKzJWdKx0W7llFXGVZNW11b6ra9ZorSlc83BtxNrGdax1pev+Wj99/blyl/KtG6gbFBu6KsIrmjdabFy18WNlVuXVKv+qvZsMNy3b9GazcPOlLX5bGrYabS3b+v578fc3tgVva6y2qi7fTtxesP3xjsQdZ35g/1C702Bn2c5PuyS7umpia07VutfW1hnWraxH6xX1PbtTd1/cE7CnucGhYdte5t6yfWCfYt+z/Wn7rx0IO9B2kH2w4UfLHzcdYhwqbUQa5zT2NWU1dTUnN3ceDj3c1uLVcugnx592tZq2Vh3RPbLyKPVo8dGBY4XH+o9Lj/eeyDzxsG162+2Tk09eORVzquN02OmzPwf9fPIM58yxs95nW895njv8C/uXpvNu5xvbXdsP/er666EOt47GC+4Xmi96XGzpnNR59JLvpROXAy7/fIV35fzVyKud1xKu3bieer3rhvDG05u5N1/eKrj14faiO4Q7pXc175bfM7xX/Zvtb3u73LqO3A+43/4g7sHth4KHzx/JH33sLn5Me1z+xORJ7VPnp609QT0Xn0151v1c+vxDb8nvWr9vemHz4sc//P5o75vc1/1S9nLgz+Wv9F/t+svlr7b+6P57r/Nef3hT+lb/bc079rsz75PeP/kw6yPpY8Un208tn8M+3xnIGxiQ8mX8wV8BDCiPNhkA/LkLAFoyAAx4bqROUZ0PBwuiOtMOIvCfsOoMOVjcAGiA//QxvfDv5joA+3YAYAX16akARNMAiPcA6MSJI3X4LDd47lQWIjwbfM//lJ6XDv5NUZ1Jv/J7dAuUqi5gdPsvtKKC9mWjFIwAAACSelRYdFJhdyBwcm9maWxlIHR5cGUgQVBQMQAACJlVjksKwzAMRPc+RY4w+li2jlOCEwKlCbn/onLtLjKDGDGIh9LePu0+1uW6z+14t7T85JrU1fkFoGKIASFQT5RZ2UjzWqCx0OwbHspxYWJUYv4swAdHx41AOIxqzr0nHr2uT5ZSFpX+3WSLSWHL4Rb8alvR9AWuiiz4ZITPhAAAF2ZJREFUeJztnXlAE2f6x59JMjlICCEQbgj3oVxyiKKCIogoShfr2VZta1vpWtf21+722K7tdrvr/nrYdreXta3brbXdqlXQtqhUQVFElFsROcN9BRJyTiaZ3x9DYwgBj9Wa3d/7+SvzZOZ5j5n5zvM+7zsJJpVKAYFAIOwMxr2uAAKBQNgAaRMCgbBHkDYhEAh7BGkTAoGwR5A2IRAIewRpEwKBsEeQNiEQCHsEaRMCgbBHkDYhEAh7BGkTAoGwR5A2IRAIewRpEwKBsEdYVts8Hu+e1APxn8vGjRsnGvfs2fNL1wNhl9z25WGtTQjEbbDnq88sNzeue+Re1QRhh9ze5WFbm7RareUmh8O57WrZM3q9/l5X4b8EnU5nZbG6hBD/n7m9ywPlmxB3jEXJjjUHprFYGL0Z5e8ZHeAFACHekqSwsZ8J83cXz5kWcM+q+F+BI4+zNHE6k4Hd64pYY/OMA8C2ByUle0Jv1dud16b09PRt27Zt27btqaeeys3NlUgkd7wIhBVMDjv9f58XePxCXb3z7S25uakT7S897llRryZJit58OD3p0YwkAFiZHJOXlUwbkyMCHlqQMPFYDs7asXGZh7OjlX1bTurd07LF8RF7n32Iyx43evBwdvzqufVR/p53qdBbZWLPeIiFD8yPZ7PsLiFj84wDwOeH5G5i/P4Mp1vydlfiJpIkP//882PHjnl4eGRkZNyNIhCWMJhMJz8vnO/wyxQXGuoXFOhlZZyfyPdwwV/7uOf2fDIZmJ/Emc9lW9mn+br7uIpuz+cNyZ0dda6hVUeQlsYH5scPqzS1bbfZkDvOZD3zH4RCZTxfo3pmvcctHWVbev+UX11ZdDhmfjZPIBzsanvniaUAEBwcnLkoU9YhKygomNopRVEKhUKhUHi4e8yIm8FisUiSFIlEOTk5IpHIZDJVVFScO3cOAFgs1vLly729vZlMplqt3r17N0VRubm5OI67uriycJZMJjt8+LDJZMIwLDMzMzQ0FMOwvr6+Q4cO6XQ6iUSyauWqa03XwsPDtVrt8ePH29raAEAsFmdlZbm6ugJAa2trfn4+AOA4vnTpUj8/PwzDZDIZbbwjzH3xSc3gsDhYKvTxJPX6H3693aDRTl+THZw1n8Vhq3oGzvz5ffWAHACCFqdGrl3GYDL6axvFIf4Fm5538vNKeeU33+e9bNQTkumhs55+pGDT8wAg8JAkP/e4wNPNRJKNBUWXv/0eABg4nvzcY67hQUw2rh1W/PDr7ZTJtHDHb/keEgBI3b7VZDL1XKy98LcvAMBndlzkuuV8idhEkrX7CpqOnpys/lKp+zs7twqdBMNyJUkaT5yo2P3pEQB49tk1GemJLJwlH1I++9zf29v7tm9/ePbsSBxnZWYmLUxPUKm099//e9pJdoqTVm9q7SJu2F1MBuO1B7MCPVzb+uTv5hf3K1R/Xp9NxwV/WJNpNFGXmjv+fuTMw+kz500P4rLx7MTpmTPClRrdtk++A4BdW1bJ+ofDfN0pijp8vu5AaTUA4Czmc79aEObjhrNYwyrNUx8dMFEUAPDY+Pt5KzR6w5aPDlhVY1a4VMDjflV8ydIodODGBfl+cuwcALyybnFde8/+0mrzt6+sW0xRlMlEvfbNMbNxTUpccoT/wIjKyhji6WppsWRiE8QChzcfXf7GwZ+udPQDwPzo4DXzZvz6w/2vPbh0Ys/QTjamz0yOCFBqdLsKz1a3dANAsKfrM/fNFwkcdIThi5MXTtU0AcBvc9MogAB3sROfV9Eke/dwCd0zNkmPDVuXGsdl4/JR9VvfnWztkwNAkKfLS6sy9p+tuT85hoOzjlyo31d8KS7IZ0v2PA7Oqm3tDvaW/HHfj7KBkSlO+onzylkxAhYLM4fVN8S2NrG5DklL1zZVnr1UdCh4xlhsxuVycTYuFApv0rWDg0NAYIBWqyVJEsOwNWvWGAyGgoICT0/PpKSky5cvKxSKpKQkHx+f7777TqvVTp82HcMwiqIcHR2dnZ0vXLhAEERycnJoaGhDQ0NMTEx4ePilS5cUCkVqauqiRYvy8/NxHMfZuLe399GjR+fNmzd37lxam1JTUzkczr59+1gslo+PD12frKwsX1/fkydPUhSVlpYWHR1dVlZms+Zff/21o2Dc4KLiYsX27dsnbamLs3t0+GBja/l7e8ShAcDA/NOSw+9bdDX/xOCV5hmbVs14fO2Z199nsJixG1f01V7tPFcZu3EFk40DAIvLYTvwMAwDABaPwxY4AADGYMx/7RmjTn/urd3iEP+IFYvbi8+r+4ciVmS6TQ89/ecPCKVKuiAJMACA8r/9gytySnl5S9Xn+xXtXXqliq7VjMdWD9Q1lu74UOjtwWRP9dR97tm1PB7n/fcPpqcnhIX5iV2EAPDAuoysrNmFhecrK689+eSvXnxx/RNPvLFrV/7hw6d3/GVzTW3zV18d1+sNZifBfly5YlwAcqislm5XYWVDVWu32c7ncoZV2vcKSp5cMicjLmzvyYvvHz0t4vNeWr3o86JyWf+wUqMDgMNldcV1zb9fnXGpufP7iisEOeZcwOOGeEs+PV4WLfVakRzzQ8Vljd6QOzs6ws9jx7cnRrW61MhgDAOgAACYDIzLZuO2hj9r5sXVtXXLRzWWxtXzZugNhuKaJgBQaLRxQb5mbcIAQrwkRdWNC6JDLA+JC/IZVmlDfdysjLLB4ck6fGIT5CqNWkcsS4y80vETAGQnTOsZUhpNlM2eoQnzdns3v3hdavzalPjqlm4M4MVV6Rq94Z3DpxbHRTyemVzX1jOoVIuFfD+J8/7SKo3esD4tMdjLtbFrwGatPJwdH8lIqmvrLqq5tiEt8cVVGY/97RsA4OAsLpt9f3JM4aWGtn65s8ABAPKWzBlUqr8trVq/IMGBw2bjrCnOOADUNGoBIDaMW1F/s5Mkkw5Ze9sad/1uAwBUHPuOnqfr7e2tra3t6+u7oVMcx7du3cpgMAiCOHDgAAB4e3tzudzW1lZnZ2edTkeSZFxc3MmT15/kQ0NDxSXF5k2VSlVaWgoA06ZNCw4ObmhoCA8PV6vVJSUlAODr6xsQcD0HcerUqdbWVoFAkJo6lgRxcHDAMMxoNPb393d3j/VRQEDA0NAQm80GAIVCERMTM5k27dixQyAQWFo6OjqmbjKpJ4r/sBMAOssqASB06QKDVqcfVTn6uA83tXslxjDZuFtUOACce2OXkTA4B/oGps+ZzJtLeBDHkd9eWS/wkhBqNaknQrLTqj77lj7xAKDs6q354jv682hXn25YCQAKWfdwi4w2YgwGzuFgGEaoNF3l1TZLMRMc7FP008WDB4uPH79w6NBfaOPynHkGwqAYUQUGeLa1dkdHB/N4nJ6eoZ6eIaPRNNA/UlXVZOnE1RnvHTRYWkrqmukPF5s6rUp8r6CENJoWRAZP9/UAgK4hxbBKAwAdA8MtvUP0PnKVRq7SkEbTgFJtNtKcb5SdqmkqrmlKCpMmhPiV1DWbe6ZzUPHPkxXmPfWksai6UUuMqxgARPi6u4kc3z40Lpbk4KyUyKD9pdX0k726tWdDmjcAvPnI8pq2ntP1zRiG/VBxJT02zMfVKSt+WoC7y4tfHPEUC788VRHiJbEyHrlQP0WfT2zCscrGVfNiWUyGgMfxchG9WfLTZD1Ds6eovKqlSyzgP5SWAABSN2cum72r8Fx5o6y+vfeTrWtmRwQUnK8DgP6R0YNnawBg1dyYGYE+k2nTnGmBAPD24VM6guTgrM1ZczzFwh65kv724LmaI+VjLXIV8vlczl/3FzX1DOJMxpbsFNo+xRm/1k4AQJj/ndCmy+dOWFkGBweLiopuxqnJZDpy5IhUKo2JiXFxcenr6xOLxQDg7Ozs7OxMu6KnFSsrKwMCAlasWEFR1NWrV3/88Ufag1wupz8MDAx4eXkBAJ/PHxoaOze9vb3BwcHm4mj1UalUDMZY+uzkyZNLlix56KGHSJI8c+ZMVVUVrVYcDickJAQA9Hr9FLOYcrlcrVZbWlQq1dRNHmpstdzkOAsBw3ySZtCbwy0y3IHnHhNu0OqMhAEA5NfaptAmobc7ADh6uTt6uQOAsqOHUKkB4NrRUx6x01Ne3kKZKFlpRcX7/5zMA2UyVf3jQNQDOdkfv64dVpS/u2ewodnmnk5OfJyN19e1AsDoqIY0jIUnjo48wLDkOVH0ZmfXgMiJr9VOuupicNjgImJO9q0lSo2WNJoAYESj9RDfbBhuCX2XUgCjGl2k1LOkrvmHisuxAV4vrV5EUVTpldYPjo4NfAyk8dNjNp5AD85PaOuTWw1DlidFUhT1fcVlerOqpXPToln+7mJPsROPwx5WadU6fc+wUkcQkVKvxBBfPpcT6OHCZDDKrrStnhtrZaxu6bqlJhyvaliTMiMlMsjDWUgYDJcm3N5WNHb1A8DQqIrJYACAi5APALTuqPUEYTC4OY09X2UDYxGcSksIHbiTOZQI+TqCoLNvtHNXId+sTWevXL/Cp0s9AaC1b8jckBsSImUDQEOr9WKCKZhUm1Qj1kU6OjpKpVKFQnHDIMJoNLa0tLS0tIhEorS0tIaGhtHRUQAoKSkxRzE0Go3myy+/5HK5CQkJCQkJly9flslkAGAeOYpEIlqndDqdk9NYnt/FxcVkMpmdUBPGz93d3bt37xaJRBkZGampqXV1dbQStbS0WAZrk7Fz504ud9wprKmpeeGFF6Y4xKAap2WGUTVlok7+/i1L40hrZ2D6XIzBoEwmR6+xIQCp0wMAW8AndXonv7EEM52cqvnioJXk6ZWjJ363Axc4hC1PD1ueLisu76+7au4BBmucNLQcO91y7LTrtJCk32yMf2Jd4dOv0fatW7e6ubkVFhaePn0aABQKNWkgI6b5f/9DmYMDl4WPXRIajZ4yURs2vD6xsRRFMVnWMnStXZeRfGsTMeN9AgAwGdaTMyaKYjGtjd4uYwXxeRz6DlFodM//44iAy16WFLlsZmRJXXNdew8AMBlYalSw3kCWXr7ekz6uTgEeLq+PTwYxMGxpQkRR9TUDaaQt8lENYTBsWJB4RdYb7OUaH+zT0jMEAK298llhUhaTca174MH5CTqCUGh0No0AwGIyti5LAYA9ReWW48eJTdARZH17z6IZ4SI+t/RK2w17xuqqH1FrAcBP4iwf1bBZTDaO0xa6Dyd2eKCHy32zojSE4aPvS2nLsFrLZbOZDIbRZPKTOAOAQn1dSkYtHksNHX0AIHUTt/QOSd3EE51PJCaMBwDVjbegTbcwTyeVStPT01NSUm7+kKKiIhaLlZCQ0NHRYTKZUlJSRCIRg8EIDw+no6Hg4GBXV1edTtfS0gIA7J/TIk5OTgEBAT4+Pq6urs3NzQDQ3t4uFAqDgoLc3NyCgoIGBmzHpTTR0dEODg4jIyPd3d3UzwwMDISHh9PpJ09Pz/Dw8MkOX7t27f3jefnll2++1QDQca5S4CmRpszEmAwHV3Ho8oUA0HWhGmNgoTnpDq7OfvMS6T0VHT2UiQpanMKXiAMWjqX2Bq80mUgyen2uwEOCMRm+c+JdQgMAwHtmjJOvl0Gl6blYBwD4z89AUqsz6PQBC5NZXA5gGAAwOeygzBQGiym/1qYZkJMWq0xnzpwZERERFRVltrS29qQtiEuZF/PKKw+bjefL6sUuwpUrF7BYTD8/t82bc8xfdXUPxseFeXiIGRY3zA9nlDwOI8D7NqeTtIRBRxgWxoRy2SzLdTuy/uH4QF+RgMfArpuTQv28xMKshAgmg3H+ajsAJIb6+UlEKh1xsakDAHgcnN6Tx8Y3LZq9OWtciPpAasKgQlUv67U0LowNxVms/WeqLI1t/cNhvu5nr7Y3dg2EertVt3cDQE17T6i325WO/rKr7eG+7nTgYNMIADiTmRDilxDiJ+KPextsYhMAIL+83k/iLHTgFZTX3bBnrJANjBhNptzZ0SIBb938eACobJ4q8vJxFSWE+M2JuJ4budTcCQBrU+NEAl5OUpSBJLuGFDaP7RsZ1RHEw+kzQ7wlK+fGTlGKmYUzhXIlefOJcJgibqJMtr1MDFKmQKlUdnR0zJw5s7y8PD8/Pzs7m365hh70AYC/v39kZCTts729valpLIVBEEROTg4AqFSqhoYGACgrKwsMDFy2bBn9bWFhobkyE6sUFxeXlpZGUZTJZDp79qzRaASAgoICWmjofSorKyerNkHceLLJCqsqXP72qMjfO+HJBxOefBAARto6G/OLDCpN1/mqyNXZkauzzRlroKi24rLQ7LTQ7DRl59jdYtQTZ9/4ZNYzj2a+8zIAUEbTubc/BQC32IhZzzxKn5q+mgbLRNK1gqKI3MXSlJkDV5pKXn2PgbNi1ufGPnw/ABAqzbm3dk+o8PUa73znm7fe3LL9lUdGRzVG0kgQBgDY+c6//AM8Nm++b/Pm+wCgt2foo48O0/vv/fLY00+v2rt3O2kgMxf/D238qVw1MEy+uMn9sVenCqunuH6OXKj/1ezoedODGjr6/vh1IW08fL7uicXJH+StJAyGje/so40minrz0fsAoLK5kw5GYgO8F+TMp53XtnZfaJTZPC8AIBLwYgK9Pzh62sq+Ijn6fGO7Wj/u1Ne194R6u51vaDNRpkh/r6rmTgCobu1aPW/G2YbWurae9WmJte29kxkBgALb7Z3YBLo4HUGMavW9w6NT9MzYZT/es4E0fvzj2bysOR/krQSA45UNY/o4vgvMR00Mpq51DZyqvbYkYdqShGkURf396Gnj5KOTT46V5WXNeXVdVnPPAACQPwebNhE4MGdG8f+069aWZWBW/+tLv+t7995ZEQqFTCZzZGTE3FQ2my0UCuVyuXmYtmHDhv7+/qKiIjr2sTzcwcEBx3GFwracW8Ln8zkcjjlvZYbL5QoEgpGREZIk7/Y7K0w2LvCQaOQjBtX1YJ4rErI4bL6HZO4LeQfWbKWNbKGAIo0GjXUKjC8RYyyWum+Q+rlzWDwuXyIe7eozGae6GgAAMEzg5mIyGjWTTxhZguMsPp974MDrTz218/LlNtrI43GCgry6ugaHx98tluTl5X302QcAcN9Cp79s9Y5acWXT+rwPP/zwZgq9Db56bv17BcUNnf2UiVJYzFvx2LjESdA1pLC8o2zUdsmc+GDfTe99bWlMDPV7Omf+b3YdHFDcILF4R5isCXwOe9dTq/eeumjOed0qTAbmKRYOKNR6A3njvW3BZbNchfzuIeUUSw0simPMDpc+vjh5/dt7J9snLy/PCQ6synSOX91AWzY/8uTNXB6/9NJSpVJpZSEIYnBwcOKeBEFMjF80Gs3EPW2iVqut8tk0Op1u4ts9dwkjYVDIuq2MuhElAPDHr+EmlLZvCTrxZAmp1U30aRuKUvXZ6NiJPLxxScaixPb23qjIQK1GZxYmANBq9XV1rZMfOo5DRYpDRTd+bNwRRlTWOq4lDOak7xTsK7m099RFK2N9e8/Wjw8MKm1cMHcPqyasnBubERuqN5DHq67etk+jieoc/LdOgY4gb8bDtpxUAYet1Onjg3wuTTl4BIC/ftb3189uPL9vhd0teweA2tpaOnf+X4y6b1BWan2H3CuKS6p8/dzEYuHuT4/k55fe6+rcgKLqxs7BqZb5Tc1EUQMAjd6g0VuvM7h72GyCjiCL65sLLzYYphwf2QnHK6/OiwxiMbA//ev4tUkWJfyb2Name/srTlVVVQDAmDAxcce5h800KlR1u/9lJ7+W1dMz/MYb39Cfb2/8bjWtCZP07SPpiXPC/W/DvxULY275xVF7Y7ImLE2Y/gvX5N8kOSKQ/vDBj2cvNtleNnGTl4cV9hg3If7juMlf5PnsxIXPTly425VB2Bu393te1rlwBAKBsAfQ7zchEAh7BGkTAoGwR5A2IRAIewRpEwKBsEeQNiEQCHsEaRMCgbBHkDYhEAh7BGkTAoGwR5A2IRAIewRpEwKBsEeQNiEQCHsEaRMCgbBHkDYhEAh7BGkTAoGwR5A2IRAIe+T/AArZkxZmI2xoAAAAAElFTkSuQmCC" width="393" height="47" class="img_ev3q"></p>
<p><strong>Hovering on types from packages with stubs will also now show the associated type rather than 'Unknown'.</strong></p>
<p>before:</p>
<p><img decoding="async" loading="lazy" alt="Stubs hover before" src="data:image/webp;base64,UklGRngQAABXRUJQVlA4WAoAAAAIAAAARwEAWQAAVlA4TPMPAAAvR0EWAFWH4rZtHHP/sa/Xd0RMQJrSKXNN3SNXhU2aQkJNtaLLaCDRXZMiIZXqyPdSsf5/ueMsZabMzMzMzMxMh5mZmZmZmZkcrqfv/f//N5y36Opzfj4bmA0UVR3rSuZW8Q54Gdz6e4oq+6nqNuKziLuAqlGluCreQN2c2kpWieoGWNXFZwGRpWyguI7xWUNdVR2zuz6uzLyBqOIGeAPjxld1VCmy6OIj4+PK9HVFGR9XVDmvjy/XsbqLuH7c68tVWUBVN9BuAf/nVaWoMh+3sW2ryvqCe+oOJVA6Q+4uESVoZiluq217jutppS7nOjOBYw+9LdQmsoBpdDlzTv8huY3kSKLG28y5zey+wI70/6suKUtpd3d3d3d3d3d3d3d3d3d3l83Muec9r47j7hqSD5mTToQtwd1WMDfvCCfztwrbQUfYbUlxMiJ3i9whYqKOcI+dG1IvsgAWMW4RNppjd5ZxyeYWITUpOxk7YafMGjTqFeCEswRc4qmJCAn1Zp0Pm2AJbgvQLXQRT4xb6qTU+WKx5wfLPXJ3WIFr1FU3o8rt7AGXyAnd3d1udYhDa0b9R6IFAGQbNc5rWLvRVttD7r7Otm3bvrrvZtu2DTna/reR4s3BYXKeWZvZnHMOHZmSmpaahy5cgAtwBC5AS89N1tJYlmxLHgUjq/8O3EZSJNceLPUyPqEb2id3UsDUEHkDvszoCL0OUBVdoOtNkpJMaA5d70pqAHinazKA6Z1SgZlgeH8myy8/97fgEkE76kE66xDUFO0C1E1nAFVwIgqic/QMLaLLctCRDXrxAM3QDqgc3Qed0irj6F1SJQBW1PMHbEVSKxJcef4K/UyXA0xWbWwXYHMxX7nBDRI17wJAhejwCMsKp04jAeTM6QCQKbkA5pUyB07O7Oac23HH07DrdtQ2tjMDp5YTp8NMCACC6gX+AIDWWBpOKqVk8s+U4Drf+TAZJgwAKIAn9ZKBuss7Pxj+1VEwGCuQrNOuXU1tsqKgnu2o8RMZ8GAf+JYC+YlQZIl+JJOiTKMLZvFlGQjm3YkSO5afrK9xqdvRpGe3BNVXVwnu3QGsEnYi+TqRfJNYvgkfOkgm33rkhvDEDWMmTmMnz5IfvIuVODODLgCuzN13snGT51ZJG0C86isv/Che6oKNFDl2pKm/M9mBO913vtMstsggYpGVuBVleEdaHA2n89gn4aVX1j0DWx96rncGvgNwNqbqnIUtD4d5nsZvdzOkkP1FnOBXBhR8iKLmGBAzTZscC4WK1yKryxrw1SmqXpoIzY8+h/mfw3uPaHwsPOIRf/xjK8sljO5YT2p6LBRqnoeG9q1JOUahWRuMFz58+Ffg+KwzY3szy/mUgs3Al2PMhgrsOjG23xnEswXz+DIbJrDnWNs41VUA6OoGdm/Z19R9ir4xeM4aad/rwxe/uCFghkHCH4lQ4wIM0sRD1k3dD0hrS1p4ktshlfdJ/GOg8tVJ9QvQT7s4jDACZW5Gmd9ioOrlSYpPBIGbuBD7H/XUJw88G9DgBMjyGtuZLUb4bRcwuWUcTxaF76NHzz+/4X0L+57uSnbndIJfieE9M+nXmQbRE6f+nOvWGU+XYgScxWt2hg2xa4IzU4e1oFyhhAsdMLUwsrGoaeNFKLJwJRYlz4RHfEkYAQ8Dkkn5G1HDk+Ca1zzOEY3tmCHTndv8WgkA1hXWV5wpLAVIuMdKF1Fjx685K4sXc4AzXZM1DMwEcG1wgAV8ibWjqVHcWrqSGQ4AOV/g6YrdQ1S89oiHC5aWSyzL5IE/VpsbC2slgtdPJyo92uWowvI2FJbHAbn7UXKPJT7cQ8buHt740TvhTfDZn8ORtsFaJm2KU12TJph3+69i1DoPAeBs0w0BAHPrRngEnE4oysDZgLWfDY/eDkTiekjVPgu3gzvsfpQixBLtMTvHLc90TEzN6lw9cuQoiXzzcwZurf2k8o0lYtUgMCOei1El7NzUljGxbSLl2bI4w2xmNsyxKHH+0R9bWdYbF86GmGkm+G/jH91J+BKD8W0Ton5qZM98aeC3cYdRihEPUpTIEUslfvXV1Lr472tuSlliCXYJa7QL+Heus6yuE10TlAvRmETm/6vLbl4sRM6zIwrC7sysl3iQILydYnf2/yaE4cOTf0nx0VzPY0EQvo0tTCcIm3A1ohSIB9yL4yYvdm+RWL03AKzps/eG9Bky8k4AMMH+TGFw36IANhY1aDn4MlsAsDHrS3p9wKQ7yCcUSTLpzp66dn05SfORXEbReabJWq7UTxaoNWUzHIUg5TA0C680KFatSWKcpyL5jjMw1CkBJpbX+yMHimFI9huWqAqQsiSiaMggNeM1OKWj8DogHTub2tEn1Dh1QaWiyJ3uGIqMshSofyojfYS0GANRBT1AlwzKVooyoGxhZ1M9HFIaTaOI0ApCKr6Uf2nyaKoamSPUEOP0zLtDc++h83GzqRVlwj0kojOkHJA2UZxJ5WPyrqwUks/R/1Iu0wY7GDVBHRAzDjebWlEYVDRSaqlkRCdwMqZe5l1FdpiUCH0ps4YWLhBKc9N8B+ygzKZmZAO1hde1dLtIbOQyvcAL1mzhE5OPDKRFDHpN5ZChRNQxUkaMrbiJeaHyoPO9aSITWT1E1KpI2SvzydcD+dnUy/eo4CLlXemMTo+2q4bvowrwXD/mYG7m771qQHi8WzhY4sOhBg7Rnub/wxQL21VutVx/RSd2laeT2rShKbjVsvsVnRg8ndy11qq7vjprAK4N3Bqp4FQrbNEK8GDKc0z93RPrhHKp47Im+nTQU3PTQWPrXpRFjEvI410actkv/lVhZBffRxoNq63sAWSSQbDyFGkrQdQgBjLK4FdW/pVPtBCmMQqZeGFkES+kFDmAE8YRjSdzzhuDShcj3so/n6VMAlkTfTqosHAYeTTOJXFyJ2pEcwm2lty+f9f9jFdpspl+dnRcQRJuDDxjVUhjHC6JTVJKxyyqiTOtoEmVAqFcBDK29znlDm3LnwpYfkipWyYo0NMafjZYZYhtz+nma4IoioPdV6OuSIDsEM3olhikigMeTg1UzGDQAfsDhdIHRK/Dzxg6CBlGLQAtcAAZiQLhhho90ut9/q7XqFnrlnnhZdLjrK261WrW+vwdb/2FFtccrlnR75FeV/X6O75a2bohBzSR3v6RWQQfGZ22Gni3MhiDTdrKMENVSEaPdmvIESccjybpq8LRt4JNSo3W4vXsqyRgWVmo83x4HwHgVCP0wnkIXWgn2QAAwLmWkxMYAqw0KWCHkQBevVC8KPLFUQCIEYyTJiC9ki87peMHd6XjGzACGOBWWqJLtXjqFIgVQnzscGwe14B7Ex9NkiWgdVIBTYyP1ygrCxgErJDORMl/wQRSAgvGDqHQLjLnnA5VAn55OqcyUoA8mVFJ+5eWZS+l1Ce9foHeLq0Gb6UurlVO5PBBmKpBYDhoky2bB50HCayNzxerB1QgdN7DZMV5gwK0jQOvkZ40kwHs7ngipBEgi+M7Sk6eA4m0w1TnJwt0SwctC36kgWaHbKDyN17lqtasXTZRbzt+X7tXej1lV4fzOO0FG6mr4ve1fE1UXcy7HNDFcT4fABzTLiPh2cycaATAf3d8WDfhDQCWjg1S+eEpv6td3Q6fGUr678ocpjGUPPmDCWJRihQEIgZIkjj8vh1K+rBSAP1JnDACADyPH0STOD7RAvY0GIAdRpE51AHo8LGCiF5pIB0/+PN4d6YpDAOG92Zhoku1OHnFVr4bgE+hQbS28p5p+ZV5g8jyHsxT5RxAQDEj63zGoMeaQTQ57Hglz7B2gFTGX9eTVqOakeiKZW2mOURiAID6kNDQfugXrJUWSC/QEtQgoY1BSdjaWjiiKyZ2gOdswVP5Ha9xMfublK03L/hpOjuna5332+R9ppaezp042t9gCj8NtMnRxLSAshVshNwO6kkSEytKBQCwShRBpdSdMacJIGaahgADAL8lVgkFXE7/ed91qBK+Obond0b+Xdxm/98nniw5vZJxxACAcPBoQRw/ijAVfzeGFrJ9RAyQ/+OY5NcYT3JJf7mVeV9InoBAODf+rhoNAIPmBEK7Ex5MiaD+81RjgJJStv6aiAxjsoucDyA3aEBsNts42EkHggR8h8WmVCkISUvL1Ino5mOIbHnAi9vfUplfdzh3M55qJYeLruq1jYtrSy9drWYkQu1r/czGA8ShWkrgBDACGrIVXHSdRNOjOQdDtq/0y2UAgI+oAxikt3dyq2hBPMcJyCn5g3u15tOKVAIACAd/E9G+4E3EdA7uTF6HYXqABonjVuatqAX0IxQ9AHjFk4F+omhf3RwHkFLK0YpIjowA06oi1QLAQhMDAF7fCdjaioUNII0Dn2gWsF0dQ1miIDBlzuxaKwAJQUF5AbiedVXP5KIMNSdibs1WFWoacLTYXHZVAcZqNI2lDxW5zxIZz9PcjXyAxoFKYldkZgAaN+FnAnYw1VAA6DfnrwFgdWQoAAu4JvrBpGRPC/0NmtFCOEoQa+qfSrQSdvV8VcOTePDRmyaOVAJqS8VoAfJqfJD8Ilr5+dJrNwDqrwNCOomSkjwJAcCLmc0JfJVZSe7rJ2UIVzFJM4CtDygDJF6L2WmKEM0Kb0oXojmwMrVMpF/DQbIHtRMrm2GxBOA12icAeOJL+C9zB+BH5ANwPdLjpf2N11rnA6rl2E8KMlLjVorV3+Q47mqlG4YXNrvu0EaTyMzEhY5/GQBGh3Amv9ah6GtH8BM9YMem0FJnAeX2RTvrLDQMABDRT1YEAHyXmEa0HlkV9MmJtMhFRCth/P8bAYLBEVhMiSZAXyoGtklJAgiZ9ke++5LA18xW/viMX+8pF4odQqJOQAKJqQOV1GS2XEHbN2mlGK0464VoQgDIFwcRrQS7legAK2TfEDkcQKMQ0TI5gZ2IAhsEAGYiCsWlROTYF7ATJQNc9IHmWNXSKm+TVe6k7fKBSqXK1zKgZG7v/kNVt8bnAHMg7eE8CM78ZbQYQByqBAAtQtR2R592OieJFsSU5ElIyCDgpoT/7hWg5iNQ2zTaAKBNXOo5zXbvhzm+C/jiKDGOmP7EkrIQrYTFTASDYwdkGkB0qU5Bh2COhBDehZbzK5t8AGDk1wlZ6PaicA2ejx+NlEz8kP82bQXBinDf6v9ZMABM5d86VgA7JgAALGF5AQAbTwVhCWGRYu5bc/4Vp/uZo2J3yfMy6VlZdZifowpavjcQPkp/u05erTESALQ/A1FP3i8A4E4LAZDdjDvSSKIBSfLr1a0Ap7SX/YAxK+iEcZGYoZhJDeHT2RTrOWoULvx9tMdDt59Nv4dD94gNJHyulwVIklX9mkbbFFLKcsbnUEYPYcgriWLRX4+eUQZ7V9zn+uc+dWFfdH3qnDTvQ/Ix8zAdSBHZznhQF11wJrk50bbPMvMk6nvP5AlIPLgswOVk6veekxmzSv+t0n9TggMARVhJRl4AAABJSSoACAAAAAIAMQECAAcAAAAmAAAAaYcEAAEAAAAuAAAAAAAAAFBpY2FzYQAAAgAAkAcABAAAADAyMjCGkgcAEgAAAEwAAAAAAAAAQVNDSUkAAABTY3JlZW5zaG90" width="328" height="90" class="img_ev3q"></p>
<p>after:
<img decoding="async" loading="lazy" alt="Stubs hover after" src="https://pyrefly.org/assets/images/stub-hover-a62a5894f4cf14e4236b4c792f087ca0.webp" width="989" height="157" class="img_ev3q"></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-do-i-ensure-this-is-part-of-my-ide-experience">How do I ensure this is part of my IDE experience?<a href="https://pyrefly.org/blog/stubs/#how-do-i-ensure-this-is-part-of-my-ide-experience" class="hash-link" aria-label="Direct link to How do I ensure this is part of my IDE experience?" title="Direct link to How do I ensure this is part of my IDE experience?">​</a></h2>
<p>If you are just using Pyrefly out of the box with no custom configuration then no action is needed! Just ensure that you are on the latest version of Pyrefly. These stubs will be bundled along with the Pyrefly installation and become active in your IDE experience.</p>
<p>If you are using Pyrefly with a configuration through <code>pyrefly.toml</code> or <code>pyproject.toml</code> file, we will instead prompt you to install stubs for the specified package. Due to potential version mismatches between installed packages and bundled stubs, we prefer to use stubs directly installed by the user.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-stubs-are-included">What Stubs are included?<a href="https://pyrefly.org/blog/stubs/#what-stubs-are-included" class="hash-link" aria-label="Direct link to What Stubs are included?" title="Direct link to What Stubs are included?">​</a></h2>
<p>Currently all stubs found in the <a href="https://github.com/python/typeshed" target="_blank" rel="noopener noreferrer">Typeshed repository</a> are included.</p>
<p>The following stubs are also included starting with Pyrefly version 0.46</p>
<ul>
<li>Boto3</li>
<li>Botocore</li>
<li>Conans</li>
<li>Matplotlib</li>
<li>Pandas</li>
<li>Scikit-image</li>
<li>Scikit-learn</li>
<li>Sympy</li>
<li>Vispy</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="further-work">Further Work<a href="https://pyrefly.org/blog/stubs/#further-work" class="hash-link" aria-label="Direct link to Further Work" title="Direct link to Further Work">​</a></h2>
<p>We have intentionally built out this support in such a way in order to very easily enable adding more stubs in the future. We invite you to share your feedback in <a href="https://discord.gg/Cf7mFQtW7W" target="_blank" rel="noopener noreferrer">discord</a>, or by opening a <a href="https://github.com/facebook/pyrefly/issues" target="_blank" rel="noopener noreferrer">GitHub issue</a>. We’d love to know how these enhancements are improving your workflow or any issues you come across. Your input will help us shape future releases and ensure Pyrefly remains a robust tool for the Python community.</p>]]></content:encoded>
            <category>IDE</category>
        </item>
        <item>
            <title><![CDATA[Making Pyrefly Diagnostics 18x Faster]]></title>
            <link>https://pyrefly.org/blog/2026/02/06/performance-improvements/</link>
            <guid>https://pyrefly.org/blog/2026/02/06/performance-improvements/</guid>
            <pubDate>Fri, 06 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn about the recent performance improvements made to Pyrefly's language server capabilities]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Hero image" src="https://pyrefly.org/assets/images/18x_faster-064e9ea631da625f6e1a88464b444f95.png" width="1216" height="644" class="img_ev3q"></p>
<p>As we move closer to a stable release of Pyrefly, our efforts have moved from expanding the language server’s capabilities to tackling performance edge cases. Recently, our friends at Astral <a href="https://astral.sh/blog/ty" target="_blank" rel="noopener noreferrer">alerted us to a specific edge case</a> that is a great example of the kind of issues we’ve been aiming to uncover. The specific example Astral highlighted showed that in some edge cases Pyrefly could take multiple seconds to update diagnostics after editing. This is much slower than expected (used across Meta’s codebases, Pyrefly usually takes less than 10 milliseconds to recheck files after saving them) and prompted us to investigate further.</p>
<p>Now we’d like to share the story of how that edge case led us to rethink our underlying approach to diagnostics and dependency tracking, improving the speed at which Pyrefly's type errors update by 18x in the process.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-problem">The Problem<a href="https://pyrefly.org/blog/2026/02/06/performance-improvements/#the-problem" class="hash-link" aria-label="Direct link to The Problem" title="Direct link to The Problem">​</a></h2>
<p>Say you’re working in a very large codebase and have two files open in your editor, A and B.</p>
<ul>
<li>A is a “load-bearing” file that has many reverse dependencies - that is to say, many files directly or transitively import A.</li>
<li>B is one of the files that imports A.</li>
</ul>
<p>Incremental type checking should be snappy - when you edit A and save the file, any effects on the type errors in B should be reflected almost instantly. However, in a small number of cases, we found that updates could take multiple seconds.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="how-incremental-updates-work-in-pyrefly">How Incremental Updates Work in Pyrefly<a href="https://pyrefly.org/blog/2026/02/06/performance-improvements/#how-incremental-updates-work-in-pyrefly" class="hash-link" aria-label="Direct link to How Incremental Updates Work in Pyrefly" title="Direct link to How Incremental Updates Work in Pyrefly">​</a></h3>
<p>To understand how this problem arises, we need to look at how incremental updates currently work. Pyrefly’s analysis operates at the module level, meaning it keeps track of the types exported from each module, as well as the dependencies between modules. Let’s illustrate with a simple example.</p>
<p><img decoding="async" loading="lazy" alt="import diagram" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U3R5bGVUeXBlPSJ0ZXh0L2NzcyIgZGF0YS1kaWFncmFtLXR5cGU9IkRFU0NSSVBUSU9OIiBoZWlnaHQ9IjE5NHB4IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIiBzdHlsZT0id2lkdGg6NjQxcHg7aGVpZ2h0OjE5NHB4O2JhY2tncm91bmQ6I0ZGRkZGRjsiIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDY0MSAxOTQiIHdpZHRoPSI2NDFweCIgem9vbUFuZFBhbj0ibWFnbmlmeSI+PGRlZnMvPjxnPjwhLS1lbnRpdHkgQS0tPjxnIGNsYXNzPSJlbnRpdHkiIGRhdGEtZW50aXR5PSJBIiBkYXRhLXNvdXJjZS1saW5lPSI1IiBkYXRhLXVpZD0iZW50MDAwMiIgaWQ9ImVudGl0eV9BIj48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjY4Ljg5MDYiIHJ4PSIyLjUiIHJ5PSIyLjUiIHN0eWxlPSJzdHJva2U6I0ZGMDAwMDtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjkxLjMxMjUiIHg9IjI3Ny41IiB5PSIxMjAiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIzMS4xOTkyIiB4PSIyODcuNSIgeT0iMTQyLjk5NTEiPkEucHk8L3RleHQ+PHRleHQgZmlsbD0iI0ZGMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iNzEuMzEyNSIgeD0iMjg3LjUiIHk9IjE1OS4yOTIiPmNsYXNzIFg6IC4uLjwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI3MC4yNzM0IiB4PSIyODcuNSIgeT0iMTc1LjU4ODkiPmNsYXNzIFk6IC4uLjwvdGV4dD48L2c+PCEtLWVudGl0eSBCMS0tPjxnIGNsYXNzPSJlbnRpdHkiIGRhdGEtZW50aXR5PSJCMSIgZGF0YS1zb3VyY2UtbGluZT0iNiIgZGF0YS11aWQ9ImVudDAwMDMiIGlkPSJlbnRpdHlfQjEiPjxyZWN0IGZpbGw9IiNGMUYxRjEiIGhlaWdodD0iNTIuNTkzOCIgcng9IjIuNSIgcnk9IjIuNSIgc3R5bGU9InN0cm9rZTojRkYwMDAwO3N0cm9rZS13aWR0aDowLjU7IiB3aWR0aD0iMTMxLjYzMDkiIHg9IjciIHk9IjciLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI0MC4xMzM4IiB4PSIxNyIgeT0iMjkuOTk1MSI+QjEucHk8L3RleHQ+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iMTExLjYzMDkiIHg9IjE3IiB5PSI0Ni4yOTIiPmZyb20gQSBpbXBvcnQgWDwvdGV4dD48L2c+PCEtLWVudGl0eSBCMi0tPjxnIGNsYXNzPSJlbnRpdHkiIGRhdGEtZW50aXR5PSJCMiIgZGF0YS1zb3VyY2UtbGluZT0iNyIgZGF0YS11aWQ9ImVudDAwMDQiIGlkPSJlbnRpdHlfQjIiPjxyZWN0IGZpbGw9IiNGMUYxRjEiIGhlaWdodD0iNTIuNTkzOCIgcng9IjIuNSIgcnk9IjIuNSIgc3R5bGU9InN0cm9rZTojRkYwMDAwO3N0cm9rZS13aWR0aDowLjU7IiB3aWR0aD0iMTMwLjU5MTgiIHg9IjE3NC41IiB5PSI3Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iNDAuMTMzOCIgeD0iMTg0LjUiIHk9IjI5Ljk5NTEiPkIyLnB5PC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjExMC41OTE4IiB4PSIxODQuNSIgeT0iNDYuMjkyIj5mcm9tIEEgaW1wb3J0IFk8L3RleHQ+PC9nPjwhLS1lbnRpdHkgQjMtLT48ZyBjbGFzcz0iZW50aXR5IiBkYXRhLWVudGl0eT0iQjMiIGRhdGEtc291cmNlLWxpbmU9IjgiIGRhdGEtdWlkPSJlbnQwMDA1IiBpZD0iZW50aXR5X0IzIj48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjUyLjU5MzgiIHJ4PSIyLjUiIHJ5PSIyLjUiIHN0eWxlPSJzdHJva2U6I0ZGMDAwMDtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjEzMC41OTE4IiB4PSIzNDAuNSIgeT0iNyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjQwLjEzMzgiIHg9IjM1MC41IiB5PSIyOS45OTUxIj5CMy5weTwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIxMTAuNTkxOCIgeD0iMzUwLjUiIHk9IjQ2LjI5MiI+ZnJvbSBBIGltcG9ydCBZPC90ZXh0PjwvZz48IS0tZW50aXR5IEI0LS0+PGcgY2xhc3M9ImVudGl0eSIgZGF0YS1lbnRpdHk9IkI0IiBkYXRhLXNvdXJjZS1saW5lPSI5IiBkYXRhLXVpZD0iZW50MDAwNiIgaWQ9ImVudGl0eV9CNCI+PHJlY3QgZmlsbD0iI0YxRjFGMSIgaGVpZ2h0PSI1Mi41OTM4IiByeD0iMi41IiByeT0iMi41IiBzdHlsZT0ic3Ryb2tlOiNGRjAwMDA7c3Ryb2tlLXdpZHRoOjAuNTsiIHdpZHRoPSIxMjkuMDQiIHg9IjUwNi41IiB5PSI3Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iNDAuMTMzOCIgeD0iNTE2LjUiIHk9IjI5Ljk5NTEiPkI0LnB5PC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjEwOS4wNCIgeD0iNTE2LjUiIHk9IjQ2LjI5MiI+ZnJvbSBBIGltcG9ydCAqPC90ZXh0PjwvZz48IS0tbGluayBCMSB0byBBLS0+PGcgY2xhc3M9ImxpbmsiIGRhdGEtZW50aXR5LTE9IkIxIiBkYXRhLWVudGl0eS0yPSJBIiBkYXRhLXNvdXJjZS1saW5lPSIxMCIgZGF0YS11aWQ9ImxuazciIGlkPSJsaW5rX0IxX0EiPjxwYXRoIGQ9Ik0xMjYuNjMsNjAuMDMgQzE3MS41NCw4MS40IDIyOS41MTIyLDEwOS4wMDE4IDI3Mi4wMDIyLDEyOS4yMjE4IiBmaWxsPSJub25lIiBpZD0iQjEtdG8tQSIgc3R5bGU9InN0cm9rZTojRkYwMDAwO3N0cm9rZS13aWR0aDoxOyIvPjxwb2x5Z29uIGZpbGw9IiNGRjAwMDAiIHBvaW50cz0iMjc3LjQyLDEzMS44LDI3MS4wMTIxLDEyNC4zMjA4LDI3Mi45MDUxLDEyOS42NTE1LDI2Ny41NzQ1LDEzMS41NDQ2LDI3Ny40MiwxMzEuOCIgc3R5bGU9InN0cm9rZTojRkYwMDAwO3N0cm9rZS13aWR0aDoxOyIvPjwvZz48IS0tbGluayBCMiB0byBBLS0+PGcgY2xhc3M9ImxpbmsiIGRhdGEtZW50aXR5LTE9IkIyIiBkYXRhLWVudGl0eS0yPSJBIiBkYXRhLXNvdXJjZS1saW5lPSIxMSIgZGF0YS11aWQ9ImxuazgiIGlkPSJsaW5rX0IyX0EiPjxwYXRoIGQ9Ik0yNTcuODEsNjAuMDMgQzI3MCw3Ny41MSAyODIuODY3OSw5NS45NTg1IDI5Ni4xMTc5LDExNC45NTg1IiBmaWxsPSJub25lIiBpZD0iQjItdG8tQSIgc3R5bGU9InN0cm9rZTojRkYwMDAwO3N0cm9rZS13aWR0aDoxOyIvPjxwb2x5Z29uIGZpbGw9IiNGRjAwMDAiIHBvaW50cz0iMjk5LjU1LDExOS44OCwyOTcuNjgyOSwxMTAuMjA5NywyOTYuNjg5OSwxMTUuNzc4OCwyOTEuMTIwOSwxMTQuNzg1OCwyOTkuNTUsMTE5Ljg4IiBzdHlsZT0ic3Ryb2tlOiNGRjAwMDA7c3Ryb2tlLXdpZHRoOjE7Ii8+PC9nPjwhLS1saW5rIEIzIHRvIEEtLT48ZyBjbGFzcz0ibGluayIgZGF0YS1lbnRpdHktMT0iQjMiIGRhdGEtZW50aXR5LTI9IkEiIGRhdGEtc291cmNlLWxpbmU9IjEyIiBkYXRhLXVpZD0ibG5rOSIgaWQ9ImxpbmtfQjNfQSI+PHBhdGggZD0iTTM4OC4xOSw2MC4wMyBDMzc2LDc3LjUxIDM2My4xMzIxLDk1Ljk1ODUgMzQ5Ljg4MjEsMTE0Ljk1ODUiIGZpbGw9Im5vbmUiIGlkPSJCMy10by1BIiBzdHlsZT0ic3Ryb2tlOiNGRjAwMDA7c3Ryb2tlLXdpZHRoOjE7Ii8+PHBvbHlnb24gZmlsbD0iI0ZGMDAwMCIgcG9pbnRzPSIzNDYuNDUsMTE5Ljg4LDM1NC44NzkxLDExNC43ODU4LDM0OS4zMTAxLDExNS43Nzg4LDM0OC4zMTcxLDExMC4yMDk3LDM0Ni40NSwxMTkuODgiIHN0eWxlPSJzdHJva2U6I0ZGMDAwMDtzdHJva2Utd2lkdGg6MTsiLz48L2c+PCEtLWxpbmsgQjQgdG8gQS0tPjxnIGNsYXNzPSJsaW5rIiBkYXRhLWVudGl0eS0xPSJCNCIgZGF0YS1lbnRpdHktMj0iQSIgZGF0YS1zb3VyY2UtbGluZT0iMTMiIGRhdGEtdWlkPSJsbmsxMCIgaWQ9ImxpbmtfQjRfQSI+PHBhdGggZD0iTTUxNy44LDYwLjAzIEM0NzMuMzksODEuMzQgNDE2LjE3OTIsMTA4Ljc4MzggMzc0LjAyOTIsMTI5LjAxMzgiIGZpbGw9Im5vbmUiIGlkPSJCNC10by1BIiBzdHlsZT0ic3Ryb2tlOiNGRjAwMDA7c3Ryb2tlLXdpZHRoOjE7Ii8+PHBvbHlnb24gZmlsbD0iI0ZGMDAwMCIgcG9pbnRzPSIzNjguNjIsMTMxLjYxLDM3OC40NjQ2LDEzMS4zMjE5LDM3My4xMjc3LDEyOS40NDY1LDM3NS4wMDMxLDEyNC4xMDk2LDM2OC42MiwxMzEuNjEiIHN0eWxlPSJzdHJva2U6I0ZGMDAwMDtzdHJva2Utd2lkdGg6MTsiLz48L2c+PCEtLVNSQz1bVFN2MTJlQ200ME5HVkt1bmsyb09lZDRiOFhXbGVVMGNRNWdhY2VJOUs0aGZ0UGljTTJsT3RWM19vc0ppUkxINmU5VzFQUnk4VFBxYUYwYTBycFg4aEZNYXFKSzNVSDd4YkhNWWxjX0x6WjJnZzdicWdrV19OVXd3aWYtNGpUM2FHMmFqcFp1bEVuTklyZ1NIMjBsTHhuQVUtMnFOckV1aDY2VVpTTzc2RXZ4aU83ZTBzbTNKN01KXzhUbDF4MDJVMGltOEpvMkVJdFNpSnpVMWhLQ01YWlMwXS0tPjwvZz48L3N2Zz4=" width="641" height="194" class="img_ev3q"></p>
<p>The above image demonstrates a file A that has 4 reverse-dependencies. B1-B4 all rely on the types exported from A.</p>
<p>When file A is edited &amp; saved, we recheck A to see if any of its exported types also change. If so, we recheck every file that imports A (and if any of those files’ exports changed, we recheck their dependencies, and so on until the change to A has been successfully propagated everywhere). After all of that finishes, we display updated errors in the IDE.</p>
<p>There are two key observations here:</p>
<ol>
<li>Our dependency tracking rechecks a module when <em>any</em> export changes, even if the module does not depend on the export that changed. This can lead to modules being rechecked unnecessarily.</li>
<li>We are waiting until the update has completely propagated to every affected file before sending any updated diagnostics, when in practice we mostly care about the files the user has open.</li>
</ol>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="solution-1-fine-grained-dependency-tracking">Solution 1: Fine-grained dependency tracking<a href="https://pyrefly.org/blog/2026/02/06/performance-improvements/#solution-1-fine-grained-dependency-tracking" class="hash-link" aria-label="Direct link to Solution 1: Fine-grained dependency tracking" title="Direct link to Solution 1: Fine-grained dependency tracking">​</a></h2>
<p>Instead of just tracking whether or not a module depends on another module for invalidation, we can specifically track what’s used.</p>
<p>Looking again at the example above, instead of rechecking everything that imports A, with smarter dependency tracking we could only recheck a subset of relevant files:</p>
<p><img decoding="async" loading="lazy" alt="import diagram improved" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U3R5bGVUeXBlPSJ0ZXh0L2NzcyIgZGF0YS1kaWFncmFtLXR5cGU9IkRFU0NSSVBUSU9OIiBoZWlnaHQ9IjE5NHB4IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJub25lIiBzdHlsZT0id2lkdGg6NjQxcHg7aGVpZ2h0OjE5NHB4O2JhY2tncm91bmQ6I0ZGRkZGRjsiIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDY0MSAxOTQiIHdpZHRoPSI2NDFweCIgem9vbUFuZFBhbj0ibWFnbmlmeSI+PGRlZnMvPjxnPjwhLS1lbnRpdHkgQS0tPjxnIGNsYXNzPSJlbnRpdHkiIGRhdGEtZW50aXR5PSJBIiBkYXRhLXNvdXJjZS1saW5lPSI5IiBkYXRhLXVpZD0iZW50MDAwMiIgaWQ9ImVudGl0eV9BIj48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjY4Ljg5MDYiIHJ4PSIyLjUiIHJ5PSIyLjUiIHN0eWxlPSJzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjkxLjMxMjUiIHg9IjI3Ny41IiB5PSIxMjAiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIzMS4xOTkyIiB4PSIyODcuNSIgeT0iMTQyLjk5NTEiPkEucHk8L3RleHQ+PHRleHQgZmlsbD0iI0ZGMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iNzEuMzEyNSIgeD0iMjg3LjUiIHk9IjE1OS4yOTIiPmNsYXNzIFg6IC4uLjwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI3MC4yNzM0IiB4PSIyODcuNSIgeT0iMTc1LjU4ODkiPmNsYXNzIFk6IC4uLjwvdGV4dD48L2c+PCEtLWVudGl0eSBCMS0tPjxnIGNsYXNzPSJlbnRpdHkiIGRhdGEtZW50aXR5PSJCMSIgZGF0YS1zb3VyY2UtbGluZT0iMTAiIGRhdGEtdWlkPSJlbnQwMDAzIiBpZD0iZW50aXR5X0IxIj48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjUyLjU5MzgiIHJ4PSIyLjUiIHJ5PSIyLjUiIHN0eWxlPSJzdHJva2U6I0ZGMDAwMDtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjEzMS42MzA5IiB4PSI3IiB5PSI3Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iNDAuMTMzOCIgeD0iMTciIHk9IjI5Ljk5NTEiPkIxLnB5PC90ZXh0Pjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjExMS42MzA5IiB4PSIxNyIgeT0iNDYuMjkyIj5mcm9tIEEgaW1wb3J0IFg8L3RleHQ+PC9nPjwhLS1lbnRpdHkgQjItLT48ZyBjbGFzcz0iZW50aXR5IiBkYXRhLWVudGl0eT0iQjIiIGRhdGEtc291cmNlLWxpbmU9IjExIiBkYXRhLXVpZD0iZW50MDAwNCIgaWQ9ImVudGl0eV9CMiI+PHJlY3QgZmlsbD0iI0YxRjFGMSIgaGVpZ2h0PSI1Mi41OTM4IiByeD0iMi41IiByeT0iMi41IiBzdHlsZT0ic3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjAuNTsiIHdpZHRoPSIxMzAuNTkxOCIgeD0iMTc0LjUiIHk9IjciLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSI0MC4xMzM4IiB4PSIxODQuNSIgeT0iMjkuOTk1MSI+QjIucHk8L3RleHQ+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZyIgdGV4dExlbmd0aD0iMTEwLjU5MTgiIHg9IjE4NC41IiB5PSI0Ni4yOTIiPmZyb20gQSBpbXBvcnQgWTwvdGV4dD48L2c+PCEtLWVudGl0eSBCMy0tPjxnIGNsYXNzPSJlbnRpdHkiIGRhdGEtZW50aXR5PSJCMyIgZGF0YS1zb3VyY2UtbGluZT0iMTIiIGRhdGEtdWlkPSJlbnQwMDA1IiBpZD0iZW50aXR5X0IzIj48cmVjdCBmaWxsPSIjRjFGMUYxIiBoZWlnaHQ9IjUyLjU5MzgiIHJ4PSIyLjUiIHJ5PSIyLjUiIHN0eWxlPSJzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC41OyIgd2lkdGg9IjEzMC41OTE4IiB4PSIzNDAuNSIgeT0iNyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjQwLjEzMzgiIHg9IjM1MC41IiB5PSIyOS45OTUxIj5CMy5weTwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIxMTAuNTkxOCIgeD0iMzUwLjUiIHk9IjQ2LjI5MiI+ZnJvbSBBIGltcG9ydCBZPC90ZXh0PjwvZz48IS0tZW50aXR5IEI0LS0+PGcgY2xhc3M9ImVudGl0eSIgZGF0YS1lbnRpdHk9IkI0IiBkYXRhLXNvdXJjZS1saW5lPSIxMyIgZGF0YS11aWQ9ImVudDAwMDYiIGlkPSJlbnRpdHlfQjQiPjxyZWN0IGZpbGw9IiNGMUYxRjEiIGhlaWdodD0iNTIuNTkzOCIgcng9IjIuNSIgcnk9IjIuNSIgc3R5bGU9InN0cm9rZTojRkYwMDAwO3N0cm9rZS13aWR0aDowLjU7IiB3aWR0aD0iMTI5LjA0IiB4PSI1MDYuNSIgeT0iNyIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBsZW5ndGhBZGp1c3Q9InNwYWNpbmciIHRleHRMZW5ndGg9IjQwLjEzMzgiIHg9IjUxNi41IiB5PSIyOS45OTUxIj5CNC5weTwvdGV4dD48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nIiB0ZXh0TGVuZ3RoPSIxMDkuMDQiIHg9IjUxNi41IiB5PSI0Ni4yOTIiPmZyb20gQSBpbXBvcnQgKjwvdGV4dD48L2c+PCEtLWxpbmsgQjEgdG8gQS0tPjxnIGNsYXNzPSJsaW5rIiBkYXRhLWVudGl0eS0xPSJCMSIgZGF0YS1lbnRpdHktMj0iQSIgZGF0YS1zb3VyY2UtbGluZT0iMTQiIGRhdGEtdWlkPSJsbms3IiBpZD0ibGlua19CMV9BIj48cGF0aCBkPSJNMTI2LjYzLDYwLjAzIEMxNzEuNTQsODEuNCAyMjkuNTEyMiwxMDkuMDAxOCAyNzIuMDAyMiwxMjkuMjIxOCIgZmlsbD0ibm9uZSIgaWQ9IkIxLXRvLUEiIHN0eWxlPSJzdHJva2U6I0ZGMDAwMDtzdHJva2Utd2lkdGg6MTsiLz48cG9seWdvbiBmaWxsPSIjRkYwMDAwIiBwb2ludHM9IjI3Ny40MiwxMzEuOCwyNzEuMDEyMSwxMjQuMzIwOCwyNzIuOTA1MSwxMjkuNjUxNSwyNjcuNTc0NSwxMzEuNTQ0NiwyNzcuNDIsMTMxLjgiIHN0eWxlPSJzdHJva2U6I0ZGMDAwMDtzdHJva2Utd2lkdGg6MTsiLz48L2c+PCEtLWxpbmsgQjIgdG8gQS0tPjxnIGNsYXNzPSJsaW5rIiBkYXRhLWVudGl0eS0xPSJCMiIgZGF0YS1lbnRpdHktMj0iQSIgZGF0YS1zb3VyY2UtbGluZT0iMTUiIGRhdGEtdWlkPSJsbms4IiBpZD0ibGlua19CMl9BIj48cGF0aCBkPSJNMjU3LjgxLDYwLjAzIEMyNzAsNzcuNTEgMjgyLjg2NzksOTUuOTU4NSAyOTYuMTE3OSwxMTQuOTU4NSIgZmlsbD0ibm9uZSIgaWQ9IkIyLXRvLUEiIHN0eWxlPSJzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MTsiLz48cG9seWdvbiBmaWxsPSIjMDAwMDAwIiBwb2ludHM9IjI5OS41NSwxMTkuODgsMjk3LjY4MjksMTEwLjIwOTcsMjk2LjY4OTksMTE1Ljc3ODgsMjkxLjEyMDksMTE0Ljc4NTgsMjk5LjU1LDExOS44OCIgc3R5bGU9InN0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxOyIvPjwvZz48IS0tbGluayBCMyB0byBBLS0+PGcgY2xhc3M9ImxpbmsiIGRhdGEtZW50aXR5LTE9IkIzIiBkYXRhLWVudGl0eS0yPSJBIiBkYXRhLXNvdXJjZS1saW5lPSIxNiIgZGF0YS11aWQ9ImxuazkiIGlkPSJsaW5rX0IzX0EiPjxwYXRoIGQ9Ik0zODguMTksNjAuMDMgQzM3Niw3Ny41MSAzNjMuMTMyMSw5NS45NTg1IDM0OS44ODIxLDExNC45NTg1IiBmaWxsPSJub25lIiBpZD0iQjMtdG8tQSIgc3R5bGU9InN0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoxOyIvPjxwb2x5Z29uIGZpbGw9IiMwMDAwMDAiIHBvaW50cz0iMzQ2LjQ1LDExOS44OCwzNTQuODc5MSwxMTQuNzg1OCwzNDkuMzEwMSwxMTUuNzc4OCwzNDguMzE3MSwxMTAuMjA5NywzNDYuNDUsMTE5Ljg4IiBzdHlsZT0ic3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjE7Ii8+PC9nPjwhLS1saW5rIEI0IHRvIEEtLT48ZyBjbGFzcz0ibGluayIgZGF0YS1lbnRpdHktMT0iQjQiIGRhdGEtZW50aXR5LTI9IkEiIGRhdGEtc291cmNlLWxpbmU9IjE3IiBkYXRhLXVpZD0ibG5rMTAiIGlkPSJsaW5rX0I0X0EiPjxwYXRoIGQ9Ik01MTcuOCw2MC4wMyBDNDczLjM5LDgxLjM0IDQxNi4xNzkyLDEwOC43ODM4IDM3NC4wMjkyLDEyOS4wMTM4IiBmaWxsPSJub25lIiBpZD0iQjQtdG8tQSIgc3R5bGU9InN0cm9rZTojRkYwMDAwO3N0cm9rZS13aWR0aDoxOyIvPjxwb2x5Z29uIGZpbGw9IiNGRjAwMDAiIHBvaW50cz0iMzY4LjYyLDEzMS42MSwzNzguNDY0NiwxMzEuMzIxOSwzNzMuMTI3NywxMjkuNDQ2NSwzNzUuMDAzMSwxMjQuMTA5NiwzNjguNjIsMTMxLjYxIiBzdHlsZT0ic3Ryb2tlOiNGRjAwMDA7c3Ryb2tlLXdpZHRoOjE7Ii8+PC9nPjwhLS1TUkM9W1RPelYydThtNUNOVl9IR05VbWlxX0ZDYUNqMi1YOTh6QjVxYkpaVmtYOTNla3hTczhZcnhrdnBwRXNUZHJ4UFhlNFE2SjhvSlA4eGdzYTVJZjNxV2dxU3dOM1kza21FRzJzbU94V0tOMjJURXd5dnZwRFdDS1RwY3ZhZkRjWTloUjBxWEZ2dFFxdXNWRHp0Q2JyQ3JmRk5CSlJIQlFhd0xXWTgxdF9WSmRUNTlETFlyREFlQkw0NHNCeWEzcXQ5NmVKVDJzcWsxOG5JNm9tRE9CNWg0bWZMT1FNRlgwZXAtV3o0Mlo1VjBoR05aaG81d2NOVk9RRU5lNFZzVEYwSkY3ajd4WUVWMDRtMDBdLS0+PC9nPjwvc3ZnPg==" width="641" height="194" class="img_ev3q"></p>
<p>But the types a module depends on are not only from imports. Imagine a program like this with a chained method call:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> module </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Class</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token class-name">Class</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">returns_type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">operation_on_type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Even though we only import <code>Class</code>, this module still depends on the type returned in <code>returns_type()</code>.</p>
<p>Similarly, <code>returns_type()</code> could be implemented in a <code>base</code> of <code>Class</code>, so we really might be depending on a class hierarchy instead of just <code>Class</code> from <code>module</code>.</p>
<p>And should a change to an unrelated part of numpy invalidate this program (i.e. recheck the module)? Probably not.</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> numpy </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> np</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">array </span><span class="token operator">=</span><span class="token plain"> np</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">ones</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>As you can see, a program’s type dependency structure is much more complicated and expansive than it might originally appear.</p>
<p>Since Pyrefly v0.51.1, we no longer invalidate a module any time a dependency changes. We now track exactly which types the module depends on at lookup to prune the modules that must be recomputed. In load-bearing files like the example in the introduction, this change turns what could be a 2000+ module invalidation into an invalidation of just over 100 modules.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="solution-2-streaming-diagnostics">Solution 2: Streaming diagnostics<a href="https://pyrefly.org/blog/2026/02/06/performance-improvements/#solution-2-streaming-diagnostics" class="hash-link" aria-label="Direct link to Solution 2: Streaming diagnostics" title="Direct link to Solution 2: Streaming diagnostics">​</a></h2>
<p>The other key insight from earlier is that we’re waiting for the update to finish completely before refreshing the errors in the IDE. To improve this, we could stream updated diagnostics to the IDE as soon as an open file has been rechecked, without waiting for other modules to be rechecked.</p>
<p>Let’s imagine a project with a dependency structure that looks like this. A is the file that we are editing, and B is another file that we have open, which is affected by edits to the types in A. As a user, what we want is for changes we make in A to be reflected in B with minimal delay.</p>
<p><img decoding="async" loading="lazy" alt="nodes diagram" src="https://pyrefly.org/assets/images/a_b_nodes_1-cffa3f5be4e5da33c5e60e6265fbffbb.png" width="1188" height="1272" class="img_ev3q"></p>
<p>Highlighted in red are the reverse dependencies of A, which may need to be rechecked if A is updated. Previously, we would recheck all the red modules before updating the diagnostics for B.</p>
<p><img decoding="async" loading="lazy" alt="nodes diagram red" src="https://pyrefly.org/assets/images/a_b_nodes_2-6d195abd0e35afb699adaa1b328affa4.png" width="1372" height="1308" class="img_ev3q"></p>
<p>Highlighted in blue are the transitive dependencies of B. Only changes to those modules affect the types and diagnostics for B.</p>
<p><img decoding="async" loading="lazy" alt="nodes diagram blue" src="https://pyrefly.org/assets/images/a_b_nodes_3-fa2d797636189819a2e2bccd6d667ab1.png" width="1338" height="1330" class="img_ev3q"></p>
<p>The image below shows the intersection of A’s reverse dependencies and B’s dependencies in purple. Once those modules are checked, we can show updated diagnostics for open files to the user, while continuing to check the remaining modules (red) in the background.</p>
<p><img decoding="async" loading="lazy" alt="nodes diagram purple" src="https://pyrefly.org/assets/images/a_b_nodes_4-e1cc33ac035b998695e777955e10b1b5.png" width="1402" height="1442" class="img_ev3q"></p>
<p>In order to provide a smooth IDE experience, Pyrefly runs language server operations (e.g. hover, autocomplete, go-to-def) on one thread and rechecks on another thread,  that way users can still use language server features while a recheck is happening in the background. Previously, the main language server thread would send updated diagnostics to the IDE, and only did so once it received a signal from the recheck thread that a recheck had finished. Now with streaming diagnostics in Pyrefly v0.52.0, the recheck thread can emit the diagnostics to the frontend directly.</p>
<p>This change means that diagnostics can now be emitted from both the main thread and the recheck thread. To prevent conflicts between diagnostics sent from different threads, we add a restriction so that only one thread may send diagnostics for a given file at a given time. When a recheck is ongoing, the main thread cannot send diagnostics for any files eligible for streaming diagnostics.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="results">Results<a href="https://pyrefly.org/blog/2026/02/06/performance-improvements/#results" class="hash-link" aria-label="Direct link to Results" title="Direct link to Results">​</a></h2>
<p>Combined, these two optimizations enable Pyrefly to provide updated diagnostics 18x faster than before. Going back to the example from the introduction, these changes reduce the diagnostic update time from ~3.6 seconds to under 200ms on an M4 Macbook Pro. For users, this means type errors refresh instantly after saving a file, a dramatic improvement:</p>
<video src="/videos/18x_faster.mp4" width="720" muted="" loop="" autoplay="" playsinline="" preload="auto"></video>
<p>To see how we benchmarked this, you can check out testing instructions on GitHub <a href="https://github.com/facebook/pyrefly/pull/2344" target="_blank" rel="noopener noreferrer">here</a></p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="whats-next">What’s Next?<a href="https://pyrefly.org/blog/2026/02/06/performance-improvements/#whats-next" class="hash-link" aria-label="Direct link to What’s Next?" title="Direct link to What’s Next?">​</a></h2>
<p>This was a tricky case to fix and we’ve made significant progress, but how fast is fast enough?</p>
<p>Speed continues to be a top priority for us on the Pyrefly team. While we’ve addressed the core problem, we’re always on the lookout for opportunities to further optimize performance, so stay tuned for further updates and make sure to regularly update your Pyrefly version. As we move toward v1, we’re carefully weighing additional speed improvements alongside other critical priorities like memory efficiency, bug fixes, and enhanced inference capabilities. Our goal is to deliver the fastest experience possible without compromising on overall functionality.</p>
<p>As always, if you’ve found a problem, please let us know by <a href="https://github.com/facebook/pyrefly/issues" target="_blank" rel="noopener noreferrer">opening a GitHub issue</a>.</p>
<p>And if you’ve made it this far, thanks for journeying down this rabbit hole with us! We hope you give Pyrefly a try if you haven’t already, see you for the next update!</p>]]></content:encoded>
            <category>language-server</category>
            <category>IDE</category>
            <category>performance</category>
        </item>
        <item>
            <title><![CDATA[4 Pyrefly Type Narrowing Patterns that make Type Checking more Intuitive]]></title>
            <link>https://pyrefly.org/blog/type-narrowing/</link>
            <guid>https://pyrefly.org/blog/type-narrowing/</guid>
            <pubDate>Mon, 26 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn about 4 ways Pyrefly narrows types, reducing the need to explicitly cast in your code.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" src="https://pyrefly.org/assets/images/type-narrowing-blog-688b8aae77abd588a29ddb9d5bab231f.png" width="5334" height="3000" class="img_ev3q"></p>
<p>If we view a type of an expression or variable as the set of possible values it can resolve to,  narrowing is the process of applying constraints on those values. For example, if you have a variable <code>x</code> whose contents you don’t know, an <code>if isinstance(x, int)</code> check will narrow the type of <code>x</code> to <code>int</code> inside the body of the if-statement.</p>
<p>Since Python is a <a href="https://en.wikipedia.org/wiki/Duck_typing" target="_blank" rel="noopener noreferrer">duck-typed</a> language, programs often narrow types by checking a structural property of something rather than just its class name. For a type checker, understanding a wide variety of narrowing patterns is essential for making it as easy as possible for users to type check their code and reduce the amount of changes made purely to “satisfy the type checker”.</p>
<p>In this blog post, we’ll go over some cool forms of narrowing that Pyrefly supports, which allows it to understand common code patterns in Python.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="hasattr-and-getattr"><code>hasattr</code> and <code>getattr</code><a href="https://pyrefly.org/blog/type-narrowing/#hasattr-and-getattr" class="hash-link" aria-label="Direct link to hasattr-and-getattr" title="Direct link to hasattr-and-getattr">​</a></h2>
<p>In a dynamic codebase where not every field is initialized in the constructor, you may encounter code that dynamically adds attributes to classes without declaring them in the class body.</p>
<p>Even if a field is not declared on the class, Pyrefly can understand a <code>hasattr</code> check indicates that the field exists.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">func</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">object</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">hasattr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"value"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       val </span><span class="token operator">=</span><span class="token plain"> x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">value </span><span class="token comment" style="color:rgb(98, 114, 164)"># Pyrefly knows that `x` has the `value` attribute and won’t error here</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Some fields are declared on the class but not always initialized, so accesses have to be done with <code>getattr</code>. To support this pattern, any checks on <code>getattr(x, “field”)</code> will generally narrow the type the same way as the same check on <code>x.field</code>. This means that using <code>getattr()</code> in a guard will narrow the field to be truthy.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> typing </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Literal</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> assert_type</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">C</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    flag</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">bool</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># the flag is not always set`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">func</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> C</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">getattr</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"flag"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       </span><span class="token comment" style="color:rgb(98, 114, 164)"># here Pyrefly knows that the flag is set to True</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       assert_type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">flag</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Literal</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="tagged-unions">Tagged Unions<a href="https://pyrefly.org/blog/type-narrowing/#tagged-unions" class="hash-link" aria-label="Direct link to Tagged Unions" title="Direct link to Tagged Unions">​</a></h2>
<p><a href="https://en.wikipedia.org/wiki/Tagged_union" target="_blank" rel="noopener noreferrer">Tagged unions</a> are a common feature in functional programming languages, but they are not a first-class language construct in Python.  Although Python’s union types are untagged, Pyrefly can emulate a tagged union by creating a union where each member explicitly defines the same field to use as a tag. Pyrefly can then check the value of the field to narrow the union to the corresponding member.</p>
<p>This works for regular classes, as well as typed dicts.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> typing </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> TypedDict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Literal</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">Ok</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">TypedDict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Literal</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"ok"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   payload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">bytes</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">Err</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">TypedDict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Literal</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"error"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin" style="color:rgb(189, 147, 249)">type</span><span class="token plain"> Response </span><span class="token operator">=</span><span class="token plain"> Ok </span><span class="token operator">|</span><span class="token plain"> Err</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">read</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">bytes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"result"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"ok"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"payload"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">raise</span><span class="token plain"> Exception</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"message"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="tuple-length-narrowing">Tuple length narrowing<a href="https://pyrefly.org/blog/type-narrowing/#tuple-length-narrowing" class="hash-link" aria-label="Direct link to Tuple length narrowing" title="Direct link to Tuple length narrowing">​</a></h2>
<p>When you check the length of something against a literal integer, Pyrefly will narrow away any tuple types that definitely do not match that length:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> typing </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> assert_type</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin" style="color:rgb(189, 147, 249)">type</span><span class="token plain"> XY </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">tuple</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)"># 2D point</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin" style="color:rgb(189, 147, 249)">type</span><span class="token plain"> RGB </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">tuple</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># color</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin" style="color:rgb(189, 147, 249)">type</span><span class="token plain"> Vec </span><span class="token operator">=</span><span class="token plain"> XY </span><span class="token operator">|</span><span class="token plain"> RGB</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">describe</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Vec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> y </span><span class="token operator">=</span><span class="token plain"> v </span><span class="token comment" style="color:rgb(98, 114, 164)"># Pyrefly knows v only has 2 elements, so it cannot be RGB</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       r</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> g</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> b </span><span class="token operator">=</span><span class="token plain"> v </span><span class="token comment" style="color:rgb(98, 114, 164)"># Pyrefly knows v has 3 elements, so it cannot be XY</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conditions-saved-in-variables">Conditions saved in variables<a href="https://pyrefly.org/blog/type-narrowing/#conditions-saved-in-variables" class="hash-link" aria-label="Direct link to Conditions saved in variables" title="Direct link to Conditions saved in variables">​</a></h2>
<p>If you want to check some condition multiple times, you may want to save it to a local variable to avoid repeating yourself. Pyrefly understands this pattern, while also being smart enough to figure out when it should invalidate a saved condition:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">f</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> y</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    x_is_int </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">isinstance</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> x_is_int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        y </span><span class="token operator">+=</span><span class="token plain"> x </span><span class="token comment" style="color:rgb(98, 114, 164)"># here Pyrefly knows x is an int and won't throw an error</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> x_is_int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        y </span><span class="token operator">+=</span><span class="token plain"> x </span><span class="token comment" style="color:rgb(98, 114, 164)"># x has not changed, so pyrefly still knows that x is an int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    x </span><span class="token operator">=</span><span class="token plain"> z</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> x_is_int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        y </span><span class="token operator">+=</span><span class="token plain"> x </span><span class="token comment" style="color:rgb(98, 114, 164)"># this is now unsafe, the x_is_int condition is invalidated so pyrefly will throw an error here</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion">Conclusion<a href="https://pyrefly.org/blog/type-narrowing/#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion">​</a></h2>
<p>These are just a few of the ways Pyrefly automatically narrows types, reducing the need for explicit casts in your programs. Not all of these features are unique to Pyrefly, but no other type checker as of writing supports the full set of narrowing patterns listed here. Given the lack of standardization of this feature, there’s a lot of room for innovation in the space. We’re currently working on expanding the narrowing patterns we support - so stay tuned for more updates!</p>
<p>Do you have a pattern for narrowing types that you wish type checkers could understand, or that you want us to support in Pyrefly? Please file an issue on our <a href="https://github.com/facebook/pyrefly" target="_blank" rel="noopener noreferrer">Github</a>!</p>]]></content:encoded>
            <category>typechecking</category>
        </item>
        <item>
            <title><![CDATA[Fly through data validation with Pyrefly’s new Pydantic integration]]></title>
            <link>https://pyrefly.org/blog/pyrefly-pydantic/</link>
            <guid>https://pyrefly.org/blog/pyrefly-pydantic/</guid>
            <pubDate>Wed, 03 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Pydantic is a data validation and data parsing library for Python. Pyrefly now supports Pydantic, allowing you to easily validate your data and get type errors before you run your code.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" src="https://pyrefly.org/assets/images/pydantic-blog-6b683fc8f527ef950de38ee168925b51.png" width="1279" height="715" class="img_ev3q"></p>
<p>We’re excited to share that experimental support for Pydantic is now available in Pyrefly! This integration aims to provide a seamless, out-of-the-box experience, allowing you to statically validate your Pydantic code as you type, rather than solely at runtime. No plugins or manual configuration required!</p>
<p>Supporting third-party packages like Pydantic in a language server or type checker is a non-trivial challenge. Unlike the Python standard library, third-party packages may introduce their own conventions, dynamic behaviors, and runtime logic that can be difficult to analyze statically. Many type checkers either require plugins (like Mypy’s Pydantic plugin) or offer only limited support for these types of projects. At the time of writing, Mypy is currently the only other major typechecker that provides robust support for Pydantic.</p>
<p>Read on to learn more about how this works and how to get started, or watch the <a href="https://www.youtube.com/watch?v=zXYpSQB57YI" target="_blank" rel="noopener noreferrer">video</a>!</p>
<h2></h2>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="background---pydantic-basics"><strong>Background - Pydantic Basics</strong><a href="https://pyrefly.org/blog/pyrefly-pydantic/#background---pydantic-basics" class="hash-link" aria-label="Direct link to background---pydantic-basics" title="Direct link to background---pydantic-basics">​</a></h2>
<p>Pydantic leverages type annotations for data validation and parsing. Similar to Python’s built-in <code>dataclasses</code>, it helps you create structured data containers in your code. But Pydantic takes it a step further by providing extensive runtime data validation, ensuring that data matches the expected types and formats as soon as it’s instantiated.</p>
<p>For example:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> pydantic </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> BaseModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Field</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">User</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">BaseModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token builtin" style="color:rgb(189, 147, 249)">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> Field</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> min_length</span><span class="token operator">=</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    email</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Example usage</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">user </span><span class="token operator">=</span><span class="token plain"> User</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token builtin" style="color:rgb(189, 147, 249)">id</span><span class="token operator">=</span><span class="token number">123</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> name</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"Alice"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> email</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"alice@example.com"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Using the code above, if you were to create a User instance, Pydantic would automatically validate the types and constraints. So if you pass invalid data (e.g., <code>name=""</code> or <code>id="not an int"</code>), Pydantic will raise a <code>ValidationError</code> at runtime.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="pyrefly--pydantic---how-it-works"><strong>Pyrefly &amp; Pydantic - How it works</strong><a href="https://pyrefly.org/blog/pyrefly-pydantic/#pyrefly--pydantic---how-it-works" class="hash-link" aria-label="Direct link to pyrefly--pydantic---how-it-works" title="Direct link to pyrefly--pydantic---how-it-works">​</a></h2>
<p>So how does Pyrefly work with Pydantic? For this initial experimental support we focused on delivering the following experiences:</p>
<ul>
<li><strong>Understands models and constructs</strong>: Pyrefly supports core Pydantic features, including <code>BaseModel</code>, <code>Field</code>, <code>ConfigDict</code>, as well as model-level configuration. No import errors or pesky red squiggles because your typechecker/language server doesn’t understand what Pydantic is.</li>
<li><strong>Automatic Recognition</strong>: Pyrefly detects Pydantic models and constructs out-of-the-box. There’s no need to install plugins, add configuration files, or tweak settings. You can just write your Pydantic code and Pyrefly will handle the rest.</li>
<li><strong>Static Analysis That Reflects Runtime Logic:</strong> One of the biggest challenges with Pydantic is that much of its validation and type coercion happens at runtime. Pyrefly’s static analysis engine is built to reflect Pydantic’s runtime logic as much as possible, ensuring that what you get in your IDE matches what will happen when your code runs.</li>
<li><strong>Immediate Feedback:</strong> As you write and edit your models, Pyrefly provides instant type errors and warnings. This helps you catch mistakes early, reducing debugging time and improving code quality.</li>
</ul>
<p>For the full list of supported Pydantic features check out the documentation <a href="https://pyrefly.org/en/docs/pydantic/#supported-features-with-examples" target="_blank" rel="noopener noreferrer">here</a>.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="automatic-recognition---write-pydantic-ally-while-pyrefly-gets-out-the-way">Automatic Recognition - Write Pydantic-ally while Pyrefly gets out the way<a href="https://pyrefly.org/blog/pyrefly-pydantic/#automatic-recognition---write-pydantic-ally-while-pyrefly-gets-out-the-way" class="hash-link" aria-label="Direct link to Automatic Recognition - Write Pydantic-ally while Pyrefly gets out the way" title="Direct link to Automatic Recognition - Write Pydantic-ally while Pyrefly gets out the way">​</a></h3>
<p>Let's take a closer look at how Pyrefly works differently to other type checkers, particularly in its unobtrusive approach. A key illustration of this difference is Pyrefly's interpretation of strictness.</p>
<p>Pydantic offers flexible validation modes to suit different use cases:</p>
<ul>
<li><strong>Lax (Default) Mode:</strong> In this mode, Pydantic will automatically coerce types where possible. For example, if you pass <code>”123”</code>(a string) to a field expecting an int, Pydantic will convert (i.e. coerce) it to <code>123</code> (an int). This is convenient for handling loosely-typed input, such as data from APIs or user forms.</li>
<li><strong>Strict Mode:</strong> here Pydantic enforces exact types, no coercion is performed. If you pass a string to an integer field, you’ll get a validation error. This is ideal for scenarios where data integrity is paramount.</li>
</ul>
<p>The handy thing about Pyrefly is that it automatically respects your choices on this matter. It reads your model’s configuration (such as <code>ConfigDict</code> or model-level config) to determine which mode to apply. For example, in the following code we are explicitly setting the <code>age</code> field using strict mode. Pyrefly inspects this statement directly and adapts its analysis to match your intent (in this case by reporting an error if a string age is passed).</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> pydantic </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> BaseModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Field</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">User</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">BaseModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    age</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> Field</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">strict</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># strict mode</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Pyrefly will report an error here.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">y </span><span class="token operator">=</span><span class="token plain"> User</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">name</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"Alice"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> age</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"30"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Another useful example of this can be seen with the use of extra parameters. Sometimes when writing Pydantic models you want to allow extra unspecified parameters when creating a model, and other times you want to explicitly forbid it (e.g. by using <code>extra=forbid</code>). Whether you’re using either approach or a mix of both in your code, Pyrefly can pick it up automatically from how you’ve written your Pydantic model:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Lax mode: We can pass extra fields to our model by default</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ModelAllow</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">BaseModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ModelAllow</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token operator">=</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> y</span><span class="token operator">=</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># pyrefly won't report an error here</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Strict mode: we can forbid extra fields</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ModelForbid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">BaseModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> extra</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"forbid"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ModelForbid</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token operator">=</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> y</span><span class="token operator">=</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># pyrefly will report an error here</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Ultimately the value here is that you don’t have to manage external configuration files or plugins to get your type checker to work with Pydantic. This is a different approach compared to say Mypy - which requires you to install a separate Pydantic plugin and configure it via your <code>mypy.ini</code> or <code>pyproject.toml</code> file.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="getting-started"><strong>Getting Started</strong><a href="https://pyrefly.org/blog/pyrefly-pydantic/#getting-started" class="hash-link" aria-label="Direct link to getting-started" title="Direct link to getting-started">​</a></h2>
<p>There are no special configurations or plugins required to start using Pyrefly with Pydantic! The steps are as simple as:</p>
<ol>
<li><a href="https://docs.pydantic.dev/latest/install/" target="_blank" rel="noopener noreferrer">Install Pydantic</a> (preferably v2).</li>
<li><a href="https://pyrefly.org/en/docs/installation/" target="_blank" rel="noopener noreferrer">Install Pyrefly</a> (v0.33.0 or later)</li>
<li>Write Pydantic models as usual.</li>
<li>Run Pyrefly / use it in your IDE</li>
</ol>
<p>Simple as that! If you’d like to have a go playing around with a few examples, we created <a href="https://github.com/migeed-z/pyrefly-pydantic-demo" target="_blank" rel="noopener noreferrer">a demo repo</a> that you can clone and try out.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion"><strong>Conclusion</strong><a href="https://pyrefly.org/blog/pyrefly-pydantic/#conclusion" class="hash-link" aria-label="Direct link to conclusion" title="Direct link to conclusion">​</a></h2>
<p>Pyrefly’s Pydantic support is experimental and evolving, so you may encounter edge cases or features that aren’t fully covered yet. We’d like to strongly encourage you to try out Pydantic support in your projects and <strong>let us know what works, what doesn’t, and what you’d like to see improved</strong>! Your insights and suggestions are invaluable in helping us refine this feature and prioritize enhancements. You can <a href="https://github.com/facebook/pyrefly/issues" target="_blank" rel="noopener noreferrer">open an issue in our GitHub repo</a> or <a href="https://discord.gg/Cf7mFQtW7W" target="_blank" rel="noopener noreferrer">connect with us on discord</a> if you have feedback to share.</p>
<p>Looking ahead, we’re also working on expanding Pyrefly’s capabilities to support additional third-party Python packages that are widely used in the ecosystem. We’re currently focused on adding support for Django and SQLAlchemy, but if there’s a particular library or framework you rely on, please let us know! Your suggestions and feedback will directly influence our roadmap and help make Pyrefly even better for the Python community.</p>]]></content:encoded>
            <category>pydantic</category>
            <category>typechecking</category>
            <category>IDE</category>
        </item>
        <item>
            <title><![CDATA[Pyrefly Beta is here!]]></title>
            <link>https://pyrefly.org/blog/pyrefly-beta/</link>
            <guid>https://pyrefly.org/blog/pyrefly-beta/</guid>
            <pubDate>Mon, 17 Nov 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Pyrefly v0.42.0 marks the first beta release of Pyrefly.]]></description>
            <content:encoded><![CDATA[
<p><img decoding="async" loading="lazy" src="https://pyrefly.org/assets/images/release-news-d289bbc4c41029b55f21ed9165321bf5.png" width="5334" height="3000" class="img_ev3q"></p>
<p>Today we’re thrilled to announce that we have reached Beta status for Pyrefly, our open-source, high-performance tool for Python code navigation and type checking! We first announced Pyrefly back in April 2025, and thanks to incredible community feedback and a ton of work from our team, we’ve hit a significant milestone.</p>
<p>But what does "Beta" mean for Pyrefly? This label can mean different things for different projects, for us it means we’ve made significant steps towards <a href="https://github.com/facebook/pyrefly/milestones" target="_blank" rel="noopener noreferrer">our goals</a> for stability and production readiness. We’ll cover more of the specifics in this blog, but at a high level what this means is that when using a version of Pyrefly with Beta status (v0.42.0 or later) you can feel confident that:</p>
<ul>
<li>The IDE extension is ready for production use right now,</li>
<li>The core type-checking features are robust, with some edge cases that will be addressed as we make progress towards a later stable v1 release.</li>
</ul>
<hr>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="-the-ide-experience-battle-tested-at-meta-scale">🚀 The IDE Experience: Battle-Tested at Meta Scale<a href="https://pyrefly.org/blog/pyrefly-beta/#-the-ide-experience-battle-tested-at-meta-scale" class="hash-link" aria-label="Direct link to 🚀 The IDE Experience: Battle-Tested at Meta Scale" title="Direct link to 🚀 The IDE Experience: Battle-Tested at Meta Scale">​</a></h2>
<p>A major priority for Pyrefly development since the beginning has been to deliver a lightning-fast and scalable IDE experience. Pyrefly was born out of a real-world production problem: Meta's Instagram developers were struggling with painfully slow code navigation, autocomplete, and type checking on their massive codebase.</p>
<p>Over the past year we’ve been rapidly adding new features and addressing bugs found by our passionate community of alpha testers (both internal and external). We're now proud to say <strong>Pyrefly is the default language server and type checker for all developers working on Instagram at Meta</strong>. By testing Pyrefly’s IDE extension on a codebase the size of Instagram we’ve been able to deliver hotly requested features with confidence that they will be reliable for large production codebases.</p>
<p>A few examples of Pyrefly’s latest language server features include:</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="automatic-import-refactoring"><strong>Automatic import refactoring</strong><a href="https://pyrefly.org/blog/pyrefly-beta/#automatic-import-refactoring" class="hash-link" aria-label="Direct link to automatic-import-refactoring" title="Direct link to automatic-import-refactoring">​</a></h4>
<p>Pyrefly will now automatically update your imports when you rename or move a file.</p>
<video src="/videos/IDE-file-rename.mp4" width="720" muted="" loop="" autoplay="" playsinline="" preload="auto"></video>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="jupyter-notebook-support"><strong>Jupyter Notebook support</strong><a href="https://pyrefly.org/blog/pyrefly-beta/#jupyter-notebook-support" class="hash-link" aria-label="Direct link to jupyter-notebook-support" title="Direct link to jupyter-notebook-support">​</a></h4>
<p>You can now use Pyrefly with Jupyter notebooks for diagnostics (red squiggles), inlay hints, go-to-definition, hover, semantic tokens, signature help, and completions.</p>
<video src="/videos/jupyter-notebook.mp4" width="720" muted="" loop="" autoplay="" playsinline="" preload="auto"></video>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="third-party-stubs-shipped-with-the-extension"><strong>Third-party stubs shipped with the extension</strong><a href="https://pyrefly.org/blog/pyrefly-beta/#third-party-stubs-shipped-with-the-extension" class="hash-link" aria-label="Direct link to third-party-stubs-shipped-with-the-extension" title="Direct link to third-party-stubs-shipped-with-the-extension">​</a></h4>
<p>Language server features like hover and autocomplete are now available for third party libraries with Typeshed stubs. This support is available by default, without the need for a config file (<code>pyproject.toml</code> / <code>pyrefly.toml</code>), to provide a better out-of-the-box experience.</p>
<video src="/videos/typeshed.mp4" width="720" muted="" loop="" autoplay="" playsinline="" preload="auto"></video>
<p>For a full list of supported IDE features check out the <a href="https://pyrefly.org/en/docs/IDE-features/" target="_blank" rel="noopener noreferrer">Pyrefly IDE documentation</a>.</p>
<hr>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="better-type-checking-check-">Better Type checking? Check ✅<a href="https://pyrefly.org/blog/pyrefly-beta/#better-type-checking-check-" class="hash-link" aria-label="Direct link to Better Type checking? Check ✅" title="Direct link to Better Type checking? Check ✅">​</a></h2>
<p>We've also made steady progress in improving Pyrefly's core type-checking engine since the Alpha release. Our focus has been on achieving higher conformance with the Python Typing Specification while also reducing false positives and improving support for modern Python patterns and popular libraries. In this section we'll highlight a few examples of the key fixes, new features, and design decisions that have paved the way for a more reliable type-checking experience in this Beta release.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="type-inference"><strong>Type Inference</strong><a href="https://pyrefly.org/blog/pyrefly-beta/#type-inference" class="hash-link" aria-label="Direct link to type-inference" title="Direct link to type-inference">​</a></h4>
<p>We understand writing type annotations can be tedious, so from the start Pyrefly has had some capabilities for automatically inferring types for returns and local variables and displaying them in the IDE. Since the first alpha release we’ve been steadily expanding inference capabilities, for example by improving our ability to infer the types of empty containers on first use:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> typing </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> reveal_type</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> f</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">T</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> T </span><span class="token operator">|</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">T</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> x </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">x </span><span class="token operator">=</span><span class="token plain"> f</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Pyrefly is now able to infer that `x` has type `list[int]`.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">reveal_type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="type-narrowing"><strong>Type Narrowing</strong><a href="https://pyrefly.org/blog/pyrefly-beta/#type-narrowing" class="hash-link" aria-label="Direct link to type-narrowing" title="Direct link to type-narrowing">​</a></h4>
<p>We’ve added several new ways to narrow types and reworked type narrowing logic to prevent unwanted "pollution" of types after checks like <code>isinstance</code>:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> typing </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> reveal_type</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">f</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Any</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">isinstance</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Reworked type narrowing means the revealed type is now correctly `Any`,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># not `int | Any` as it was previously.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    reveal_type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="preliminary-support-for-pydantic-and-django">Preliminary Support for Pydantic and Django<a href="https://pyrefly.org/blog/pyrefly-beta/#preliminary-support-for-pydantic-and-django" class="hash-link" aria-label="Direct link to Preliminary Support for Pydantic and Django" title="Direct link to Preliminary Support for Pydantic and Django">​</a></h3>
<p>We’ve added preliminary support for two of the most popular Python web/data libraries: Pydantic and Django. Pyrefly can now recognize key objects from these libraries and perform static analysis to catch potential errors before you ever run your code. This support works out-of-the-box with no configuration or plugins required, and includes IDE support.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="django-support"><strong>Django Support</strong><a href="https://pyrefly.org/blog/pyrefly-beta/#django-support" class="hash-link" aria-label="Direct link to django-support" title="Direct link to django-support">​</a></h4>
<p>As shown in this example, by default, Django automatically adds an <code>id</code> field to serve as the primary key (unless you define a custom primary key). Pyrefly is able to infer that the id exists and is of type <code>int</code></p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> django</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">db </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> models</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">Reporter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">models</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Model</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    full_name </span><span class="token operator">=</span><span class="token plain"> models</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">CharField</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">max_length</span><span class="token operator">=</span><span class="token number">70</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># Django auto-adds: id = models.AutoField(primary_key=True)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">reporter </span><span class="token operator">=</span><span class="token plain"> Reporter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">reveal_type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">reporter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Pyrefly infers: int</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>See more examples in the <a href="https://pyrefly.org/en/docs/django/" target="_blank" rel="noopener noreferrer">Pyrefly Django docs</a>.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="pydantic-support"><strong>Pydantic Support</strong><a href="https://pyrefly.org/blog/pyrefly-beta/#pydantic-support" class="hash-link" aria-label="Direct link to pydantic-support" title="Direct link to pydantic-support">​</a></h4>
<p>In this example Pyrefly can read your Pydantic model’s configuration directly from your code to determine there is a type error where <code>age</code> should be an <code>int</code> not a <code>string</code>:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> pydantic </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> BaseModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> Field</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">User</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">BaseModel</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    age</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> Field</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">strict</span><span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># strict mode</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Pyrefly will report an error here.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">y </span><span class="token operator">=</span><span class="token plain"> User</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">name</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"Alice"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> age</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"30"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>See more examples in the <a href="https://pyrefly.org/en/docs/pydantic/" target="_blank" rel="noopener noreferrer">Pyrefly Pydantic docs</a></p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="dataclass-transforms-support"><strong>Dataclass Transforms Support</strong><a href="https://pyrefly.org/blog/pyrefly-beta/#dataclass-transforms-support" class="hash-link" aria-label="Direct link to dataclass-transforms-support" title="Direct link to dataclass-transforms-support">​</a></h4>
<p>An important addition that laid the foundation for supporting libraries like <code>attrs</code> and Pydantic, was allowing Pyrefly to recognize custom dataclass-like class transformations:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> attrs </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> define  </span><span class="token comment" style="color:rgb(98, 114, 164)"># third-party `attrs` package</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@define</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">C</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># `attrs` uses a dataclass transform, and Pyrefly now recognizes the</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># auto-generated constructor based on the class attributes.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">C</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token operator">=</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<hr>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="-an-improved-user-experience">🙌 An improved user experience<a href="https://pyrefly.org/blog/pyrefly-beta/#-an-improved-user-experience" class="hash-link" aria-label="Direct link to 🙌 An improved user experience" title="Direct link to 🙌 An improved user experience">​</a></h2>
<p>We understand writing type annotations, dealing with strict checks, and configuring tools can be a barrier to adopting typed Python for many people, so we are always looking for ways to make using Pyrefly as painless as possible, dare we say even delightful? This section highlights some of the incremental improvements we've been working on to improve the overall user experience:</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="type-error-message-improvements"><strong>Type Error Message Improvements</strong><a href="https://pyrefly.org/blog/pyrefly-beta/#type-error-message-improvements" class="hash-link" aria-label="Direct link to type-error-message-improvements" title="Direct link to type-error-message-improvements">​</a></h4>
<p>We’ve also cleaned up our error messages, making them clearer and providing embedded code snippets to pinpoint the exact location of the issue:</p>
<p>Before:</p>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">foo.py:1:5-15: `+` is not supported between `Literal[1]` and `Literal['oops']`</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>After (with snippet and detailed explanation):</p>
<div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">ERROR `+` is not supported between `Literal[1]` and `Literal['oops']` [unsupported-operation]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> --&gt; foo.py:1:5</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  |</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">1 | x = 1 + "oops"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  |     ^^^^^^^^^^</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  |</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  Argument `Literal['oops']` is not assignable to parameter `value` with type `int` in function `int.__add__`</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="smoother-migration-from-mypy-or-pyright"><strong>Smoother migration from MyPy or Pyright</strong><a href="https://pyrefly.org/blog/pyrefly-beta/#smoother-migration-from-mypy-or-pyright" class="hash-link" aria-label="Direct link to smoother-migration-from-mypy-or-pyright" title="Direct link to smoother-migration-from-mypy-or-pyright">​</a></h4>
<p>Different typecheckers display different behaviour, which can make it a challenge to switch from one to another. While it's not always possible to cleanly translate one config option to another, the <code>pyrefly init</code> command now does a better job of searching for an existing MyPy or Pyright configuration and transforming it into a <code>pyrefly.toml</code> (or <code>[tool.pyrefly]</code> section). You can find more details in the <a href="https://pyrefly.org/en/docs/migrating-to-pyrefly/" target="_blank" rel="noopener noreferrer">migration guides section</a> of the Pyrefly docs.</p>
<h4 class="anchor anchorWithStickyNavbar_LWe7" id="a-more-configurable-ide-experience"><strong>A more configurable IDE experience</strong><a href="https://pyrefly.org/blog/pyrefly-beta/#a-more-configurable-ide-experience" class="hash-link" aria-label="Direct link to a-more-configurable-ide-experience" title="Direct link to a-more-configurable-ide-experience">​</a></h4>
<p>Do you want type error diagnostics (i.e. the red squiggles) in your IDE or not? Do you want to use Pyrefly’s type errors but not other LSP features? Do you want to pick and choose specific language server features to enable? We know that how you set up your code editor is a personal choice, so we’ve added <a href="https://pyrefly.org/en/docs/IDE/#customization" target="_blank" rel="noopener noreferrer">more configuration options</a> to enable you to “choose your own adventure” with your Pyrefly IDE experience.</p>
<hr>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="-enhancing-performance-squashing-bugs-and-aligning-with-the-spec">🔬 Enhancing Performance, Squashing Bugs and Aligning with the Spec<a href="https://pyrefly.org/blog/pyrefly-beta/#-enhancing-performance-squashing-bugs-and-aligning-with-the-spec" class="hash-link" aria-label="Direct link to 🔬 Enhancing Performance, Squashing Bugs and Aligning with the Spec" title="Direct link to 🔬 Enhancing Performance, Squashing Bugs and Aligning with the Spec">​</a></h2>
<p>We started developing Pyrefly with a clear goal in mind: make a tool that can statically analyze your code, catch type-related bugs, and do it <em>much</em> faster than existing tools like Mypy. While performance was good even in the early releases of Pyrefly, on the journey to Beta we’ve been focused on improvements for increasingly complex codebases, reducing type checking time and addressing edge cases for projects of varying size with many dependencies.</p>
<p>In some cases we’ve been able to reduce the time it takes to type check large projects with virtual envs, node-modules and other large dependencies by over 95% compared to earlier alpha versions of pyrefly. We've also closed <strong>over 350 bug issues</strong> opened by users and have been using the <a href="https://htmlpreview.github.io/?https://github.com/python/typing/blob/main/conformance/results/results.html" target="_blank" rel="noopener noreferrer">Python typing conformance test suite</a> to benchmark our progress towards fully implementing the typing specification. So far we’ve gone from <strong>39% conformant at alpha launch to over 70% conformant today.</strong></p>
<hr>
<p>There’s a lot more that’s been happening than we can possibly be squeezed into one blog post, so if you’d like to take a look back in more detail you can check out the completed <a href="https://github.com/facebook/pyrefly/milestone/4" target="_blank" rel="noopener noreferrer">beta milestone</a> or our previous <a href="https://github.com/facebook/pyrefly/releases" target="_blank" rel="noopener noreferrer">release notes</a>.</p>
<hr>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="whats-next">What’s Next?<a href="https://pyrefly.org/blog/pyrefly-beta/#whats-next" class="hash-link" aria-label="Direct link to What’s Next?" title="Direct link to What’s Next?">​</a></h2>
<p>This Beta release marks a significant milestone, but the journey isn't over! We’re now focusing on the further work that is needed to bring you a stable v1.0 release next year, including:</p>
<ul>
<li>Adding further support for popular third-party libraries (especially Django and Pydantic integration).</li>
<li>Implementing the remaining missing features from the Python type system to reach 100% conformance.</li>
<li>Making Pyrefly work even faster and consume even less memory</li>
<li>Fixing more bugs!</li>
</ul>
<p>We’d also like to say a huge thank-you to everyone who has helped get Pyrefly to this release. Whether you’ve contributed to the codebase, opened GitHub issues or just commented with your questions and feedback, your contributions have helped shape the direction of this project thus far. If you want to stay in the loop and continue being part of the conversation as we work towards Pyrefly v1.0 you can check out the <a href="https://github.com/facebook/pyrefly" target="_blank" rel="noopener noreferrer">GitHub repo</a> or join our <a href="https://discord.gg/Cf7mFQtW7W" target="_blank" rel="noopener noreferrer">Discord</a> community.</p>]]></content:encoded>
            <category>news</category>
            <category>typechecking</category>
            <category>IDE</category>
        </item>
        <item>
            <title><![CDATA[Bringing NumPy's type-completeness score to nearly 90%]]></title>
            <link>https://pyrefly.org/blog/numpy-type-completeness/</link>
            <guid>https://pyrefly.org/blog/numpy-type-completeness/</guid>
            <pubDate>Mon, 29 Sep 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[We tell the story of how we brought NumPy's type-completeness score from ~33% to nearly 90%]]></description>
            <content:encoded><![CDATA[<p>Because <a href="https://numpy.org/" target="_blank" rel="noopener noreferrer">NumPy</a> is one of the most downloaded packages in the Python ecosystem, any incremental improvement can have a large impact on the data science ecosystem. In particular, improvements related to static typing can improve developer experience and help downstream libraries write safer code. We'll tell the story about how we (Quansight Labs, with support from Meta's Pyrefly team) helped bring its type-completeness score to nearly 90% from an initial 33%.</p>
<p><strong>TL;DR</strong>:</p>
<ul>
<li>NumPy's type-completeness score was ~33%.</li>
<li>A one-line fix doubled coverage to over 80%.</li>
<li>Fully typing MaskedArray pushed the score to nearly 90%.</li>
<li>What's left? Top-level <code>numpy.ma</code> functions, more precise overloads, and adding a type-checker to NumPy's CI.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="wait-whats-type-completeness">Wait, what's type completeness?<a href="https://pyrefly.org/blog/numpy-type-completeness/#wait-whats-type-completeness" class="hash-link" aria-label="Direct link to Wait, what's type completeness?" title="Direct link to Wait, what's type completeness?">​</a></h2>
<p>Modern IDEs use type annotations to help developers by showing them helpful suggestions and highlighting syntax. <a href="https://github.com/microsoft/pyright" target="_blank" rel="noopener noreferrer">Pyright</a> is a popular type-checker which, as well as checking for correctness and consistency, can also measure what percentage of a library's public API has type annotations. We call the percentage of fully-typed symbols exported by a library the <em>type-completeness score</em>.</p>
<p>For example, a module which exports functions <code>foo</code> and <code>bar</code>:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">foo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">bar</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token number">1</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>would have a 50% type-completeness score, because:</p>
<ul>
<li><code>foo</code> is partially unknown, as it's missing a return annotation.</li>
<li><code>bar</code> is type-complete.</li>
</ul>
<p>By changing the <code>foo</code> signature to be <code>def foo(a: int) -&gt; None:</code>, the type-completeness score would jump to 100%. The more type-complete a library is, the more helpful the suggestions an IDE can show to the user.</p>
<p>Note that type-completeness only measures how much of the public API (at least, the part known to Pyright) is covered by types. If you want to verify that those types are correct and self-consistent, you'll also need to run a type checker. The most used type checkers currently are <a href="https://github.com/python/mypy" target="_blank" rel="noopener noreferrer">mypy</a> and Pyright, but <a href="https://github.com/facebook/pyrefly" target="_blank" rel="noopener noreferrer">Pyrefly</a> and <a href="https://github.com/astral-sh/ty" target="_blank" rel="noopener noreferrer">ty</a> are also attracting a lot of attention due to their impressive performance characteristics (note however that neither yet describes itself as production-ready, so temper your expectations accordingly if you try them out!).</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-type-complete-is-numpy">How type-complete is NumPy?<a href="https://pyrefly.org/blog/numpy-type-completeness/#how-type-complete-is-numpy" class="hash-link" aria-label="Direct link to How type-complete is NumPy?" title="Direct link to How type-complete is NumPy?">​</a></h2>
<p>When we started this effort (in March 2025), we first tried measuring NumPy's type-completeness by running:</p>
<div class="language-console codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-console codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">pyright --verifytypes numpy</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The output showed a completeness score of...18%. Wait, only 18%? This seemed very low, because by then typing efforts in NumPy had been ongoing for some time. Was something up with the metric? Upon closer inspection of the output, we noticed a few things:</p>
<ul>
<li>Some objects, such as <code>DTypeLike</code>, were reported to be "partially unknown", even though they had type annotations.</li>
<li>The type-completeness report included test modules such as <code>numpy.tests.test_matlib</code>.</li>
</ul>
<p>The first issue was caused by an import from the standard library <code>decimal</code> module, which itself is partially untyped. Given that this is outside of NumPy's direct control, we decided to exclude it from the coverage report by using <code>--ignoreexternal</code>, <a href="https://github.com/microsoft/pyright/discussions/9911" target="_blank" rel="noopener noreferrer">as suggested by Eric Traut</a>.</p>
<p>For the second issue, Pyright gives us an option to export the coverage report to json (<code>--outputjson</code>). We could then parse the json and exclude <code>numpy.tests</code>. Given that NumPy users wouldn't ordinarily interact with NumPy's internal test suite but that Pyright considers it public, we decided that it made sense for us to exclude tests in order to focus our efforts on what would make the biggest user-facing impact.</p>
<p>Once we'd addressed the two steps above, the baseline type-completeness score became 33%. This was our starting point. We could then focus our efforts on the remaining 67%!</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-one-line-change-which-doubled-type-completeness">The one-line change which doubled type-completeness<a href="https://pyrefly.org/blog/numpy-type-completeness/#the-one-line-change-which-doubled-type-completeness" class="hash-link" aria-label="Direct link to The one-line change which doubled type-completeness" title="Direct link to The one-line change which doubled type-completeness">​</a></h2>
<p>Pyright's report includes classes, methods, functions, type aliases, and more. A lot of scientific Python code centres around some central classes such as <code>numpy.ndarray</code>. <code>ndarray</code> was reported as "partially unknown", but eye-balling the exported symbols related to that class revealed something interesting:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token operator">&gt;&gt;</span><span class="token operator">&gt;</span><span class="token plain"> np</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">mean</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">'isTypeKnown'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> x </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> exported </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">'name'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'numpy.ndarray.'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">np</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">float64</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0.9811320754716981</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>So, <code>ndarray</code> was reported as "partially unknown", but 98% of its methods had known types. It shouldn't be much effort to bring that number to 100%! In fact, all it took was a <a href="https://github.com/numpy/numpy/pull/28908" target="_blank" rel="noopener noreferrer">one-line change to fix a typo in a type annotation</a>:</p>
<div class="language-diff codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-diff codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">- def setfield(self, /, val: ArrayLike, dtype: DTypeLike, offset: CanIndex = 0) -&gt; None: ...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">+ def setfield(self, /, val: ArrayLike, dtype: DTypeLike, offset: SupportsIndex = 0) -&gt; None: ...</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>That's it! <code>CanIndex</code> was an unknown symbol and was probably mistyped, and replacing it with the correct <code>SupportsIndex</code> one brought NumPy's overall type-completeness to over 80%! We then started examining other NumPy classes to see if there was anywhere else where we could make an impact, and hopefully a much larger one.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="enter-maskedarray">Enter MaskedArray<a href="https://pyrefly.org/blog/numpy-type-completeness/#enter-maskedarray" class="hash-link" aria-label="Direct link to Enter MaskedArray" title="Direct link to Enter MaskedArray">​</a></h2>
<p>When we looked at the percentage of typed symbols from the MaskedArray class, we noticed something interesting:</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token operator">&gt;&gt;</span><span class="token operator">&gt;</span><span class="token plain"> np</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">mean</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">'isTypeKnown'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> x </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> exported </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">'name'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">startswith</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'numpy.ma.core.MaskedArray.'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">np</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">float64</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0.2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Only 20% of them were typed! That's quite a contrast with <code>ndarray</code>, which was already at 98% when we started. It's also a fairly widely used class, appearing in the codebases of pandas, scikit-learn, and xarray. Given how poorly typed it was, we decided it would be a good candidate to spend time on!</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="typing-maskedarray">Typing MaskedArray<a href="https://pyrefly.org/blog/numpy-type-completeness/#typing-maskedarray" class="hash-link" aria-label="Direct link to Typing MaskedArray" title="Direct link to Typing MaskedArray">​</a></h2>
<p>The main difficulty in typing NumPy code isn't inferring the possible argument values (which is quite easy to do with automated tools), but rather dealing with the large number of overloads. This is because many of NumPy methods' return types depend on the exact combinations of the input types. For example, suppose we have a <code>MaskedArray</code> <code>ma</code> and want to count the number of non-null values:</p>
<ul>
<li><code>ma.count()</code> returns an integer.</li>
<li><code>ma.count(axis=0)</code> returns an array.</li>
<li><code>ma.count(keepdims=True)</code> also returns an array, of the same shape as the input array.</li>
</ul>
<p>We can type this by having a different overload of each of these different cases. This is a relatively simple example, but there are others where the number of necessary overloads was as high as 9! This isn't something which is easy to automate, and requires careful reading of the documentation and of the source code. It's a non-trivial amount of work.</p>
<p>But...we pulled through it, and thanks to some very timely and constructive reviews from the amazing <a href="https://github.com/jorenham" target="_blank" rel="noopener noreferrer">Joren Hammudoglu</a>, MaskedArray is now reported as 100% type-complete! As for the overall type-completeness, that's now at 88%. So...what's left?</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="whats-missing-from-numpy">What's missing from NumPy?<a href="https://pyrefly.org/blog/numpy-type-completeness/#whats-missing-from-numpy" class="hash-link" aria-label="Direct link to What's missing from NumPy?" title="Direct link to What's missing from NumPy?">​</a></h2>
<p>In the MaskedArray module, there's still some untyped top-level functions, such as <code>numpy.ma.count</code>. Overloads could be made more precise and shape-preserving (e.g. if the input is 2D, then make sure to preserve this fact in the output where possible). There's some missing defaults in the stubs. There's no shortage of work here.</p>
<p>The biggest missing bit, however, is the elephant in the room: NumPy doesn't run a type-checker over its codebase in CI. It has some typing tests, sure, but that's different from running a type-checker. If any motivated reader is interested in making a significant open source contribution, then getting NumPy's typing into a state such that a type checker (and possibly even <a href="https://mypy.readthedocs.io/en/stable/stubtest.html" target="_blank" rel="noopener noreferrer">stubtest</a>) can be run over it could be a great use of your time.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion-acknowledgements">Conclusion, acknowledgements<a href="https://pyrefly.org/blog/numpy-type-completeness/#conclusion-acknowledgements" class="hash-link" aria-label="Direct link to Conclusion, acknowledgements" title="Direct link to Conclusion, acknowledgements">​</a></h2>
<p>We've looked at how we contributed towards increasing NumPy's type-completeness. Given how widespread NumPy's adoption is, we expect this effort to have been an impactful one. We look forward to seeing what else we can achieve in this space - thank you to <a href="https://discuss.python.org/t/call-for-suggestions-nominate-python-packages-for-typing-improvements/80186/1" target="_blank" rel="noopener noreferrer">Meta and Quansight Labs</a> for having funded and facilitated this effort!</p>]]></content:encoded>
            <category>typechecking</category>
            <category>news</category>
        </item>
        <item>
            <title><![CDATA[Give your Python IDE a Glow-Up with Pyrefly]]></title>
            <link>https://pyrefly.org/blog/2025/09/15/ide-extension/</link>
            <guid>https://pyrefly.org/blog/2025/09/15/ide-extension/</guid>
            <pubDate>Mon, 15 Sep 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how Pyrefly's language server capabilities can help you navigate large Python codebases with ease.]]></description>
            <content:encoded><![CDATA[
<p><img decoding="async" loading="lazy" src="https://pyrefly.org/assets/images/ide-blog-hero-22aa9f0c1c4873f0e6c18e02faf40823.png" width="5058" height="2680" class="img_ev3q"></p>
<p>The challenges of managing ever-growing codebases are hardly new. As far back as 1995, Niklaus Wirth, (creator of programming language Pascal) already emphasized the importance of keeping software lean in his essay <a href="https://people.inf.ethz.ch/wirth/Articles/LeanSoftware.pdf" target="_blank" rel="noopener noreferrer">A Plea for Lean Software</a>. Fast forward to today: many programmers still face the reality that, despite their best coding intentions, as projects grow in size and complexity, so do their codebases. Even when a project scales to millions of lines of code, developers still expect their IDE to be fast, accurate, and efficient. And with increasingly large and interconnected (dare I say, spaghetti?) code, you probably rely on your IDE even more to help you navigate the chaos (ahem I mean complexity).</p>
<p>But what you may not have thought about before is that scaling project size presents a real challenge for your IDE: how do you keep code navigation tools responsive and reliable when the codebase keeps expanding? In this blog, we’ll explore this challenge and introduce you to Pyrefly - a scalable language server and typechecker designed to keep your Python development experience smooth and snappy at scale.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="background---what-is-a-language-server-and-why-does-it-matter"><strong>Background - What is a Language Server and Why Does It Matter?</strong><a href="https://pyrefly.org/blog/2025/09/15/ide-extension/#background---what-is-a-language-server-and-why-does-it-matter" class="hash-link" aria-label="Direct link to background---what-is-a-language-server-and-why-does-it-matter" title="Direct link to background---what-is-a-language-server-and-why-does-it-matter">​</a></h2>
<p>The Language Server Protocol (LSP) is a standardized way for code editors to communicate with language-specific servers that provide features like autocomplete, go-to-definition, and symbol renaming. Instead of each editor implementing these features separately, LSP allows a single language server to support multiple editors. Pyrefly’s language server capabilities are based on the <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/" target="_blank" rel="noopener noreferrer">Language Server Protocol Specification</a> and are designed to still be blazing fast even on code bases with over 20 million lines of code.</p>
<p>A key capability of many language servers, including Pyrefly, is typechecking, which enhances IDE features by providing type diagnostics. Pyrefly’s language server not only reports type errors like basic type checkers, such as <a href="https://github.com/python/mypy" target="_blank" rel="noopener noreferrer">Mypy</a>, but also replaces core IDE functionalities including “find definition,” hover (displaying types and docstrings), and completions. By consolidating these features, Pyrefly ensures that the types it checks and the types displayed in your IDE will always match.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="instagram---a-case-study-on-the-pain-of-slow-code-navigation"><strong>Instagram - A Case Study on the Pain of Slow Code Navigation</strong><a href="https://pyrefly.org/blog/2025/09/15/ide-extension/#instagram---a-case-study-on-the-pain-of-slow-code-navigation" class="hash-link" aria-label="Direct link to instagram---a-case-study-on-the-pain-of-slow-code-navigation" title="Direct link to instagram---a-case-study-on-the-pain-of-slow-code-navigation">​</a></h2>
<p>Meta operates an incredibly large Python codebase - a massive monorepo containing <strong>almost 1.5 million Python files</strong> maintained by thousands of developers. Instagram is one of those projects, with <strong>over</strong> <strong>20 million lines</strong> of Python code. At this scale, even simple navigation tasks like jumping to a function definition, searching for references, or loading syntax highlights could take almost a minute in the worst cases. That may not sound like a lot on its own, but experiencing it every few minutes quickly becomes frustrating and has a tangible impact on developer productivity, especially when multiplied across a large company like Meta.</p>
<p>Pyrefly emerged in part to address this exact challenge (you can read more about our origin story in our <a href="https://pyrefly.org/blog/introducing-pyrefly/" target="_blank" rel="noopener noreferrer">intro blog</a>). In real world use cases, developers who switched from Pyright (the default LSP for VSCode) to Pyrefly spent <strong>98%</strong> less time waiting on hover results and go-to definition was <strong>~10x</strong> faster. On the slowest files (p99), these IDE responses grew from an order of minutes to seconds (<strong>30x</strong> improvement). If those numbers are hard to visualise, the TL;DR is that this upgrade took instagram developers from questioning “is my editor frozen?” to not giving their IDE a second thought.</p>
<div style="display:flex;flex-direction:row;max-width:100%"><video src="/videos/pyrefly-ide-comparison.mov" style="max-width:50%" muted="" loop="" autoplay="" playsinline="" preload="metadata"></video><video src="/videos/pyright-ide-comparison.mov" style="max-width:50%" muted="" loop="" autoplay="" playsinline="" preload="metadata"></video></div>
<p style="text-align:center"><em>Pyrefly (left) vs Pyright (right) autocomplete speed comparison</em></p>
<p>These early results are certainly promising indications of a smoother developer experience for Meta engineers, and we're excited to share more insights as developers continue using Pyrefly. But Pyrefly isn’t just for Meta developers, it’s open source and ready for everyone to explore! If you’re curious about Pyrefly’s language server features and want to see how to get it up and running in your IDE, keep reading!</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="pyrefly-lsp-at-a-glance"><strong>Pyrefly LSP at a Glance</strong><a href="https://pyrefly.org/blog/2025/09/15/ide-extension/#pyrefly-lsp-at-a-glance" class="hash-link" aria-label="Direct link to pyrefly-lsp-at-a-glance" title="Direct link to pyrefly-lsp-at-a-glance">​</a></h2>
<p>While Pyrefly is still in Alpha as of the time of posting, it already supports most of the essential IDE capabilities that Python developers rely on daily, such as:</p>
<ul>
<li><strong>Autocomplete:</strong> predicts what you’re likely to type next, reducing the need to remember exact names of variables, functions, and others, even importing them automatically for you</li>
<li><strong>Go to Definition</strong>: allows you to jump directly to the source of a function, class, or variable with a single click.</li>
<li><strong>Hover:</strong> when you hover over a symbol, Pyrefly displays useful information such as type annotations, documentation, and inferred types.</li>
<li><strong>Rename symbols</strong>: right click to rename variables, functions, or classes across the entire codebase.</li>
<li><strong>Typechecking</strong>: Pyrefly will also show type errors and infer types, which you can toggle on or off in your Pyrefly settings (more info in the next section)</li>
<li><strong>And many more</strong>! explore the full list and details in the <a href="https://pyrefly.org/en/docs/IDE/" target="_blank" rel="noopener noreferrer">Pyrefly IDE docs</a>.</li>
</ul>
<p>The Pyrefly team and our open-source community are continuously working on improvements and new features so stay tuned for updates! We also want to hear from you - what features do you need to make your IDE experience better? <a href="https://github.com/facebook/pyrefly/issues" target="_blank" rel="noopener noreferrer">Open a GitHub issue</a> or <a href="https://discord.gg/Cf7mFQtW7W" target="_blank" rel="noopener noreferrer">join our discord</a> to share your thoughts.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-to-add-pyrefly-to-your-ide"><strong>How to add Pyrefly to your IDE</strong><a href="https://pyrefly.org/blog/2025/09/15/ide-extension/#how-to-add-pyrefly-to-your-ide" class="hash-link" aria-label="Direct link to how-to-add-pyrefly-to-your-ide" title="Direct link to how-to-add-pyrefly-to-your-ide">​</a></h2>
<p>Pyrefly can be used in a range of IDEs, including standard GUI editors like <strong>VSCode</strong> or <strong>Pycharm</strong>, terminal editors like <strong>Neovim</strong> or <strong>Emacs</strong>, and AI editors like <strong>Cursor</strong> or <strong>Windsurf</strong>. You can check out the full list of supported IDEs in the <a href="https://pyrefly.org/en/docs/IDE/" target="_blank" rel="noopener noreferrer">Pyrefly IDE docs</a>.</p>
<p>For GUI editors, setup is pretty straightforward and generally follows the same steps:</p>
<ol>
<li>Search for “pyrefly” in the appropriate extension marketplace for your IDE and install it</li>
<li>Open any Python file and the extension will activate automatically</li>
<li><strong>Note</strong>: if using an editor other than VSCode you may need to uninstall the default Python LSP in your editor. You can do this by opening your extension settings and setting <code>"Language Server: None"</code></li>
<li><strong>Optional</strong>: we recommend all developers use a typechecker as part of their regular software development process, however we know that type errors in your IDE can be noisy, so the type errors feature is not turned on by default if you don't have a <code>pyrefly.toml</code> (from <code>pyrefly init</code>). If you DO want type errors to show up in your editor everywhere (as red squiggles), you should update your extension settings to include <code>"python.pyrefly.displayTypeErrors": "force-on"</code></li>
</ol>
<p><img decoding="async" loading="lazy" src="https://pyrefly.org/assets/images/ide-settings-1ad2d1d2b8aa91be0826783fcdac8ecb.png" width="1975" height="403" class="img_ev3q"></p>
<p>For terminal editors the setup process can vary depending on which editor you use so check out the <a href="https://pyrefly.org/en/docs/IDE/#other-editors" target="_blank" rel="noopener noreferrer">installation documentation</a> for specific instructions.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion"><strong>Conclusion</strong><a href="https://pyrefly.org/blog/2025/09/15/ide-extension/#conclusion" class="hash-link" aria-label="Direct link to conclusion" title="Direct link to conclusion">​</a></h2>
<p>As Python projects continue to grow in size and complexity, having a fast, reliable, and scalable language server is essential for maintaining developer productivity (and our sanity to be honest). So if you’re working on a large codebase and want an LSP designed with scalability in mind we invite you to give Pyrefly a try!</p>
<p>While the project is still in Alpha we’re especially eager to hear from more users like you about how the IDE extension performs on real world codebases. If you have any feedback, bug reports or feature requests please feel free to <a href="https://github.com/facebook/pyrefly/issues" target="_blank" rel="noopener noreferrer">open a GitHub issue</a>, and if you have any questions or need support please come chat with us on <a href="https://discord.com/invite/Cf7mFQtW7W" target="_blank" rel="noopener noreferrer">Discord</a>!</p>
<p>Happy coding, fellow pyreflies! 🔥🪰</p>]]></content:encoded>
            <category>language-server</category>
            <category>IDE</category>
        </item>
        <item>
            <title><![CDATA[Why Today’s Python Developers Are Embracing Type Hints]]></title>
            <link>https://pyrefly.org/blog/why-typed-python/</link>
            <guid>https://pyrefly.org/blog/why-typed-python/</guid>
            <pubDate>Tue, 19 Aug 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[What is Typed Python? Why is it important for Python developers today? How to can you get started?]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" src="https://pyrefly.org/assets/images/why-typing-blog-6798a36e31db2a87069bd3636c0c97c2.png" width="1849" height="1201" class="img_ev3q"></p>
<p>Python is one of the most successful programming languages out there, with it recently overtaking Javascript as the most popular language on GitHub, according to the latest <a href="https://github.blog/news-insights/octoverse/octoverse-2024/" target="_blank" rel="noopener noreferrer">GitHub Octoverse report</a>. The report emphasises the popularity of the language in the growing fields of AI, data science and scientific computing - fields where speedy experimentation and iteration are critical, and where developers are coming from a broad range of STEM backgrounds, not necessarily computer science. But as the Python community expands and projects grow from experiments to production systems, that same flexibility can become a liability.</p>
<p>That’s why today we’re going to talk about typed Python - what it is, why it’s become important for Python developers today, and how to get started using it to write higher quality, more reliable code.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="what-is-typed-python">What is Typed Python?<a href="https://pyrefly.org/blog/why-typed-python/#what-is-typed-python" class="hash-link" aria-label="Direct link to What is Typed Python?" title="Direct link to What is Typed Python?">​</a></h2>
<p>Before we dive into why you should be using typed Python in your daily development lives, first we need to understand some core concepts and how we got here.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="dynamic-vs-static-typing">Dynamic vs static typing<a href="https://pyrefly.org/blog/why-typed-python/#dynamic-vs-static-typing" class="hash-link" aria-label="Direct link to Dynamic vs static typing" title="Direct link to Dynamic vs static typing">​</a></h3>
<p>The classic Python programming language that you know and love is <em>dynamically typed</em>. What does that mean exactly? It means that types are determined at runtime, not when you write your code. Variables can hold any type of value, and you don't need to declare what type they are.</p>
<p>Here’s an example of dynamic typing in action:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">x </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain">        </span><span class="token comment" style="color:rgb(98, 114, 164)"># x is an integer</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">x </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"hello"</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># now x is a string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">x </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># now x is a list</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>This behaviour is one of the things that sets Python apart from languages that are <em>statically typed</em>, like Java or C++, which require you to declare types from the get go:</p>
<div class="language-c codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-c codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">int</span><span class="token plain"> x </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">std</span><span class="token operator">::</span><span class="token plain">string x_str </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"hello"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">std</span><span class="token operator">::</span><span class="token plain">vector</span><span class="token operator">&lt;</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">int</span><span class="token operator">&gt;</span><span class="token plain"> x_vec </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>In the above example we can’t just reassign the variable <code>x</code> to a value of whatever type we want, it can only hold an integer because of the static typing nature of the C++ language.</p>
<p>The fact that Python is a dynamically typed language is one of the reasons it is so easy to use and popular amongst new and experienced programmers alike. It makes it easy to develop quick demos, experimental research and proof of concepts, without needing to spend precious development time declaring types. This flexibility has been instrumental in Python's adoption in AI, data science, and scientific computing, where researchers need to rapidly iterate and experiment with different approaches.</p>
<p>However… (surely you knew there was a “but” coming?)</p>
<p>We are quickly moving past the “proof-of-concept” phase for many of these industries. AI and machine learning efforts are actively being integrated into production applications, and with that comes production-level expectations of reliability and stability. Relying on dynamic typing opens these codebases up to a certain level of risk that may not be acceptable at the scale they are now expected to operate.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="enter-pep-484-static-typing-comes-to-python">Enter PEP 484: Static Typing Comes to Python<a href="https://pyrefly.org/blog/why-typed-python/#enter-pep-484-static-typing-comes-to-python" class="hash-link" aria-label="Direct link to Enter PEP 484: Static Typing Comes to Python" title="Direct link to Enter PEP 484: Static Typing Comes to Python">​</a></h3>
<p>Cast your mind back to September 2014: Germany has just won the world cup, skinny jeans are still in fashion and Taylor Swift’s “Shake it Off” is number 1 on the charts. That same month <a href="https://peps.python.org/pep-0484/" target="_blank" rel="noopener noreferrer">PEP 484</a> was first created, proposing the addition of type hints to Python, and fundamentally changing how future developers would be able to write and maintain Python code.</p>
<p>With PEP 484’s acceptance and introduction in Python 3.5, developers could now use static type annotations to declare the expected data types of function arguments and return values, and <a href="https://pyrefly.org/en/docs/python-features-and-peps/" target="_blank" rel="noopener noreferrer">subsequent PEPs have continually added more features</a> to expand and refine Python's type system. Today you can write statically typed Python statements like this:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">my_func</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> y</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">bool</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    z</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">x</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> z </span><span class="token operator">==</span><span class="token plain"> y</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The key innovation of PEP 484 was introducing a <em>gradual</em> type system that allows developers to slowly add type annotations over time without breaking existing code. The system works by:</p>
<ul>
<li>Only type-checking functions that have explicit return or parameter annotations</li>
<li>Introducing the <code>Any</code> type as an escape hatch that has all possible attributes</li>
<li>Assuming untyped functions implicitly return <code>Any</code></li>
</ul>
<p>This approach has meant developers can incrementally adopt typing, while still allowing them to take advantage of the default dynamic typing approach that makes Python so easy to work with and ideal for quick prototyping.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="benefits-of-python-type-hints-write-better-code-faster">Benefits of Python Type Hints: Write Better Code, Faster<a href="https://pyrefly.org/blog/why-typed-python/#benefits-of-python-type-hints-write-better-code-faster" class="hash-link" aria-label="Direct link to Benefits of Python Type Hints: Write Better Code, Faster" title="Direct link to Benefits of Python Type Hints: Write Better Code, Faster">​</a></h2>
<p>So why specifically should you start using type hints in your Python code? Python type hints offer a range of advantages that can significantly improve the quality, maintainability, and scalability of your codebase, at the same time making it easier for other developers to understand your code and collaborate with you.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="types-help-you-catch-bugs-early">Types help you catch bugs early<a href="https://pyrefly.org/blog/why-typed-python/#types-help-you-catch-bugs-early" class="hash-link" aria-label="Direct link to Types help you catch bugs early" title="Direct link to Types help you catch bugs early">​</a></h3>
<p>Type hints assist static analysis tools in identifying mismatches and potential errors before the code is executed, allowing for early bug detection. Take the following example:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">add_numbers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> b</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">       </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> a </span><span class="token operator">+</span><span class="token plain"> b</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">add_numbers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"4"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Potential error</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>The above error might be easy to spot when you’re calling the function so close to where you’re defining it, but imagine you’re working across multiple files and/or with many lines of code separating them - suddenly it’s not so easy!</p>
<p>In comparison, if you’re using type hints in conjunction with a typechecking tool (such as Pyrefly or MyPy), you can catch this error much earlier - when you’re actually writing the code, rather than when it fails at runtime:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">add_numbers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> b</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> a </span><span class="token operator">+</span><span class="token plain"> b</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">add_numbers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"4"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># a typechecker will catch this error at time of writing</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>Using a typechecker to highlight these types of errors also ensures you can catch an error like this even if you’ve missed this code path in your unit tests.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="typed-code-is-self-documenting">Typed code is self-documenting<a href="https://pyrefly.org/blog/why-typed-python/#typed-code-is-self-documenting" class="hash-link" aria-label="Direct link to Typed code is self-documenting" title="Direct link to Typed code is self-documenting">​</a></h3>
<p>Another benefit of writing typed Python is that using function signatures and variable annotations provide clarity of intent for a given piece of code. In other words, it makes code easier to read and review. It makes refactoring safer and more predictable. It even helps new team members get up to speed quickly on what’s going on in your codebase without wasting their own time, or yours!</p>
<p>Take the following example, without type hints you have to carefully read the internal function code to understand what type of parameters will work and what will be returned:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">calculate_stats</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> weights</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    total </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    weighted_sum </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> value </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">enumerate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> i </span><span class="token operator">&lt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">weights</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            weighted_sum </span><span class="token operator">+=</span><span class="token plain"> value </span><span class="token operator">*</span><span class="token plain"> weights</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            total </span><span class="token operator">+=</span><span class="token plain"> weights</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    avg </span><span class="token operator">=</span><span class="token plain"> weighted_sum </span><span class="token operator">/</span><span class="token plain"> total </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> total </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> avg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>With this version, you can tell instantly what type of arguments you should be passing and what you should expect to get back - saving precious dev time and just generally making your life easier:</p>
<div class="language-py codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-py codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">calculate_stats</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> weights</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">tuple</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">float</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">int</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    total </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    weighted_sum </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> value </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">enumerate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> i </span><span class="token operator">&lt;</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">weights</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            weighted_sum </span><span class="token operator">+=</span><span class="token plain"> value </span><span class="token operator">*</span><span class="token plain"> weights</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            total </span><span class="token operator">+=</span><span class="token plain"> weights</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    avg </span><span class="token operator">=</span><span class="token plain"> weighted_sum </span><span class="token operator">/</span><span class="token plain"> total </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> total </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">else</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> avg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">len</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>
<p>I know I know - ideally all developers should be adding clear docstrings with every function they write, but we know in reality it doesn’t always shape up that way! Adding type hints is quicker than writing a typical docstring, won’t go stale (if enforced using a typechecker) and is better than no documentation at all. Modern Python typecheckers also have <a href="https://pyrefly.org/en/docs/IDE/" target="_blank" rel="noopener noreferrer">IDE extensions</a> that include autocomplete functionality to make life easier.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="typed-python-helps-you-scale-from-proof-of-concept-to-production-ready">Typed Python helps you scale from proof-of-concept to production-ready<a href="https://pyrefly.org/blog/why-typed-python/#typed-python-helps-you-scale-from-proof-of-concept-to-production-ready" class="hash-link" aria-label="Direct link to Typed Python helps you scale from proof-of-concept to production-ready" title="Direct link to Typed Python helps you scale from proof-of-concept to production-ready">​</a></h3>
<p>One of the most important benefits of using type annotations in your code is that it helps you scale your code faster and with less risk. For developers today, the pipeline from experimental code to production systems moves faster than ever, especially in AI and machine learning workflows where research prototypes must quickly evolve into robust, scalable applications.</p>
<p>For example, say there is a team of data scientists that has just published their findings and now needs to operationalize their models. If their published code already includes type hints it makes it much easier, quicker and safer for an engineering team to step in and integrate that research into production applications. In situations like these, type annotations act as a contract between different stages of development, making it clear how data flows through complex, multi-step processing pipelines. This is particularly valuable in AI workflows where a single type mismatch, like passing a NumPy array where a PyTorch tensor is expected, can cause silent failures or performance degradation that only surfaces under production load.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="get-started-with-typed-python-today">Get Started with Typed Python today!<a href="https://pyrefly.org/blog/why-typed-python/#get-started-with-typed-python-today" class="hash-link" aria-label="Direct link to Get Started with Typed Python today!" title="Direct link to Get Started with Typed Python today!">​</a></h2>
<p>So now you know what typed python is and why you should be doing it, how can you actually get started adding types to your code?</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="step-0---start-early">Step 0 - start early!<a href="https://pyrefly.org/blog/why-typed-python/#step-0---start-early" class="hash-link" aria-label="Direct link to Step 0 - start early!" title="Direct link to Step 0 - start early!">​</a></h3>
<p>As a general rule of thumb, the earlier in a project you start adding type annotations the better.
Type hints are much easier to add as you go than to retrofit across an entire codebase later.</p>
<p>As we’ve mentioned before, one of the great benefits of Python is that its dynamic typing default makes it very flexible and easy to get started with. So when you’re doing your initial experimentation and prototyping maybe you’re not thinking about making sure it’s type safe - and that’s ok! But as soon as you start to think your project might be going somewhere, if more than one person might be working on it, using it or just reading it, you should start adding type hints.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="step-1---install-a-type-checker">Step 1 - install a type checker<a href="https://pyrefly.org/blog/why-typed-python/#step-1---install-a-type-checker" class="hash-link" aria-label="Direct link to Step 1 - install a type checker" title="Direct link to Step 1 - install a type checker">​</a></h3>
<p>Choose and install a type checker that fits your needs. Typecheckers leverage the code annotations you write to provide important errors and warnings to ensure your codebase is type safe.</p>
<p>At Meta, we recommend <a href="https://pyrefly.org/" target="_blank" rel="noopener noreferrer">Pyrefly</a>, our new open-source type checker built in Rust. Pyrefly is designed to scale from small projects to massive codebases incredibly fast, while providing excellent developer experience. Read the <a href="https://pyrefly.org/en/docs/" target="_blank" rel="noopener noreferrer">Pyrefly documentation</a> to understand configuration options and best practices, then start adding simple type hints to new functions before gradually working your way up to more complex scenarios.</p>
<p>You should also consider working with a typechecker that <a href="https://pyrefly.org/en/docs/IDE/" target="_blank" rel="noopener noreferrer">supports IDE integration</a> to get real-time feedback as you write code. Pyrefly provides extensions for editors like VS Code, PyCharm, and Vim which will highlight errors and provide autocomplete suggestions based on your type annotations.</p>
<p>Adding your typechecker to your CI processes is also valuable for maintaining code quality at scale. You can <a href="https://pyrefly.org/en/docs/installation/#add-pyrefly-to-ci" target="_blank" rel="noopener noreferrer">configure your CI/CD pipeline to run type checking</a> on every pull request, treating type errors as build failures.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="step-2---make-use-of-resources-to-get-better-at-typing">Step 2 - make use of resources to get better at typing<a href="https://pyrefly.org/blog/why-typed-python/#step-2---make-use-of-resources-to-get-better-at-typing" class="hash-link" aria-label="Direct link to Step 2 - make use of resources to get better at typing" title="Direct link to Step 2 - make use of resources to get better at typing">​</a></h3>
<p>Typing is one of those skills that gets better the more you practice it in your code, but there are also great resources out there for getting to grips with the functionality and diving deeper into the concepts:</p>
<ul>
<li>Official <a href="https://docs.python.org/3/library/typing.html" target="_blank" rel="noopener noreferrer">Python typing</a> documentation - The typing module docs provide comprehensive coverage of all available types</li>
<li><a href="https://pyrefly.org/en/docs/python-features-and-peps/" target="_blank" rel="noopener noreferrer">PEP 484 and related PEPs</a> - Understanding the foundational specifications helps you grasp the "why" behind typing decisions</li>
<li>Documentation for your chosen typechecker, e.g. <a href="https://pyrefly.org/en/docs/typing-for-python-developers/" target="_blank" rel="noopener noreferrer">Pyrefly Docs on learning typing</a></li>
<li>Join community forums and get support, e.g. <a href="https://discord.gg/Cf7mFQtW7W" target="_blank" rel="noopener noreferrer">Pyrefly Discord</a>, <a href="https://discuss.python.org/c/typing" target="_blank" rel="noopener noreferrer">Typing Discourse</a></li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="conclusion">Conclusion<a href="https://pyrefly.org/blog/why-typed-python/#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion">​</a></h2>
<p>So there you have it - a quick trip around the world of Python typing! By now you’ve hopefully learnt that type hints aren't just another Python feature to add to the long list of things you’ll <em>definitely</em> get round to implementing eventually - they're a practical investment in your code's future. The upfront effort of adding type hints pays dividends in reduced debugging sessions, smoother code reviews, and fewer production issues. Most importantly, they give you the confidence to refactor and scale your codebase without fear of breaking things in unexpected ways. Start small by adding type annotations to your next function, add a type checker to your workflow, and before you know it writing typed Python will be second nature. Your future self (and your users and teammates!) will thank you.</p>]]></content:encoded>
            <category>typechecking</category>
            <category>python-typing-basics</category>
        </item>
        <item>
            <title><![CDATA[Introducing Pyrefly - A new type checker and IDE experience for Python]]></title>
            <link>https://pyrefly.org/blog/introducing-pyrefly/</link>
            <guid>https://pyrefly.org/blog/introducing-pyrefly/</guid>
            <pubDate>Thu, 15 May 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Today we are announcing an alpha version of Pyrefly, an open source Python typechecker and IDE extension crafted in Rust]]></description>
            <content:encoded><![CDATA[
<p><img decoding="async" loading="lazy" alt="Pyrefly Intro" src="https://pyrefly.org/assets/images/pyrefly_intro-29fb0e2d54b79ced50ee1065dc4a2a4d.webp" width="2242" height="1260" class="img_ev3q"></p>
<p>Today we are announcing an alpha version of Pyrefly, an open source Python typechecker and IDE extension crafted in Rust. Pyrefly is a static type checker that analyzes Python code to ensure type consistency and help you catch errors throughout your codebase before your code runs. It also supports IDE integration and CLI usage to give you flexibility in how you incorporate it into your workflow.</p>
<p>The open source community is the backbone of the Python language. We are eager to collaborate on Pyrefly with the community and improve Python’s type system and the many libraries that we all rely on.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="get-started">Get started<a href="https://pyrefly.org/blog/introducing-pyrefly/#get-started" class="hash-link" aria-label="Direct link to Get started" title="Direct link to Get started">​</a></h2>
<p>Ready to dive in? The <a href="https://pyrefly.org/" target="_blank" rel="noopener noreferrer">official Pyrefly website</a> has all the details, but to quickly get started:</p>
<ul>
<li><a href="https://pyrefly.org/en/docs/installation/" target="_blank" rel="noopener noreferrer">Install</a> Pyrefly on the command-line: pip install pyrefly.</li>
<li><a href="https://pyrefly.org/en/docs/migrating-to-pyrefly/" target="_blank" rel="noopener noreferrer">Migrate your existing type checker configuration to Pyrefly</a>.</li>
<li>Enhance Your IDE: Download the <a href="https://marketplace.visualstudio.com/items?itemName=meta.pyrefly" target="_blank" rel="noopener noreferrer">Pyrefly extension for VSCode</a> and enjoy a lightning fast IDE experience from starter projects to monorepos.</li>
<li>Leave feedback for us on <a href="https://github.com/facebook/pyrefly/issues" target="_blank" rel="noopener noreferrer">GitHub</a>.</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-we-built-pyrefly">Why we built Pyrefly<a href="https://pyrefly.org/blog/introducing-pyrefly/#why-we-built-pyrefly" class="hash-link" aria-label="Direct link to Why we built Pyrefly" title="Direct link to Why we built Pyrefly">​</a></h2>
<p>Back in 2017, we embarked on a mission to create a type checker that could handle <a href="https://instagram-engineering.com/web-service-efficiency-at-instagram-with-python-4976d078e366" target="_blank" rel="noopener noreferrer">Instagram’s massive codebas</a> of typed Python. This mission led to the birth of the <a href="https://github.com/facebook/pyre-check" target="_blank" rel="noopener noreferrer">Pyre</a> type checker, inspired by the robust designs of <a href="https://hacklang.org/" target="_blank" rel="noopener noreferrer">Hack</a> and <a href="https://flow.org/" target="_blank" rel="noopener noreferrer">Flow</a>, and written in OCaml to deliver scalable performance.</p>
<p>Over the years, Pyre served us well, but as the type system evolved and the need for typechecking to drive responsive IDE emerged, it was clear that we needed to take a new approach. We explored alternate solutions and leveraged community tools like <a href="https://github.com/Microsoft/pyright" target="_blank" rel="noopener noreferrer">Pyright</a> for code navigation. But the need for an extensible type checker that can bring code navigation, checking at scale, and exporting types to other services drove us to start over, creating Pyrefly.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-principles-behind-pyrefly">The principles behind Pyrefly<a href="https://pyrefly.org/blog/introducing-pyrefly/#the-principles-behind-pyrefly" class="hash-link" aria-label="Direct link to The principles behind Pyrefly" title="Direct link to The principles behind Pyrefly">​</a></h2>
<p>Today, we’re excited to unveil Pyrefly, a project <a href="https://github.com/facebook/pyrefly" target="_blank" rel="noopener noreferrer">we’ve been developing openly on GitHub</a>. We invite you to explore our work and try it out on your own project. While a project like Pyrefly is the sum of thousands of technical choices, a few notable principles we’ve followed are:</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="performance">Performance<a href="https://pyrefly.org/blog/introducing-pyrefly/#performance" class="hash-link" aria-label="Direct link to Performance" title="Direct link to Performance">​</a></h3>
<p>We want to shift checks that used to happen later on CI to happening on every single keystroke. That requires checking code at speed (on large codebases we can check 1.8 million lines of code per second!) and careful thought to incrementality and updates. Pyrefly is implemented in Rust and designed for high performance on codebases of all sizes.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="ide-first">IDE first<a href="https://pyrefly.org/blog/introducing-pyrefly/#ide-first" class="hash-link" aria-label="Direct link to IDE first" title="Direct link to IDE first">​</a></h3>
<p>We want the IDE and command line to share a consistent view of the world, which means crafting abstractions that capture the differences without incurring unnecessary costs. Designing these abstractions from the beginning is much easier than retrofitting them, which we tried with Pyre.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="inference">Inference<a href="https://pyrefly.org/blog/introducing-pyrefly/#inference" class="hash-link" aria-label="Direct link to Inference" title="Direct link to Inference">​</a></h3>
<p>Some <a href="https://engineering.fb.com/2024/12/09/developer-tools/typed-python-2024-survey-meta/" target="_blank" rel="noopener noreferrer">Python programs are typed</a>, but many aren’t. We want users to benefit from types even if they haven’t annotated their code – so automatically infer types for returns and local variables and display them in the IDE. What’s more, in the IDE you can even double click to insert these inferred types if you think that would make the program better.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="open-source">Open source<a href="https://pyrefly.org/blog/introducing-pyrefly/#open-source" class="hash-link" aria-label="Direct link to Open source" title="Direct link to Open source">​</a></h3>
<p>Python is open source, and hugely popular. The <a href="https://typing.python.org/en/latest/spec/" target="_blank" rel="noopener noreferrer">Python typing specification</a> is open source, which made Pyrefly vastly easier to develop. Many of the libraries Meta contributes to are open source,( e.g., <a href="https://pytorch.org/" target="_blank" rel="noopener noreferrer">PyTorch</a>).</p>
<p>Pyrefly is also open source, <a href="https://github.com/facebook/pyrefly/" target="_blank" rel="noopener noreferrer">available on GitHub</a> under the <a href="https://github.com/facebook/pyrefly/blob/main/LICENSE" target="_blank" rel="noopener noreferrer">MIT license</a>, and we encourage <a href="https://github.com/facebook/pyrefly/pulls" target="_blank" rel="noopener noreferrer">pull requests</a> and <a href="https://github.com/facebook/pyrefly/issues" target="_blank" rel="noopener noreferrer">issue reports</a>. We also have a <a href="https://discord.gg/Cf7mFQtW7W" target="_blank" rel="noopener noreferrer">Discord channel</a> for more free flowing discussions. We would love to build a community around Pyrefly.</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-future-of-pyrefly">The future of Pyrefly<a href="https://pyrefly.org/blog/introducing-pyrefly/#the-future-of-pyrefly" class="hash-link" aria-label="Direct link to The future of Pyrefly" title="Direct link to The future of Pyrefly">​</a></h2>
<p>We will work with the Python community to drive the language forward and improve the developer experience. Since the beginning of Pyre, we open sourced our code and contributed a number of PEPs alongside the community of type checker maintainers. We feel we can do more with Pyrefly to help Python developers leverage the benefits of types for developers, library authors, and folks just learning the language.</p>
<p>Meta has leveraged types in dynamic languages from the beginning and knows the significant benefits it brings to developer productivity and security. We plan to share more of our learnings and tooling with <a href="https://engineering.fb.com/2024/12/09/developer-tools/typed-python-2024-survey-meta/" target="_blank" rel="noopener noreferrer">blogs</a>, better types in the ecosystem and language enhancements.</p>
<p>Today we’re releasing Pyrefly as an alpha. At the same time, we’re busy burning down the long-tail of bugs and features aiming to remove the alpha label this Summer. Your feedback is invaluable to get there, so please give it a try and <a href="https://github.com/facebook/pyrefly/issues" target="_blank" rel="noopener noreferrer">report your bugs</a> or things you think can be improved. Even if Pyrefly isn’t right for your project, we would love to hear how you use types and what you would like to see improved in your editor.</p>
<p>Join us on the journey as we help illuminate your bugs with Pyrefly. Happy coding! 🐍✨</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="hear-more-about-pyrefly">Hear more about Pyrefly<a href="https://pyrefly.org/blog/introducing-pyrefly/#hear-more-about-pyrefly" class="hash-link" aria-label="Direct link to Hear more about Pyrefly" title="Direct link to Hear more about Pyrefly">​</a></h2>
<p>Check out the <a href="https://engineering.fb.com/2025/05/15/developer-tools/open-sourcing-pyrefly-a-faster-python-type-checker-written-in-rust" target="_blank" rel="noopener noreferrer">episode of the Meta Tech Podcast</a> where several team members share their experience developing Pyrefly and technical details for how it works. We also just <a href="https://us.pycon.org/2025/schedule/presentation/118/" target="_blank" rel="noopener noreferrer">talked at PyCon US</a> about high-performance Python through faster type checking and free threaded execution.</p>
<p>To learn more about Meta Open Source, visit our<a href="https://opensource.fb.com/" target="_blank" rel="noopener noreferrer">open source site</a>, subscribe to our <a href="https://www.youtube.com/channel/UCCQY962PmHabTjaHv2wJzfQ" target="_blank" rel="noopener noreferrer">YouTube channel</a>, or follow us on <a href="https://www.facebook.com/MetaOpenSource" target="_blank" rel="noopener noreferrer">Facebook</a>, <a href="https://www.threads.net/@metaopensource" target="_blank" rel="noopener noreferrer">Threads</a>, <a href="https://x.com/MetaOpenSource" target="_blank" rel="noopener noreferrer">X</a>, and <a href="https://www.linkedin.com/showcase/meta-open-source?fbclid=IwZXh0bgNhZW0CMTEAAR2fEOJNb7zOi8rJeRvQry5sRxARpdL3OpS4sYLdC1_npkEy60gBS1ynXwQ_aem_mJUK6jEUApFTW75Emhtpqw" target="_blank" rel="noopener noreferrer">LinkedIn</a>.</p>
<h3 class="anchor anchorWithStickyNavbar_LWe7" id="acknowledgements">Acknowledgements<a href="https://pyrefly.org/blog/introducing-pyrefly/#acknowledgements" class="hash-link" aria-label="Direct link to Acknowledgements" title="Direct link to Acknowledgements">​</a></h3>
<p><em>Pyrefly was created By Meta’s Python Language Tooling Team: Jia Chen, Rebecca Chen, Sam Goldman, David Luo, Kyle Into, Zeina Migeed, Neil Mitchell, Maggie Moss, Conner Nilsen, Aaron Pollack, Teddy Sudol, Steven Troxler, Lucian Wischik, Danny Yang, and Sam Zhou.</em></p>]]></content:encoded>
            <category>typechecking</category>
            <category>news</category>
        </item>
    </channel>
</rss>