Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 42 additions & 14 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,7 @@
FineGrainedDeferredNodeType: _TypeAlias = Union[FuncDef, MypyFile, OverloadedFuncDef]


# A node which is postponed to be processed during the next pass.
# In normal mode one can defer functions and methods (also decorated and/or overloaded)
# and lambda expressions. Nested functions can't be deferred -- only top-level functions
# and methods of classes not defined within a function can be deferred.

class DeferredNode(NamedTuple):
node: DeferredNodeType
# And its TypeInfo (for semantic analysis self type handling
Expand Down Expand Up @@ -2426,16 +2423,7 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type

# Special case: only non-abstract non-protocol classes can be assigned to
# variables with explicit type Type[A], where A is protocol or abstract.
rvalue_type = get_proper_type(rvalue_type)
lvalue_type = get_proper_type(lvalue_type)
if (isinstance(rvalue_type, CallableType) and rvalue_type.is_type_obj() and
(rvalue_type.type_object().is_abstract or
rvalue_type.type_object().is_protocol) and
isinstance(lvalue_type, TypeType) and
isinstance(lvalue_type.item, Instance) and
(lvalue_type.item.type.is_abstract or
lvalue_type.item.type.is_protocol)):
self.msg.concrete_only_assign(lvalue_type, rvalue)
if not self.check_concrete_only_assign(lvalue_type, lvalue, rvalue_type, rvalue):
return
if rvalue_type and infer_lvalue_type and not isinstance(lvalue_type, PartialType):
# Don't use type binder for definitions of special forms, like named tuples.
Expand All @@ -2453,6 +2441,46 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
self.infer_variable_type(inferred, lvalue, rvalue_type, rvalue)
self.check_assignment_to_slots(lvalue)

def check_concrete_only_assign(self,
lvalue_type: Optional[Type],
lvalue: Expression,
rvalue_type: Type,
rvalue: Expression) -> bool:
Comment on lines +2444 to +2448
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 +2444 to +2448
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

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`
Comment on lines +2449 to +2451
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

return True

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

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

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 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)
)
Comment on lines +2472 to +2478

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
)

Comment on lines +2472 to +2478
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

Comment on lines +2472 to +2478
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 +2472 to +2479
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

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

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

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

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

return False
return True

# (type, operator) tuples for augmented assignments supported with partial types
partial_type_augmented_ops: Final = {
('builtins.list', '+'),
Expand Down
32 changes: 32 additions & 0 deletions test-data/unit/check-abstract.test
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,38 @@ if int():
var_old = C # OK
[out]

[case testInstantiationAbstractsWithCallables]
from typing import Callable, Type
from abc import abstractmethod

class A:
@abstractmethod
def m(self) -> None: pass
class B(A): pass
class C(B):
def m(self) -> None:
pass

var: Callable[[], A]
var() # OK

var = A # E: Can only assign concrete classes to a variable of type "Callable[[], A]"
var = B # E: Can only assign concrete classes to a variable of type "Callable[[], A]"
var = C # OK

# Type aliases:
A1 = A
B1 = B
C1 = C
var = A1 # E: Can only assign concrete classes to a variable of type "Callable[[], A]"
var = B1 # E: Can only assign concrete classes to a variable of type "Callable[[], A]"
var = C1 # OK

# Self assign:
A = A # OK
B = B # OK
C = C # OK

[case testInstantiationAbstractsInTypeForClassMethods]
from typing import Type
from abc import abstractmethod
Expand Down
30 changes: 30 additions & 0 deletions test-data/unit/check-protocols.test
Original file line number Diff line number Diff line change
Expand Up @@ -1619,6 +1619,36 @@ if int():
var_old = B # OK
var_old = C # OK

[case testInstantiationProtocolWithCallables]
from typing import Callable, Protocol

class P(Protocol):
def m(self) -> None: pass
class B(P): pass
class C:
def m(self) -> None:
pass

var: Callable[[], P]
var() # OK

var = P # E: Can only assign concrete classes to a variable of type "Callable[[], P]"
var = B # OK
var = C # OK

# Type aliases:
P1 = P
B1 = B
C1 = C
var = P1 # E: Can only assign concrete classes to a variable of type "Callable[[], P]"
var = B1 # OK
var = C1 # OK

# Self assign:
P = P # OK
B = B # OK
C = C # OK

[case testInstantiationProtocolInTypeForClassMethods]
from typing import Type, Protocol

Expand Down