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

Code before:
            }
        };

        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.
        let not_implemented_type = UnionType::from_elements(
            db,
            [
                KnownClass::NotImplementedType.to_subclass_of(db),
                KnownClass::NotImplementedType.to_instance(db),
            ],
        );
        completions.retain(|item| {
            let Some(ty) = item.ty else { return true };
            !ty.is_assignable_to(db, not_implemented_type)
        });
    }

    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));

Review comment (refactor):
I think here you can simplify this a bit -- the big reason that folks mix up `NotImplemented` and `NotImplementedError` all the time is that they not only have very similar names, they're also both builtins. `type(NotImplemented)` doesn't really have the same issue, because it's not exposed as a builtin (you have to import the `NotImplementedType` class from the `types` module)

```suggestion
        // 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)
        });
```

this will of course require making yet _another_ `ty_python_semantic` API `pub` rather than `pub(crate)` 😆

https://github.com/astral-sh/ruff/blob/a57e29131125bf05db7379e90c7616eec32624fe/crates/ty_python_semantic/src/types.rs#L895-L897

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