Repository: astral-sh/ruff
File: crates/ty_python_semantic/src/place.rs (Rust)

Code before:
            )
        })
        .unwrap_or_default();

    if name == "platform"
        && file_to_module(db, scope.file(db))
            .is_some_and(|module| module.is_known(db, KnownModule::Sys))
    {
        match Program::get(db).python_platform(db) {
            crate::PythonPlatform::Identifier(platform) => {
                return Place::bound(Type::string_literal(db, platform.as_str())).into();
            }
            crate::PythonPlatform::All => {
                // Fall through to the looked up type
            }
        }
    } else if name == "__file__" {
        // We special-case `__file__` here because we know that for a successfully imported
        // Python module, that hasn't been explicitly overridden it is always a string,
        // even though typeshed says `str | None`.
        //
        // Cases where this isn't the case:
        //  - statically linked stdlib modules using C Extensions (e.g sys, itertools)
        //    we do not handle this and will infer these as `str`, this matches other major
        //    typecheckers
        //  - namespace packages, `__file__` is None at runtime but we type
        //    this as `Unknown` and emit a `unresolved-attribute`
        if let Some(module) = file_to_module(db, scope.file(db))
            && let Some(_f) = module.file(db)
        {
            match place.place {
                Place::Defined(defined_place) => match defined_place.definedness {
                    Definedness::AlwaysDefined => return place,
                    Definedness::PossiblyUndefined => {
                        let new_type = UnionType::from_elements(
                            db,
                            [defined_place.ty, KnownClass::Str.to_instance(db)],
                        );
                        let def_place = DefinedPlace::new(new_type)
                            .with_definedness(Definedness::AlwaysDefined)
                            .with_widening(defined_place.widening)
                            .with_origin(defined_place.origin);
                        return Place::Defined(def_place).into();
                    }
                },
                Place::Undefined => {
                    return Place::bound(KnownClass::Str.to_instance(db)).into();
                }
            }
        }
    }

Review comment (suggestion):
This is existing behaviour on `main` probably preferable `str | None` (or could we infer `None`?), let me know if I should add a TODO, Issue or try and tackle in this PR.

pyright is `str | None`, pyrefly/mypy is `str` and ty/zuban emit unknown-attr errors.

Code after:
            )
        })
        .unwrap_or_default();

    if name == "platform"
        && file_to_module(db, scope.file(db))
            .is_some_and(|module| module.is_known(db, KnownModule::Sys))
    {
        match Program::get(db).python_platform(db) {
            crate::PythonPlatform::Identifier(platform) => {
                return Place::bound(Type::string_literal(db, platform.as_str())).into();
            }
            crate::PythonPlatform::All => {
                // Fall through to the looked up type
            }
        }
    } else if name == "__file__"
        && let Some(module) = file_to_module(db, scope.file(db))
    {
        // We special-case `__file__` here because we know that for a successfully imported
        // non-namespace-package Python module, that hasn't been explicitly overridden it
        // is always a string, even though typeshed says `str | None`. For a namespace package,
        // meanwhile, it will always be `None`.
        //
        // Note that C-extension modules (stdlib examples include `sys`, `itertools`, etc.)
        //  may not have a `__file__` attribute at runtime at all, but that doesn't really
        // affect the *type* of the attribute, just the *boundness*. There's no way for us
        // to know right now whether a stub represents a C extension or not, so for now we
        // do not attempt to detect this; we just infer `str` still. This matches the
        // behaviour of other major type checkers.
        let default_type = if module.file(db).is_some() {
            KnownClass::Str.to_instance(db)
        } else {
            Type::none(db)
        };

        match place.place {
            Place::Defined(defined_place) => match defined_place.definedness {
                Definedness::AlwaysDefined => return place,
                Definedness::PossiblyUndefined => {
                    let new_type = UnionType::from_elements(db, [defined_place.ty, default_type]);
                    let def_place = DefinedPlace::new(new_type)
                        .with_definedness(Definedness::AlwaysDefined)
                        .with_widening(defined_place.widening)
                        .with_origin(defined_place.origin);
                    return Place::Defined(def_place).into();
                }
            },
            Place::Undefined => return Place::bound(default_type).into(),
        }
    }