Skip to content

Fix constrained TypeVar inference in subtype checking (#2221)#2225

Closed
jackulau wants to merge 6 commits intofacebook:mainfrom
jackulau:2221
Closed

Fix constrained TypeVar inference in subtype checking (#2221)#2225
jackulau wants to merge 6 commits intofacebook:mainfrom
jackulau:2221

Conversation

@jackulau
Copy link
Copy Markdown
Contributor

@jackulau jackulau commented Jan 25, 2026

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
@rchen152 rchen152 self-assigned this Jan 26, 2026
@meta-codesync
Copy link
Copy Markdown
Contributor

meta-codesync Bot commented Jan 28, 2026

@rchen152 has imported this pull request. If you are a Meta employee, you can view this in D91648395.

Copy link
Copy Markdown
Contributor

@rchen152 rchen152 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! Unfortunately, I don't think this is the right fix. This PR seems to be flipping the direction of the is_subset_eq check we do on vars for constraints (i.e., we currently do is_subset_eq(t2, bound) and this PR changes it to is_subset_eq(c, t2) for each constraint c). That happens to be the right direction to do the check for a contravariant protocol like in #2221, but this PR isn't checking for variance; it's always flipping the check. I'm pretty sure that's the wrong thing do in most cases.

@github-actions
Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity for more than 2 weeks.

If you are still working on this this pull request, please add a comment or push new commits to keep it active. Otherwise, please unassign yourself and allow someone else to take over.

Thank you for your contributions!

@github-actions github-actions Bot added the stale label Feb 12, 2026
@rchen152
Copy link
Copy Markdown
Contributor

I'm going to close this PR since it's stale and has some merge conflicts, but please feel free to open another PR if you'd like to take another stab at the issue (or comment on the issue if you'd prefer to discuss ideas first)

@rchen152 rchen152 closed this Feb 16, 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

2 participants