Skip to content

Conversation

visz11
Copy link
Collaborator

@visz11 visz11 commented Oct 6, 2025

(Explain how this PR changes mypy.)

Summary by CodeRabbit

  • New Features
    • Improved validation for assignments involving abstract classes and protocols, including Type[...] and Callable-returning abstract/protocol types.
  • Bug Fixes
    • Better detection and reporting of invalid “concrete-only” assignments while allowing valid self-assignments of abstract/protocol types.
  • Tests
    • Added unit tests covering abstracts and protocols with Callable and Type scenarios, aliases, calls, and self-assignment.
  • Refactor
    • Centralized concrete-only assignment validation to simplify and unify checks.

Copy link

Summary of Changes

Hello @visz11, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors and enhances the type checker's logic concerning the assignment of abstract classes and protocols. It consolidates the existing validation into a new, more modular method, check_concrete_only_assign. This new method expands the scope of the check to include Callable types, ensuring that only concrete implementations are assigned when an abstract type or protocol is expected as a return type. Additionally, it introduces a specific allowance for self-assignment of abstract types, preventing unnecessary errors in such cases.

Highlights

  • Refactored Concrete Assignment Check: The complex logic for checking concrete-only assignments has been extracted from the check_assignment method into a new dedicated helper method named check_concrete_only_assign.
  • Extended Type Checking for Callables: The concrete assignment validation now also applies to CallableType return types, ensuring that only concrete classes can be assigned to variables typed as Callable[[], A] where A is an abstract class or protocol.
  • Allowed Self-Assignment of Abstract/Protocol Types: An explicit check has been added within the new check_concrete_only_assign method to permit self-assignment (e.g., A = A) even when A is an abstract class or protocol.
  • New Test Cases: New unit tests have been added for both abstract classes and protocols to cover the extended Callable type checking and the allowance for self-assignment.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

coderabbitai bot commented Oct 6, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a new helper TypeChecker.check_concrete_only_assign and updates check_assignment to delegate concrete-only assignment validation; introduces unit tests for abstract classes and protocols with Callable-returned types. The patch also accidentally includes a duplicate definition of the new helper.

Changes

Cohort / File(s) Summary
Type checking core refactor
mypy/checker.py
Adds TypeChecker.check_concrete_only_assign(...) and updates check_assignment to call it and short-circuit on concrete-only assignment errors; centralizes logic for TypeType/CallableType and self-reassignment checks. Patch includes a duplicate (identical) definition of the helper in the same class.
Abstracts with Callable tests
test-data/unit/check-abstract.test
Adds testInstantiationAbstractsWithCallables to exercise assignments/instantiation rules for abstract classes used via Callable[[], A] and Type[...] aliases, including valid/invalid assignments and self-assignments.
Protocols with Callable tests
test-data/unit/check-protocols.test
Adds testInstantiationProtocolWithCallables covering Callable[[], P] and protocol-class assignment scenarios, alias reassignments, and self-assignment cases.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant CA as check_assignment
  participant TC as TypeChecker
  participant CCOA as check_concrete_only_assign
  participant ERR as error_emitter

  CA->>TC: determine lvalue_type, rvalue_type
  CA->>CCOA: check_concrete_only_assign(lvalue_type, lvalue, rvalue_type, rvalue)
  alt concrete-only violation
    CCOA->>ERR: emit concrete_only_assign error
    CCOA-->>CA: False (stop further processing)
  else no violation
    CCOA-->>CA: True (continue)
    CA->>CA: proceed with remaining assignment checks
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

In a warren of types I quietly tread,
I hop to a helper where checks are led.
Two copies discovered — an extra surprise,
I nibble the duplicate, neat and wise.
Tests bloom like clover, precise and bright. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title “Clone issue 13171” does not summarize the actual changes, which introduce a new helper method to centralize concrete‐only assignment logic and add related tests, so it is misleading and off‐topic. Rename the title to clearly reflect the main change, such as “Add check_concrete_only_assign helper to centralize concrete-only assignment logic and related tests.”
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch clone-issue-13171

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

refacto-test bot commented Oct 6, 2025

Fix concrete_only_assign check for Callable return types

TL;DR: Extends the concrete-only assignment check to handle Callable return types with abstract classes or protocols.


Refacto PR Summary

Refactors the concrete-only assignment check into a dedicated method that also handles Callable return types.
This PR fixes an issue where mypy wasn't properly checking assignments of abstract classes or protocols to variables with Callable return types. Previously, the check only worked for explicit Type[A] annotations but missed cases where a Callable returns an abstract class. The implementation extracts the logic into a new check_concrete_only_assign method that handles both TypeType and CallableType cases, ensuring consistent type checking behavior across different ways of referencing abstract classes and protocols.

Change Highlights

Click to expand
  • mypy/checker.py: Extracted concrete-only assignment check into a dedicated method
  • mypy/checker.py: Added support for Callable return types with abstract classes/protocols
  • mypy/checker.py: Added special case handling for self-assignment of abstract classes
  • test-data/unit/check-abstract.test: Added tests for abstract class assignments with Callables
  • test-data/unit/check-protocols.test: Added tests for protocol assignments with Callables

Sequence Diagram

sequenceDiagram
    participant TC as TypeChecker
    participant CCOA as check_concrete_only_assign
    participant MSG as MessageBuilder
    
    TC->>CCOA: lvalue_type, lvalue, rvalue_type, rvalue
    Note over CCOA: Check for self-assignment case
    CCOA->>CCOA: Check if rvalue is abstract/protocol
    CCOA->>CCOA: Check if lvalue is TypeType or CallableType
    alt lvalue is TypeType or CallableType with abstract/protocol
        CCOA->>MSG: concrete_only_assign error
        CCOA-->>TC: Return False (error)
    else
        CCOA-->>TC: Return True (valid)
    end
Loading

Testing Guide

Click to expand
  1. Test assignment of abstract class to Callable returning abstract class: var: Callable[[], A] = A should produce an error
  2. Test assignment of concrete class to Callable returning abstract class: var: Callable[[], A] = C should be valid
  3. Test assignment of protocol to Callable returning protocol: var: Callable[[], P] = P should produce an error
  4. Test self-assignment of abstract classes: A = A should be valid
  5. Verify existing Type[A] assignment checks still work correctly

@visz11
Copy link
Collaborator Author

visz11 commented Oct 6, 2025

/refacto-test

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the logic for checking assignments of abstract or protocol classes into a new check_concrete_only_assign method. This new method extends the check to also cover assignments to variables of type Callable[..., A] where A is an abstract class or protocol. Additionally, it correctly allows self-assignments like A = A. The changes are well-implemented and include corresponding tests for both abstract classes and protocols. I have one minor suggestion to improve the readability of the new method.

Comment on lines +2473 to +2479
lvalue_is_a_callable = False
if isinstance(lvalue_type, CallableType):
ret_type = get_proper_type(lvalue_type.ret_type)
lvalue_is_a_callable = (
isinstance(ret_type, Instance) and
(ret_type.type.is_abstract or ret_type.type.is_protocol)
)

Choose a reason for hiding this comment

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

medium

This block can be slightly refactored to improve readability by nesting the isinstance check. This makes the logic a little more explicit and avoids a long boolean expression.

Suggested change
lvalue_is_a_callable = False
if isinstance(lvalue_type, CallableType):
ret_type = get_proper_type(lvalue_type.ret_type)
lvalue_is_a_callable = (
isinstance(ret_type, Instance) and
(ret_type.type.is_abstract or ret_type.type.is_protocol)
)
lvalue_is_a_callable = False
if isinstance(lvalue_type, CallableType):
ret_type = get_proper_type(lvalue_type.ret_type)
if isinstance(ret_type, Instance):
lvalue_is_a_callable = (
ret_type.type.is_abstract or ret_type.type.is_protocol
)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 380cb8d and 6a06e36.

📒 Files selected for processing (3)
  • mypy/checker.py (2 hunks)
  • test-data/unit/check-abstract.test (1 hunks)
  • test-data/unit/check-protocols.test (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
mypy/checker.py (3)
mypy/types.py (13)
  • Type (210-249)
  • get_proper_type (2561-2561)
  • get_proper_type (2563-2563)
  • get_proper_type (2566-2583)
  • CallableType (1473-1854)
  • is_type_obj (1273-1273)
  • is_type_obj (1633-1634)
  • is_type_obj (1882-1885)
  • type_object (1276-1276)
  • type_object (1636-1644)
  • type_object (1887-1890)
  • TypeType (2454-2524)
  • Instance (1091-1258)
mypy/nodes.py (2)
  • Expression (212-218)
  • NameExpr (1656-1674)
mypy/messages.py (1)
  • concrete_only_assign (1360-1362)
🔇 Additional comments (2)
test-data/unit/check-abstract.test (1)

253-284: Solid coverage for callable-returning abstracts.

Thanks for exercising callable-typed vars against abstract/concrete class assignments, aliases, and self-rebinds—this mirrors the existing Type[...] scenarios perfectly and guards against regressions in the new helper.

test-data/unit/check-protocols.test (1)

1622-1651: Nice parity for protocol callables.

This adds the protocol counterpart to the abstract-class tests, ensuring callable variables, aliases, and self-assignments behave consistently after the refactor.

@visz11
Copy link
Collaborator Author

visz11 commented Oct 6, 2025

/refacto-visz

Copy link

refacto-visz bot commented Oct 6, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

Copy link

refacto-visz bot commented Oct 6, 2025

Code Review: Type Assignment Refactoring

👍 Well Done
Self Assignment Check

Prevents false positives for abstract class self-assignment patterns.

Method Extraction Pattern

Successfully extracted complex logic into dedicated method improving modularity.

📁 Selected files for review (3)
  • mypy/checker.py
  • test-data/unit/check-abstract.test
  • test-data/unit/check-protocols.test
🎯 Custom Instructions
✅ Applied Instructions
Organization Guidelines
  • Reuse code wherever possible and avoid redundant code by refactoring with utils and static method across application

Scope: All files

Repository Guidelines
  • Preserve source‑location info when synthesising a dummy subject.
  • When reviewing Python code for this project Prioritize portability over clarity, especially when dealing with cross-Python compatibility. However, with the priority in mind, do still consider improvements to clarity when relevant.
  • When reviewing Python code for this project‑As a style convention, consider the code style advocated in CEP-8

Scope: 1 specific files

Matched files

Paths [mypy/checker.py] matched 1 files:

  • mypy/checker.py
❌ Unapplied Instructions
portal-backend

Reason: Repository 'portal-backend' does not match current PR repository

refacto-api

Reason: Repository 'refacto-api' does not match current PR repository

pr-reviewer

Reason: Repository 'pr-reviewer' does not match current PR repository

bazel

Reason: Repository 'bazel' does not match current PR repository

devd-client

Reason: Repository 'devd-client' does not match current PR repository

📝 Additional Comments
mypy/checker.py (4)
Complex Boolean Logic

Complex nested boolean condition with multiple isinstance checks and property access reduces readability. The negated condition structure makes the logic harder to follow. Consider extracting this into a descriptive helper method like 'is_abstract_or_protocol_type_obj' to improve code clarity.

Standards:

  • Clean-Code-Functions
  • Refactoring-Extract-Method
  • Maintainability-Quality-Readability
Type Safety Enhancement

Using cast() to force type conversion bypasses mypy's type safety without runtime validation. The comment indicates assumption about lvalue_type being TypeType or CallableType, but cast() removes compile-time guarantees. If lvalue_type is unexpectedly None or different type, this could cause runtime errors. Consider explicit type checking before the cast or restructuring to avoid the cast entirely.

Standards:

  • CWE-704
  • NIST-SSDF-PW.1
  • Repo-Guideline-Prioritize portability over clarity, especially when dealing with cross-Python compatibility
Boolean Logic Simplification

Boolean logic can be simplified by combining the conditional checks earlier in the method. The current structure creates unnecessary branching complexity. Consider consolidating the type checking logic to reduce cognitive load and improve maintainability.

Standards:

  • Algorithm-Correctness-Simplification
  • Logic-Verification-Clarity
Early Return Optimization

Good early return pattern that avoids expensive type processing for self-assignment cases. This optimization prevents unnecessary get_proper_type() calls and complex type checking when assignment is trivially valid. Improves performance for common self-assignment patterns.

Standards:

  • ISO-IEC-25010-Performance-Efficiency-Time-Behavior
  • Optimization-Pattern-Early-Exit

mypy/checker.py Outdated
Comment on lines 2457 to 2458
rvalue_type = get_proper_type(rvalue_type)
lvalue_type = get_proper_type(lvalue_type)
Copy link

Choose a reason for hiding this comment

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

Redundant Type Processing

Duplicate get_proper_type() calls on same variables that were already processed at lines 2429. This creates redundant type processing overhead in assignment checking hot path. The refactored method performs the same operations twice for identical inputs.

Standards
  • Org-Guideline-Reuse code wherever possible and avoid redundant code by refactoring with utils and static method across application
  • ISO-IEC-25010-Performance-Efficiency-Resource-Utilization
  • Optimization-Pattern-Redundancy-Elimination

mypy/checker.py Outdated
Comment on lines 2457 to 2458
rvalue_type = get_proper_type(rvalue_type)
lvalue_type = get_proper_type(lvalue_type)
Copy link

Choose a reason for hiding this comment

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

Code Duplication Opportunity

The get_proper_type() calls are duplicated from the original code that was refactored. This creates maintenance overhead where the same type processing logic exists in both locations. Consider extracting this common pattern into a shared utility method to follow DRY principles.

Standards
  • Clean-Code-Functions
  • SOLID-SRP
  • Refactoring-Extract-Method
  • Org-Guideline-Reuse code wherever possible and avoid redundant code by refactoring with utils and static method across application

Comment on lines +2473 to +2479
lvalue_is_a_callable = False
if isinstance(lvalue_type, CallableType):
ret_type = get_proper_type(lvalue_type.ret_type)
lvalue_is_a_callable = (
isinstance(ret_type, Instance) and
(ret_type.type.is_abstract or ret_type.type.is_protocol)
)
Copy link

Choose a reason for hiding this comment

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

Callable Logic Enhancement

Callable validation logic only checks return type for abstract/protocol status but ignores callable parameter types. Complete callable validation should verify all parameter types for abstract/protocol constraints. This creates incomplete validation coverage for callable type assignments.

Standards
  • Algorithm-Correctness-Completeness
  • Business-Rule-Type-Validation
  • Logic-Verification-Coverage


if lvalue_is_a_type or lvalue_is_a_callable:
# `lvalue_type` here is either `TypeType` or `CallableType`:
self.msg.concrete_only_assign(cast(Type, lvalue_type), rvalue)
Copy link

Choose a reason for hiding this comment

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

Type Cast Safety

Type cast to Type without runtime validation could cause ClassCastException if lvalue_type is not actually a Type instance. The cast assumes type safety based on conditional checks but doesn't handle edge cases where type system assumptions fail. Adding runtime type validation before cast would prevent potential runtime failures.

Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Functional-Correctness-Appropriateness
  • DbC-Preconditions

Comment on lines +2447 to +2451
def check_concrete_only_assign(self,
lvalue_type: Optional[Type],
lvalue: Expression,
rvalue_type: Type,
rvalue: Expression) -> bool:
Copy link

Choose a reason for hiding this comment

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

Method Parameter Ordering

Parameter ordering mixes types and expressions inconsistently (lvalue_type, lvalue, rvalue_type, rvalue). Grouping related parameters together (types together, expressions together) would improve method signature readability and follow consistent parameter organization patterns.

Standards
  • Clean-Code-Functions
  • Maintainability-Quality-Readability

Comment on lines +2452 to +2454
if (isinstance(lvalue, NameExpr) and isinstance(rvalue, NameExpr)
and lvalue.node == rvalue.node):
# This means that we reassign abstract class to itself. Like `A = A`
Copy link

Choose a reason for hiding this comment

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

Self Assignment Detection

Self-assignment detection relies on node identity comparison which may not handle all edge cases like aliased references or complex expressions. While the current implementation prevents false positives for direct self-assignment, it could miss security-relevant reassignments through aliases. Consider expanding validation to handle indirect references that could bypass abstract class assignment restrictions.

Standards
  • CWE-670
  • OWASP-A04

@visz11
Copy link
Collaborator Author

visz11 commented Oct 6, 2025

/refacto-visz

Copy link

refacto-visz bot commented Oct 6, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

Copy link

refacto-visz bot commented Oct 6, 2025

Code Review: Type Processing Refactoring

👍 Well Done
Self Assignment Protection

Added safety check preventing false positives for abstract class reassignment.

Method Extraction Applied

Refactored complex assignment logic into dedicated method improving code organization.

📁 Selected files for review (3)
  • mypy/checker.py
  • test-data/unit/check-abstract.test
  • test-data/unit/check-protocols.test
🎯 Custom Instructions
✅ Applied Instructions
Organization Guidelines
  • Reuse code wherever possible and avoid redundant code by refactoring with utils and static method across application

Scope: All files

Repository Guidelines
  • Preserve source‑location info when synthesising a dummy subject.
  • When reviewing Python code for this project Prioritize portability over clarity, especially when dealing with cross-Python compatibility. However, with the priority in mind, do still consider improvements to clarity when relevant.
  • When reviewing Python code for this project‑As a style convention, consider the code style advocated in CEP-8

Scope: 1 specific files

Matched files

Paths [mypy/checker.py] matched 1 files:

  • mypy/checker.py
❌ Unapplied Instructions
portal-backend

Reason: Repository 'portal-backend' does not match current PR repository

refacto-api

Reason: Repository 'refacto-api' does not match current PR repository

pr-reviewer

Reason: Repository 'pr-reviewer' does not match current PR repository

bazel

Reason: Repository 'bazel' does not match current PR repository

devd-client

Reason: Repository 'devd-client' does not match current PR repository

📝 Additional Comments
mypy/checker.py (4)
Incomplete Callable Validation

Callable validation logic only examines return type for abstract/protocol constraints while ignoring parameter types. Complete validation should verify all callable parameter types for abstract/protocol status. Incomplete validation coverage could allow invalid callable assignments that violate type safety guarantees.

Standards:

  • ISO-IEC-25010-Functional-Correctness-Functional-Completeness
  • DbC-Postconditions
Type Processing Optimization

Multiple get_proper_type() calls throughout method create repeated type normalization overhead. Caching normalized types or passing pre-normalized types from caller would reduce computational cost. Current pattern performs O(n) type processing operations where O(1) cached access possible.

Standards:

  • ISO-IEC-25010-Performance-Efficiency-Time-Behavior
  • Optimization-Pattern-Caching
  • Algorithmic-Complexity-Linear-Optimization
Type Cast Safety

Type cast without runtime validation assumes type safety based on conditional checks. Adding explicit type validation before cast would prevent potential runtime failures and improve defensive programming practices.

Standards:

  • Clean-Code-Error-Handling
  • Maintainability-Quality-Fault-Tolerance
Self Assignment Bypass

Self-assignment detection relies on node identity comparison which may not handle all edge cases like aliased references or complex expressions. While preventing false positives for direct self-assignment, it could miss security-relevant reassignments through aliases that bypass abstract class assignment restrictions. Consider expanding validation to handle indirect references.

Standards:

  • CWE-670
  • OWASP-A04
  • NIST-SSDF-PW.1

mypy/checker.py Outdated
Comment on lines 2454 to 2455
rvalue_type = get_proper_type(rvalue_type)
lvalue_type = get_proper_type(lvalue_type)
Copy link

Choose a reason for hiding this comment

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

Redundant Type Processing

Duplicate get_proper_type() calls violate organization guideline requiring code reuse and redundancy avoidance. The same type processing operations were already performed at lines 2426 in the original code. This creates unnecessary computational overhead in the assignment checking hot path and maintenance burden through code duplication.

        # Types should already be proper types from the caller
Commitable Suggestion
Suggested change
rvalue_type = get_proper_type(rvalue_type)
lvalue_type = get_proper_type(lvalue_type)
# Types should already be proper types from the caller
Standards
  • Org-Guideline-Reuse code wherever possible and avoid redundant code by refactoring with utils and static method across application
  • ISO-IEC-25010-Performance-Efficiency-Resource-Utilization


if lvalue_is_a_type or lvalue_is_a_callable:
# `lvalue_type` here is either `TypeType` or `CallableType`:
self.msg.concrete_only_assign(cast(Type, lvalue_type), rvalue)
Copy link

Choose a reason for hiding this comment

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

Unsafe Type Cast

Type cast to Type without runtime validation assumes type safety based on conditional checks but doesn't handle edge cases where type system assumptions fail. The cast could cause runtime failures if lvalue_type is not actually a Type instance despite passing the conditional guards.

Standards
  • Algorithm-Correctness-Type-Safety
  • Logic-Verification-Runtime-Safety
  • Business-Rule-Defensive-Programming

Comment on lines +2444 to +2448
def check_concrete_only_assign(self,
lvalue_type: Optional[Type],
lvalue: Expression,
rvalue_type: Type,
rvalue: Expression) -> bool:
Copy link

Choose a reason for hiding this comment

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

Parameter Organization Inconsistency

Parameter ordering mixes types and expressions inconsistently reducing method signature readability. Grouping related parameters together would improve comprehension and follow consistent organization patterns. Current ordering makes parameter relationships unclear.

Standards
  • Clean-Code-Functions
  • Maintainability-Quality-Readability

Comment on lines +2470 to +2477
lvalue_is_a_callable = False
if isinstance(lvalue_type, CallableType):
ret_type = get_proper_type(lvalue_type.ret_type)
lvalue_is_a_callable = (
isinstance(ret_type, Instance) and
(ret_type.type.is_abstract or ret_type.type.is_protocol)
)

Copy link

Choose a reason for hiding this comment

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

Incomplete Validation Logic

Callable validation only checks return type for abstract/protocol status but ignores parameter types. Complete validation should verify all parameter types for abstract/protocol constraints. This creates incomplete coverage for callable type assignments.

Standards
  • SOLID-ISP
  • Clean-Code-Functions
  • Maintainability-Quality-Completeness

Co-authored-by: refacto-visz[bot] <230139763+refacto-visz[bot]@users.noreply.github.com>
@visz11
Copy link
Collaborator Author

visz11 commented Oct 6, 2025

/refacto-visz

Copy link

refacto-visz bot commented Oct 6, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

Copy link

refacto-visz bot commented Oct 6, 2025

Code Review: Type System Refactoring

👍 Well Done
Method Extraction Pattern

Complex logic extracted into dedicated method improving maintainability and testability.

PartialType Guard Added

Defensive check prevents assertion failures during type processing.

Self-Assignment Protection

Correctly prevents abstract class self-assignment validation errors through node comparison.

📁 Selected files for review (3)
  • mypy/checker.py
  • test-data/unit/check-abstract.test
  • test-data/unit/check-protocols.test
🎯 Custom Instructions
✅ Applied Instructions
Organization Guidelines
  • Reuse code wherever possible and avoid redundant code by refactoring with utils and static method across application

Scope: All files

Repository Guidelines
  • Preserve source‑location info when synthesising a dummy subject.
  • When reviewing Python code for this project Prioritize portability over clarity, especially when dealing with cross-Python compatibility. However, with the priority in mind, do still consider improvements to clarity when relevant.
  • When reviewing Python code for this project‑As a style convention, consider the code style advocated in CEP-8

Scope: 1 specific files

Matched files

Paths [mypy/checker.py] matched 1 files:

  • mypy/checker.py
❌ Unapplied Instructions
portal-backend

Reason: Repository 'portal-backend' does not match current PR repository

refacto-api

Reason: Repository 'refacto-api' does not match current PR repository

pr-reviewer

Reason: Repository 'pr-reviewer' does not match current PR repository

bazel

Reason: Repository 'bazel' does not match current PR repository

devd-client

Reason: Repository 'devd-client' does not match current PR repository

📝 Additional Comments
mypy/checker.py (3)
Parameter Organization Inconsistency

Parameter ordering mixes types and expressions inconsistently reducing method signature readability. Grouping related parameters (types together, expressions together) would improve comprehension and follow consistent organization patterns. Current ordering makes parameter relationships unclear.

Standards:

  • Clean-Code-Functions
  • Maintainability-Quality-Readability
Method Complexity Optimization

New method extracts complex assignment validation logic improving code organization and reusability. Method handles multiple validation paths with early returns optimizing execution flow. This follows organization guideline for code reuse while reducing cyclomatic complexity in main assignment checking method.

Standards:

  • Org-Guideline-Reuse code wherever possible and avoid redundant code by refactoring with utils and static method across application
  • ISO-IEC-25010-Maintainability-Modularity
  • Optimization-Pattern-Method-Extraction
Incomplete Validation Logic

Callable validation only checks return type for abstract/protocol status but ignores parameter types. Complete validation should verify all parameter types for abstract/protocol constraints to ensure comprehensive type safety coverage. This creates incomplete coverage for callable type assignments.

Standards:

  • CWE-20
  • CWE-697
  • OWASP-A04


if lvalue_is_a_type or lvalue_is_a_callable:
# `lvalue_type` here is either `TypeType` or `CallableType`:
self.msg.concrete_only_assign(cast(Type, lvalue_type), rvalue)
Copy link

Choose a reason for hiding this comment

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

Unsafe Type Cast

Type cast to Type without runtime validation assumes type safety based on conditional checks but doesn't handle edge cases where type system assumptions fail. The cast could cause runtime failures if lvalue_type is not actually a Type instance despite passing conditional guards.

            # Handle each type case separately to avoid unsafe casting
            if lvalue_is_a_type:
                self.msg.concrete_only_assign(lvalue_type, rvalue)
            elif lvalue_is_a_callable:
                self.msg.concrete_only_assign(lvalue_type, rvalue)
Commitable Suggestion
Suggested change
self.msg.concrete_only_assign(cast(Type, lvalue_type), rvalue)
# Handle each type case separately to avoid unsafe casting
if lvalue_is_a_type:
self.msg.concrete_only_assign(lvalue_type, rvalue)
elif lvalue_is_a_callable:
self.msg.concrete_only_assign(lvalue_type, rvalue)
Standards
  • CWE-704
  • CWE-843
  • OWASP-A04

Comment on lines +2456 to +2457
if isinstance(lvalue_type, PartialType):
return True
Copy link

Choose a reason for hiding this comment

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

PartialType Crash Prevention

Early return for PartialType prevents assertion failures in get_proper_type() calls that would crash the type checker. This addresses critical reliability issue where partially inferred types during branch analysis could cause complete checker failure. Performance impact includes avoiding expensive crash recovery and maintaining system stability.

        # Skip processing for PartialType to avoid assertion failures
        if isinstance(lvalue_type, PartialType) or lvalue_type is None:
            return True
Commitable Suggestion
Suggested change
if isinstance(lvalue_type, PartialType):
return True
# Skip processing for PartialType to avoid assertion failures
if isinstance(lvalue_type, PartialType) or lvalue_type is None:
return True
Standards
  • ISO-IEC-25010-Reliability-Fault-Tolerance
  • ISO-IEC-25010-Performance-Efficiency-Time-Behavior
  • Algorithmic-Complexity-Early-Exit-Optimization


if lvalue_is_a_type or lvalue_is_a_callable:
# `lvalue_type` here is either `TypeType` or `CallableType`:
self.msg.concrete_only_assign(cast(Type, lvalue_type), rvalue)
Copy link

Choose a reason for hiding this comment

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

Unsafe Type Cast

Type cast to Type without runtime validation assumes type safety based on conditional checks. The cast could cause runtime failures if lvalue_type is not actually a Type instance despite passing conditional guards. Logic assumes perfect type system alignment without defensive validation.

Standards
  • Algorithm-Correctness-Type-Safety
  • Logic-Verification-Runtime-Safety
  • Business-Rule-Defensive-Programming

Comment on lines +2472 to +2478
lvalue_is_a_callable = False
if isinstance(lvalue_type, CallableType):
ret_type = get_proper_type(lvalue_type.ret_type)
lvalue_is_a_callable = (
isinstance(ret_type, Instance) and
(ret_type.type.is_abstract or ret_type.type.is_protocol)
)
Copy link

Choose a reason for hiding this comment

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

Incomplete Callable Validation

Callable validation logic only examines return type for abstract/protocol constraints but ignores parameter types. Complete validation requires checking all parameter types for abstract/protocol status. This creates logical gaps where callable assignments with abstract parameter types bypass validation checks.

        lvalue_is_a_callable = False
        if isinstance(lvalue_type, CallableType):
            ret_type = get_proper_type(lvalue_type.ret_type)
            # Check return type for abstract/protocol status
            lvalue_is_a_callable = (
                isinstance(ret_type, Instance) and
                (ret_type.type.is_abstract or ret_type.type.is_protocol)
            )
            # Also check parameter types for abstract/protocol status
            if not lvalue_is_a_callable:
                for arg_type in lvalue_type.arg_types:
                    arg_type = get_proper_type(arg_type)
                    if (isinstance(arg_type, Instance) and
                            (arg_type.type.is_abstract or arg_type.type.is_protocol)):
                        lvalue_is_a_callable = True
                        break
Commitable Suggestion
Suggested change
lvalue_is_a_callable = False
if isinstance(lvalue_type, CallableType):
ret_type = get_proper_type(lvalue_type.ret_type)
lvalue_is_a_callable = (
isinstance(ret_type, Instance) and
(ret_type.type.is_abstract or ret_type.type.is_protocol)
)
lvalue_is_a_callable = False
if isinstance(lvalue_type, CallableType):
ret_type = get_proper_type(lvalue_type.ret_type)
# Check return type for abstract/protocol status
lvalue_is_a_callable = (
isinstance(ret_type, Instance) and
(ret_type.type.is_abstract or ret_type.type.is_protocol)
)
# Also check parameter types for abstract/protocol status
if not lvalue_is_a_callable:
for arg_type in lvalue_type.arg_types:
arg_type = get_proper_type(arg_type)
if (isinstance(arg_type, Instance) and
(arg_type.type.is_abstract or arg_type.type.is_protocol)):
lvalue_is_a_callable = True
break
Standards
  • Business-Rule-Validation-Completeness
  • Logic-Verification-Coverage
  • Algorithm-Correctness-Completeness

Comment on lines +2454 to +2457
# Types should already be proper types from the caller
# Skip processing for PartialType to avoid assertion failures
if isinstance(lvalue_type, PartialType):
return True
Copy link

Choose a reason for hiding this comment

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

Redundant Type Processing

Comment indicates types should already be proper types from caller, yet original code performed get_proper_type() calls that were removed. This violates organization guideline requiring code reuse and redundancy avoidance. The refactoring assumes caller responsibility without enforcing the contract through preconditions.

Standards
  • Org-Guideline-Reuse code wherever possible and avoid redundant code by refactoring with utils and static method across application
  • ISO-IEC-25010-Reliability-Maturity
  • DbC-Preconditions

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fccc28e and 8b3694b.

📒 Files selected for processing (1)
  • mypy/checker.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
mypy/checker.py (3)
mypy/types.py (14)
  • Type (210-249)
  • PartialType (2399-2433)
  • CallableType (1473-1854)
  • is_type_obj (1273-1273)
  • is_type_obj (1633-1634)
  • is_type_obj (1882-1885)
  • type_object (1276-1276)
  • type_object (1636-1644)
  • type_object (1887-1890)
  • TypeType (2454-2524)
  • Instance (1091-1258)
  • get_proper_type (2561-2561)
  • get_proper_type (2563-2563)
  • get_proper_type (2566-2583)
mypy/nodes.py (2)
  • Expression (212-218)
  • NameExpr (1656-1674)
mypy/messages.py (1)
  • concrete_only_assign (1360-1362)

Comment on lines +2444 to +2484
def check_concrete_only_assign(self,
lvalue_type: Optional[Type],
lvalue: Expression,
rvalue_type: Type,
rvalue: Expression) -> bool:
if (isinstance(lvalue, NameExpr) and isinstance(rvalue, NameExpr)
and lvalue.node == rvalue.node):
# This means that we reassign abstract class to itself. Like `A = A`
return True

# Types should already be proper types from the caller
# Skip processing for PartialType to avoid assertion failures
if isinstance(lvalue_type, PartialType):
return True
if not (
isinstance(rvalue_type, CallableType) and
rvalue_type.is_type_obj() and
(rvalue_type.type_object().is_abstract or
rvalue_type.type_object().is_protocol)):
return True

lvalue_is_a_type = (
isinstance(lvalue_type, TypeType) and
isinstance(lvalue_type.item, Instance) and
(lvalue_type.item.type.is_abstract or
lvalue_type.item.type.is_protocol)
)

lvalue_is_a_callable = False
if isinstance(lvalue_type, CallableType):
ret_type = get_proper_type(lvalue_type.ret_type)
lvalue_is_a_callable = (
isinstance(ret_type, Instance) and
(ret_type.type.is_abstract or ret_type.type.is_protocol)
)

if lvalue_is_a_type or lvalue_is_a_callable:
# `lvalue_type` here is either `TypeType` or `CallableType`:
self.msg.concrete_only_assign(cast(Type, lvalue_type), rvalue)
return False
return True
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Restore alias handling before concrete-only enforcement

Factoring the concrete-only check into check_concrete_only_assign() dropped the get_proper_type() normalization we used to do in-line. As a result, when the lvalue (or rvalue) type arrives wrapped in a TypeAliasType, the new helper never recognizes it as a TypeType/CallableType, so the concrete-only guard silently stops running. For example:

from typing import Type, TypeAlias
from abc import ABC

class Base(ABC): ...
Concrete = type("Concrete", (Base,), {})

AbstractType: TypeAlias = Type[Base]
x: AbstractType = Base  # used to error, now passes

To keep the previous behaviour, normalize both sides after the PartialType guard so aliases (and other wrappers) are unwrapped before the isinstance checks:

         if isinstance(lvalue_type, PartialType):
             return True
-        if not (
-            isinstance(rvalue_type, CallableType) and
-            rvalue_type.is_type_obj() and
-            (rvalue_type.type_object().is_abstract or
-                rvalue_type.type_object().is_protocol)):
+        proper_rvalue_type = get_proper_type(rvalue_type)
+        if not (
+            isinstance(proper_rvalue_type, CallableType) and
+            proper_rvalue_type.is_type_obj() and
+            (proper_rvalue_type.type_object().is_abstract or
+                proper_rvalue_type.type_object().is_protocol)):
             return True
 
-        lvalue_is_a_type = (
-            isinstance(lvalue_type, TypeType) and
-            isinstance(lvalue_type.item, Instance) and
-            (lvalue_type.item.type.is_abstract or
-                lvalue_type.item.type.is_protocol)
+        proper_lvalue_type = get_proper_type(lvalue_type)
+
+        lvalue_is_a_type = (
+            isinstance(proper_lvalue_type, TypeType) and
+            isinstance(proper_lvalue_type.item, Instance) and
+            (proper_lvalue_type.item.type.is_abstract or
+                proper_lvalue_type.item.type.is_protocol)
         )
 
         lvalue_is_a_callable = False
-        if isinstance(lvalue_type, CallableType):
-            ret_type = get_proper_type(lvalue_type.ret_type)
+        if isinstance(proper_lvalue_type, CallableType):
+            ret_type = get_proper_type(proper_lvalue_type.ret_type)
             lvalue_is_a_callable = (
                 isinstance(ret_type, Instance) and
                 (ret_type.type.is_abstract or ret_type.type.is_protocol)
             )
 
         if lvalue_is_a_type or lvalue_is_a_callable:
             # `lvalue_type` here is either `TypeType` or `CallableType`:
-            self.msg.concrete_only_assign(cast(Type, lvalue_type), rvalue)
+            self.msg.concrete_only_assign(cast(Type, proper_lvalue_type), rvalue)
             return False

That reinstates the original guard for alias-backed annotations.

🤖 Prompt for AI Agents
In mypy/checker.py around lines 2444 to 2484, the new helper dropped
normalization with get_proper_type(), so TypeAliasType (and other wrappers) can
bypass the concrete-only enforcement; after the PartialType guard, call
get_proper_type() on both lvalue_type and rvalue_type (e.g. assign lvalue_type =
get_proper_type(lvalue_type) and rvalue_type = get_proper_type(rvalue_type))
before the isinstance checks so TypeAliasType/other wrappers are unwrapped and
the subsequent TypeType/CallableType logic runs as before.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants