Make semantic model aware of docstring (#9960)

## Summary

This PR introduces a new semantic model flag `DOCSTRING` which suggests
that the model is currently in a module / class / function docstring.
This is the first step in eliminating the docstring detection state
machine which is prone to bugs as stated in #7595.

## Test Plan

~TODO: Is there a way to add a test case for this?~

I tested this using the following code snippet and adding a print
statement in the `string_like` analyzer to print if we're currently in a
docstring or not.

<details><summary>Test code snippet:</summary>
<p>

```python
"Docstring" ", still a docstring"
"Not a docstring"


def foo():
    "Docstring"
    "Not a docstring"
    if foo:
        "Not a docstring"
        pass


class Foo:
    "Docstring"
    "Not a docstring"

    foo: int
    "Unofficial variable docstring"

    def method():
        "Docstring"
        "Not a docstring"
        pass


def bar():
    "Not a docstring".strip()


def baz():
    _something_else = 1
    """Not a docstring"""
```

</p>
</details>
This commit is contained in:
Dhruv Manilawala 2024-02-13 09:56:08 +05:30 committed by GitHub
parent 1ccd8354c1
commit 180920fdd9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 75 additions and 1 deletions

View file

@ -1489,6 +1489,11 @@ impl<'a> SemanticModel<'a> {
.intersects(SemanticModelFlags::TYPE_CHECKING_BLOCK)
}
/// Return `true` if the model is in a docstring.
pub const fn in_docstring(&self) -> bool {
self.flags.intersects(SemanticModelFlags::DOCSTRING)
}
/// Return `true` if the model has traversed past the "top-of-file" import boundary.
pub const fn seen_import_boundary(&self) -> bool {
self.flags.intersects(SemanticModelFlags::IMPORT_BOUNDARY)
@ -1853,6 +1858,26 @@ bitflags! {
/// ```
const COMPREHENSION_ASSIGNMENT = 1 << 19;
/// The model is in a module / class / function docstring.
///
/// For example, the model could be visiting either the module, class,
/// or function docstring in:
/// ```python
/// """Module docstring."""
///
///
/// class Foo:
/// """Class docstring."""
/// pass
///
///
/// def foo():
/// """Function docstring."""
/// pass
/// ```
const DOCSTRING = 1 << 20;
/// The context is in any type annotation.
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits();