Skip to content

[ty] Ignore rejected member annotations for synthesized bindings#25427

Merged
charliermarsh merged 4 commits into
mainfrom
charlie/ignore-invalid-subscript-annotations
Jun 3, 2026
Merged

[ty] Ignore rejected member annotations for synthesized bindings#25427
charliermarsh merged 4 commits into
mainfrom
charlie/ignore-invalid-subscript-annotations

Conversation

@charliermarsh

@charliermarsh charliermarsh commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

We now emit invalid-type-form for an invalid annotation on a non-name target (#25324)... But it turns out that we still allow that annotation to participate in downstream operations, since we create an annotated-assignment definition for it during the indexing pass.

For example:

def accepts_inner(inner: dict[str, int]): ...

def _(x: dict[str, object]):
    x["inner"]: dict[str, float | str] = {"a": 1, "b": "a"}  # error: [invalid-type-form]
    x["inner"]["c"] = 1.0  # error: [invalid-assignment] because we infer from the RHS, not the annotation
    x["inner"] = {"inner": {"a": 1}}
    accepts_inner(**x["inner"])  # ok

The rejected annotation should not widen the dictionary assigned to x["inner"], nor should it constrain the later replacement. (The same issue applies to rejected attribute annotations, like obj.inner: T.)

We now represent each syntactically indexed declaration outcome as either Declared(...) or Rejected. Declaration consumers only use Declared(...).

@astral-sh-bot astral-sh-bot Bot added the ty Multi-file analysis & type inference label May 28, 2026
@astral-sh-bot

astral-sh-bot Bot commented May 28, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 91.94%. The percentage of expected errors that received a diagnostic held steady at 87.09%. The number of fully passing files held steady at 92/134.

@astral-sh-bot

astral-sh-bot Bot commented May 28, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented May 28, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

No diagnostic changes detected ✅

Full report with detailed diff (timing results)

@charliermarsh charliermarsh force-pushed the charlie/ignore-invalid-subscript-annotations branch from 76cf309 to 9a8a555 Compare May 28, 2026 11:33
@charliermarsh charliermarsh changed the base branch from charlie/non-never to charlie/reject-assignment May 28, 2026 11:34
@charliermarsh charliermarsh changed the title [ty] Ignore invalid subscript annotations for synthesized bindings [ty] Ignore rejected member annotations for synthesized bindings May 28, 2026
@charliermarsh charliermarsh force-pushed the charlie/ignore-invalid-subscript-annotations branch 2 times, most recently from 8e66497 to 80432a6 Compare May 28, 2026 11:43
@charliermarsh charliermarsh added the bug Something isn't working label May 28, 2026
@charliermarsh charliermarsh changed the base branch from charlie/reject-assignment to main May 28, 2026 13:39
@charliermarsh charliermarsh force-pushed the charlie/ignore-invalid-subscript-annotations branch 7 times, most recently from a410e49 to e73c5f3 Compare May 30, 2026 14:27
@charliermarsh charliermarsh marked this pull request as ready for review May 30, 2026 14:34
@astral-sh-bot astral-sh-bot Bot requested a review from oconnor663 May 30, 2026 14:34
@charliermarsh

Copy link
Copy Markdown
Member Author

For context, this is a bit like #25340.

@carljm

carljm commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Haven't looked at this PR closely, but I was curious about the approach and had Codex look at it; it pointed out that this panics in a stub file:

# repro.pyi
x: dict[str, object]
x["a"]: int
reveal_type(x["a"])

I think because we consider declarations in stub files to also be bindings.

@charliermarsh

Copy link
Copy Markdown
Member Author

fml

@carljm

carljm commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

It's really too bad that we can't just ignore these rejected declarations entirely in semantic indexing and avoid a lot of this complexity. It seems like maybe we could for subscripts, since those are never valid? So for x[0]: int we could just not record a definition at all, and for x[0]: int = 1 we could record something that functions only as binding, not as declaration.

But for attributes we can't, since self.x: int is valid, and it's not until typecheck time that we can tell for sure whether self is a valid "self" target.

Although I'd be really tempted to just make this decision in semantic indexing based on "are we in a method and self is its first argument", so we can discard these declarations all up front and eliminate the new enum etc. If you end up in the venn diagram of "doing weird things with shadowing self" and "making invalid attribute annotations on non-self", then you just get some odd results and that's fine.

@charliermarsh

Copy link
Copy Markdown
Member Author

I agree that it's a pain, though I'm partial to getting it right as long as we can hide the complexity from callers...

@charliermarsh charliermarsh force-pushed the charlie/ignore-invalid-subscript-annotations branch 3 times, most recently from c411148 to 82bf02e Compare June 2, 2026 01:09

@oconnor663 oconnor663 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Edit: Just refreshed and saw @carljm's comment above after posting this. Looks like we had similar thoughts.]

Mostly nits about comment wording, so approving with comments. However, one high level thing I'd like to double check about the approach here: Are we sure we can't omit these bad-annotation definitions from the SemanticIndex entirely?

My guess is a lot of cases we can (e.g. x[0]: int is clearly never valid), but not all cases. IIUC the edge cases look like this:

class C:
    @classmethod
    def ok_method(x) -> None:
        x.attr: int = 1  # valid (`x` is "self" here)

    @staticmethod
    def bad_method(x) -> None:
        x.attr: int = 1  # invalid (staticmethods don't get a "self")

We could almost analyze those cases entirely statically, except that @classmethod and @staticmethod aren't keywords, and we have to resolve them during type inference (after semantic indexing).

I think that's the rub? But I'm curious if you agree. It feels like it would be nicer to discard bad definitions early if there was a way.

/// The inferred declaration produced by a syntactically indexed declaration.
#[derive(Debug, Clone, Copy, Eq, PartialEq, salsa::Update, get_size2::GetSize)]
pub(crate) enum InferredDeclaration<'db> {
/// A semantic declaration with an inferred declared type.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unless "semantic declaration" is a term of art that's new to me(?), I think it's clearer to just call this a "valid declaration".

pub(crate) enum InferredDeclaration<'db> {
/// A semantic declaration with an inferred declared type.
Declared(TypeAndQualifiers<'db>),
/// A syntactic declaration candidate that is not a valid semantic declaration.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IIUC "syntactic declaration" means "an annotation or annotated assignment that the SemanticIndex has a Definition for". Is that right? If so, using the word "syntactic" to refer to the SemanticIndex is pretty confusing, even though I see where you're coming from.

Also I think it's important to give a couple examples of invalid declarations here, so that the reader knows what we're talking about.


if let Some(value) = value {
// The value remains an ordinary assignment, but normal binding resolution
// would consult this rejected annotation as a declaration.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The grammar/logic of this comment seems like it almost makes sense but then not quite?

Comment thread crates/ty_python_semantic/src/types.rs Outdated

/// Infer the type of a declaration.
pub(crate) fn declaration_type<'db>(
/// Infer whether a syntactically indexed declaration is a semantic declaration.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The important thing this function is doing, in the overwhelming majority of valid cases, is still inferring the type of the declaration, correct? Changing the comment like this seems to obscure that.

/// The other 50% have less than 10 declarations. Because of that, use a
/// slice with linear search over a hash map.
declarations: Box<[(Definition<'db>, TypeAndQualifiers<'db>)]>,
declarations: Box<[(Definition<'db>, InferredDeclaration<'db>)]>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Possibly misguided q: Do we need to retain the invalid ones here? Or could we filter out just the valid ones and keep the tuple type unchanged?

@charliermarsh

Copy link
Copy Markdown
Member Author

👍 Yeah, I don't think it's possible. I also wanted to do that, but ultimately we do need semantic information to correctly determine where/when to discard because we can't robustly identify the implicit receiver rules.

@charliermarsh charliermarsh force-pushed the charlie/ignore-invalid-subscript-annotations branch from 82bf02e to b433318 Compare June 3, 2026 02:55
@charliermarsh charliermarsh force-pushed the charlie/ignore-invalid-subscript-annotations branch from b433318 to 73c2d3f Compare June 3, 2026 02:57
@charliermarsh charliermarsh enabled auto-merge (squash) June 3, 2026 11:30
@charliermarsh charliermarsh merged commit bc802a8 into main Jun 3, 2026
58 checks passed
@charliermarsh charliermarsh deleted the charlie/ignore-invalid-subscript-annotations branch June 3, 2026 11:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants