Repository: astral-sh/ruff
File: crates/ty_ide/src/completion.rs (Rust)

Code before:
        [.., last] if typed.is_none() => is_definition_keyword(last),
        _ => false,
    } {
        return true;
    }
    // Analyze the AST if token matching is insufficient
    // to determine if we're inside a name definition.
    is_in_variable_binding(parsed, offset, typed)
}

/// Returns true when the cursor sits on a binding statement.
/// E.g. naming a parameter, type parameter, or `for` <name>).
fn is_in_variable_binding(parsed: &ParsedModuleRef, offset: TextSize, typed: Option<&str>) -> bool {
    let range = if let Some(typed) = typed {
        let tokens = tokens_start_before(parsed.tokens(), offset);
        // `typed` contains the entire token even if the cursor sits in the
        // middle of it (pa<CURSOR>ram). To correctly suggest completions
        // based on the cursor's current position, we must only consider
        // the characters in the token that are left of the cursor.
        // E.g. we should only count "pa" if the state is "pa<CUSROR>ram".
        let typed_len = typed
            .text_len()
            .min(tokens.last().map_or(typed.text_len(), |token| {
                offset.saturating_sub(token.start())
            }));
        let start = offset.saturating_sub(typed_len);
        TextRange::new(start, offset)
    } else {
        TextRange::empty(offset)
    };

    let covering = covering_node(parsed.syntax().into(), range);
    covering.ancestors().any(|node| match node {
        ast::AnyNodeRef::Parameter(param) => param.name.range.contains_range(range),
        ast::AnyNodeRef::TypeParamTypeVar(type_param) => {
            type_param.name.range.contains_range(range)
        }
        ast::AnyNodeRef::StmtFor(stmt_for) => stmt_for.target.range().contains_range(range),
        // The AST does not produce `ast::AnyNodeRef::Parameter` nodes for keywords
        // or otherwise invalid syntax. Rather they are captured in a
        // `ast::AnyNodeRef::Parameters` node as "empty space". To ensure
        // we still suppress suggestions even when the syntax is technically
        // invalid we extract the token under the cursor and check if it makes
        // up that "empty space" inside the Parameters Node. If it does, we know
        // that we are still binding variables, just that the current state is
        // syntatically invalid. Hence we suppress autocomplete suggestons
        // also in those cases.
        ast::AnyNodeRef::Parameters(params) => {
            if !params.range.contains_range(range) {
                return false;
            }

Review comment (bug):
This is technically not relevant to this PR but I realised from https://github.com/astral-sh/ruff/pull/21570 that this calculation was logically incorrect. `typed.textlen()` is the length of the entire token, even if the cursor is currently in the middle of the token which is not quite what we want.
 
 I have not managed to write a test based on suggestions that highlights this bug, but by calling `is_in_variable_binding` directly with different inputs it became obvious that it was previously incorrect.

Code after:
) -> bool {
    is_in_comment(tokens)
        || is_in_string(tokens)
        || is_in_definition_place(db, file, parsed, offset, tokens, typed)
}

/// Whether the last token is within a comment or not.
fn is_in_comment(tokens: &[Token]) -> bool {
    tokens.last().is_some_and(|t| t.kind().is_comment())
}

/// Whether the last token is positioned within a string token (regular, f-string, t-string, etc).
///
/// Note that this will return `false` when the last token is positioned within an
/// interpolation block in an f-string or a t-string.
fn is_in_string(tokens: &[Token]) -> bool {
    tokens.last().is_some_and(|t| {
        matches!(
            t.kind(),
            TokenKind::String | TokenKind::FStringMiddle | TokenKind::TStringMiddle
        )
    })
}

/// Returns true when the tokens indicate that the definition of a new
/// name is being introduced at the end.
fn is_in_definition_place(
    db: &dyn Db,
    file: File,
    parsed: &ParsedModuleRef,
    offset: TextSize,
    tokens: &[Token],
    typed: Option<&str>,
) -> bool {
    fn is_definition_token(token: &Token) -> bool {
        matches!(
            token.kind(),
            TokenKind::Def | TokenKind::Class | TokenKind::Type | TokenKind::As | TokenKind::For
        )
    }

    let is_definition_keyword = |token: &Token| {
        if is_definition_token(token) {
            true
        } else if token.kind() == TokenKind::Name {
            let source = source_text(db, file);
            &source[token.range()] == "type"
        } else {
            false
        }
    };