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

Code before:
        ("None", Type::none(db)),
        ("True", Type::BooleanLiteral(true)),
        ("False", Type::BooleanLiteral(false)),
    ];
    for (name, ty) in keyword_values {
        completions.try_add(Completion::value_keyword(name, ty));
    }

    // Note that we specifically omit the `type` keyword here, since
    // it will be included via `builtins`. This does make its sorting
    // priority slighty different than other keywords, but it's not
    // clear (to me, AG) if that's an issue or not. Since the builtin
    // completion has an actual type associated with it, we use that
    // instead of a keyword completion.
    let keywords = [
        "and", "as", "assert", "async", "await", "break", "class", "continue", "def", "del",
        "elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "is",
        "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try", "while", "with",
        "yield", "case", "match",
    ];
    for name in keywords {
        completions.try_add(Completion::keyword(name));
    }
}

/// Adds completions not in scope.
///
/// `scoped` should be information about the identified scope
/// in which the cursor is currently placed.
///
/// The completions returned will auto-insert import statements
/// when selected into `File`.
fn add_unimported_completions<'db>(
    db: &'db dyn Db,
    file: File,
    parsed: &ParsedModuleRef,
    scoped: ScopedTarget<'_>,
    completions: &mut Completions<'db>,
) {
    // This is redundant since `all_symbols` will also bail
    // when the query can match everything. But we bail here
    // to avoid building an `Importer` and other plausibly
    // costly work when we know we won't use it.
    if completions.query.will_match_everything() {
        return;
    }

    let source = source_text(db, file);
    let stylist = Stylist::from_tokens(parsed.tokens(), source.as_str());
    let importer = Importer::new(db, &stylist, file, source.as_str(), parsed);
    let members = importer.members_in_scope_at(scoped.node, scoped.node.start());

Review comment (suggestion):
I suppose you can also raise instances of exceptions. So if a user has something like

```py
exception_to_raise = NotImplementedError("foooooooooooooooooooooooooooo")
raise exception_to<CURSOR>
```

we should probably suggest `exception_to_raise` there. That implies that this should be

```suggestion
    if is_raising_exception(tokens) {
        let raisable_type = UnionType::from_elements(
            db,
            [KnownClass::BaseException.to_subclass_of(db), KnownClass::BaseException.to_instance(db)]
        );
        completions.retain(|c| {
            let Some(ty) = c.ty else { return true };
            ty.is_assignable_to(db, raisable_type)
        });
    }
```

basically, the same as this bit in the type-inference builder:

https://github.com/astral-sh/ruff/blob/3410041b4ca8226641b72e2f947ebd612dd2ea28/crates/ty_python_semantic/src/types/infer/builder.rs#L6003-L6025

Code after:
        };

        completions.extend(semantic_completions);
        if scoped.is_some() {
            add_keyword_completions(db, &mut completions);
        }
        if settings.auto_import {
            if let Some(scoped) = scoped {
                add_unimported_completions(db, file, &parsed, scoped, &mut completions);
            }
        }
    }

    if is_raising_exception(tokens) {
        completions.tag_raisable();

        // As a special case, and because it's a common footgun, we
        // specifically disallow `NotImplemented` in this context.
        // `NotImplementedError` should be used instead. So if we can
        // definitively detect `NotImplemented`, then we can safely
        // omit it from suggestions.
        completions.retain(|item| {
            let Some(ty) = item.ty else { return true };
            !ty.is_notimplemented(db)
        });
    }

    completions.into_completions()
}

/// Adds completions derived from keywords.
///
/// This should generally only be used when offering "scoped" completions.
/// This will include keywords corresponding to Python values (like `None`)
/// and general language keywords (like `raise`).
fn add_keyword_completions<'db>(db: &'db dyn Db, completions: &mut Completions<'db>) {
    let keyword_values = [
        ("None", Type::none(db)),
        ("True", Type::BooleanLiteral(true)),
        ("False", Type::BooleanLiteral(false)),
    ];
    for (name, ty) in keyword_values {
        completions.try_add(Completion::value_keyword(name, ty));
    }

    // Note that we specifically omit the `type` keyword here, since
    // it will be included via `builtins`. This does make its sorting
    // priority slighty different than other keywords, but it's not
    // clear (to me, AG) if that's an issue or not. Since the builtin
    // completion has an actual type associated with it, we use that
    // instead of a keyword completion.