Skip to content

Conversation

@jackulau
Copy link
Contributor

Summary

Fixes #2221

When inferring constrained TypeVars during subtype checking (Var(TypeVar) <: ConcreteType), pyrefly was incorrectly
setting the TypeVar to the concrete type and then checking if it satisfies the constraint. This fails for constrained
TypeVars which must be inferred to exactly one of their constraint types per PEP 484.

The Bug

import shutil                                                                                                               
import urllib.request as request                                                                                            
with request.urlopen("https://duckduckgo.com") as remote, open("main.html", 'wb') as local:                                 
    shutil.copyfileobj(remote, local)                                                                                       

This valid code produced:
ERROR Buffer is not assignable to upper bound bytes | str of type variable AnyStr

Root Cause

shutil.copyfileobj has signature (fsrc: SupportsRead[AnyStr], fdst: SupportsWrite[AnyStr]) where AnyStr = TypeVar("AnyStr",
str, bytes).

When checking BufferedWriter <: SupportsWrite[AnyStr]:

  • Protocol matching compares write methods (contravariant parameters)
  • This requires AnyStr <: Buffer
  • Old behavior: Set AnyStr = Buffer, check Buffer <: (str | bytes) → fails
  • New behavior: Find constraint where constraint <: Buffer, find bytes <: Buffer → Set AnyStr = bytes → succeeds

The Fix

For constrained TypeVars, find a constraint that satisfies the subtype relationship rather than directly assigning the
concrete type. This follows PEP 484 semantics: constrained TypeVars must be inferred to exactly one of their constraint
types.

Test Plan

  • Added test_constrained_typevar_protocol_inference - the original bug repro
  • Added test_constrained_typevar_with_any_argument - minimal test with explicit Any
  • Added test_constrained_typevar_no_valid_constraint - verifies errors when no constraint works
  • All 3697 existing tests pass

@meta-cla meta-cla bot added the cla signed label Jan 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

urllib.request.urlopen() type is unknown

1 participant