Skip to content

Fix diagnostic categorization for ReadOnly TypedDict key modifications - #11565

Open
hsusul wants to merge 1 commit into
microsoft:mainfrom
hsusul:fix/typeddict-readonly-key-diagnostic
Open

Fix diagnostic categorization for ReadOnly TypedDict key modifications#11565
hsusul wants to merge 1 commit into
microsoft:mainfrom
hsusul:fix/typeddict-readonly-key-diagnostic

Conversation

@hsusul

@hsusul hsusul commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary of Changes

Fixes an issue where attempting to set or delete a ReadOnly key in a TypedDict (d["read_only_key"] = val or del d["read_only_key"]) was categorized under the diagnostic rule reportTypedDictNotRequiredAccess instead of reportGeneralTypeIssues.

Problem & Root Cause

In getTypeOfIndexedTypedDict (packages/pyright-internal/src/analyzer/typedDicts.ts), allDiagsInvolveNotRequiredKeys tracks whether all generated diagnostic addendums involve access to non-required keys. If allDiagsInvolveNotRequiredKeys remains true, the overall diagnostic is emitted under DiagnosticRule.reportTypedDictNotRequiredAccess instead of DiagnosticRule.reportGeneralTypeIssues.

When evaluating an indexed set or del operation on a ReadOnly field:

} else if (entry.isReadOnly && usage.method !== 'get') {
    diag.addMessage(
        LocAddendum.keyReadOnly().format({
            name: entryName,
            type: evaluator.printType(baseType),
        })
    );
}

allDiagsInvolveNotRequiredKeys was not updated to false. As a result, Pyright classified the keyReadOnly error under reportTypedDictNotRequiredAccess. In standard configuration (where reportTypedDictNotRequiredAccess is 'none'), modifying or deleting a ReadOnly field resulted in a false negative with no diagnostics produced.

Solution

Set allDiagsInvolveNotRequiredKeys = false; when adding a keyReadOnly diagnostic addendum for set or del operations on ReadOnly TypedDict keys so that the diagnostic is correctly categorized under reportGeneralTypeIssues.

Validation Results

  • Unit tests added in typedDictReadOnly3.py and typeEvaluator5.test.ts verifying that modifications and deletions of ReadOnly keys produce errors even when reportTypedDictNotRequiredAccess is disabled ('none').
  • npm run check passed cleanly.
  • npm run typecheck passed cleanly.
  • git diff --check passed cleanly.

@rchiodo

rchiodo commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

🔒 Automated review in progress — @rchiodo is auto-reviewing this PR.

@github-actions

Copy link
Copy Markdown
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

sympy (https://github.com/sympy/sympy)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:314:30 - error: Cannot access attribute "as_independent" for class "Basic"
-     Attribute "as_independent" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:380:15 - error: Operator "*" not supported for "None" (reportOptionalOperand)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:383:13 - error: No overloads for "__init__" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:383:18 - error: Argument of type "list[Iterator[Unknown]]" cannot be assigned to parameter "iterable" of type "Iterable[list[bytes]]" in function "__init__" (reportArgumentType)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:383:30 - error: Cannot access attribute "as_independent" for class "Basic"
-     Attribute "as_independent" is unknown (reportAttributeAccessIssue)
-   .../projects/sympy/sympy/solvers/diophantine/tests/test_diophantine.py:383:78 - error: "args" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/hypergeometric.py:247:67 - error: Operator "**" not supported for types "Basic" and "Literal[2]" (reportOperatorIssue)
+   .../projects/sympy/sympy/solvers/ode/lie_group.py:619:61 - error: Operator "-" not supported for type "Unknown | Basic" (reportOperatorIssue)
-   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:468:28 - error: Argument of type "Expr | Unknown | None" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
+   .../projects/sympy/sympy/solvers/ode/nonhomogeneous.py:468:28 - error: Argument of type "Unknown | None" cannot be assigned to parameter "expr" of type "Expr" in function "make_args"
-     Type "Expr | Unknown | None" is not assignable to type "Expr"
+     Type "Unknown | None" is not assignable to type "Expr"
-   .../projects/sympy/sympy/solvers/ode/ode.py:1579:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1580:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1590:9 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1590:12 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1590:19 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1597:43 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1597:64 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1603:9 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1603:12 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1603:19 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1610:43 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1610:74 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1616:9 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1616:12 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1616:19 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1623:49 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:1623:70 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1753:36 - error: Cannot access attribute "lhs" for class "Expr"
+     Attribute "lhs" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1753:55 - error: Cannot access attribute "rhs" for class "Expr"
+   .../projects/sympy/sympy/solvers/ode/ode.py:1754:36 - error: Cannot access attribute "lhs" for class "Expr"
+     Attribute "lhs" is unknown (reportAttributeAccessIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1755:17 - error: No overloads for "__setitem__" match the provided arguments (reportCallIssue)
+   .../projects/sympy/sympy/solvers/ode/ode.py:1755:17 - error: Argument of type "Equality | BooleanFalse | BooleanTrue | Unknown | Expr" cannot be assigned to parameter "value" of type "Equality | BooleanFalse | BooleanTrue" in function "__setitem__"
+     Type "Equality | BooleanFalse | BooleanTrue | Unknown | Expr" is not assignable to type "Equality | BooleanFalse | BooleanTrue"
+       Type "Expr" is not assignable to type "Equality | BooleanFalse | BooleanTrue"
+         "Expr" is not assignable to "Equality"
+         "Expr" is not assignable to "BooleanFalse"
+         "Expr" is not assignable to "BooleanTrue" (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3442:7 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3442:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3443:7 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3443:38 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3444:14 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3445:14 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3446:14 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3458:50 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3459:50 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3460:50 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3500:5 - error: No overloads for "update" match the provided arguments (reportCallIssue)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3500:7 - error: "update" is not a known attribute of "None" (reportOptionalMemberAccess)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3500:14 - error: Argument of type "Unknown | dict[Unknown, Unknown] | None" cannot be assigned to parameter "m" of type "Iterable[tuple[str, Unknown]]" in function "update"
-     Type "Unknown | dict[Unknown, Unknown] | None" is not assignable to type "Iterable[tuple[str, Unknown]]"
-       "None" is incompatible with protocol "Iterable[tuple[str, Unknown]]"
-         "__iter__" is not present (reportArgumentType)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3501:18 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3501:43 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3502:9 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3502:16 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3502:24 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3502:31 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3503:9 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3503:15 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)
-   .../projects/sympy/sympy/solvers/ode/ode.py:3503:23 - error: Object of type "None" is not subscriptable (reportOptionalSubscript)

... (truncated 266 lines) ...

@rchiodo rchiodo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved via Review Center.

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