diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index f91bd1490169e8..176865a04d5a47 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -2711,9 +2711,9 @@ We give special diagnostics for this common case too: import foo import baz -# error: [unresolved-attribute] +# error: [possibly-missing-attribute] reveal_type(foo.bar) # revealed: Unknown -# error: [unresolved-attribute] +# error: [possibly-missing-attribute] reveal_type(baz.bar) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/resources/mdtest/import/nonstandard_conventions.md b/crates/ty_python_semantic/resources/mdtest/import/nonstandard_conventions.md index 7dc28745155894..e17a026e3257b8 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/nonstandard_conventions.md +++ b/crates/ty_python_semantic/resources/mdtest/import/nonstandard_conventions.md @@ -60,7 +60,7 @@ Y: int = 47 import mypackage reveal_type(mypackage.imported.X) # revealed: int -# error: [unresolved-attribute] "Submodule `fails` may not be available" +# error: [possibly-missing-attribute] "Submodule `fails` may not be available" reveal_type(mypackage.fails.Y) # revealed: Unknown ``` @@ -90,7 +90,7 @@ Y: int = 47 import mypackage reveal_type(mypackage.imported.X) # revealed: int -# error: [unresolved-attribute] "Submodule `fails` may not be available" +# error: [possibly-missing-attribute] "Submodule `fails` may not be available" reveal_type(mypackage.fails.Y) # revealed: Unknown ``` @@ -125,7 +125,7 @@ Y: int = 47 import mypackage reveal_type(mypackage.imported.X) # revealed: int -# error: [unresolved-attribute] "Submodule `fails` may not be available" +# error: [possibly-missing-attribute] "Submodule `fails` may not be available" reveal_type(mypackage.fails.Y) # revealed: Unknown ``` @@ -155,7 +155,7 @@ Y: int = 47 import mypackage reveal_type(mypackage.imported.X) # revealed: int -# error: [unresolved-attribute] "Submodule `fails` may not be available" +# error: [possibly-missing-attribute] "Submodule `fails` may not be available" reveal_type(mypackage.fails.Y) # revealed: Unknown ``` @@ -184,7 +184,7 @@ X: int = 42 import mypackage # TODO: this could work and would be nice to have? -# error: [unresolved-attribute] "Submodule `imported` may not be available" +# error: [possibly-missing-attribute] "Submodule `imported` may not be available" reveal_type(mypackage.imported.X) # revealed: Unknown ``` @@ -208,7 +208,7 @@ X: int = 42 import mypackage # TODO: this could work and would be nice to have -# error: [unresolved-attribute] "Submodule `imported` may not be available" +# error: [possibly-missing-attribute] "Submodule `imported` may not be available" reveal_type(mypackage.imported.X) # revealed: Unknown ``` @@ -242,9 +242,9 @@ X: int = 42 import mypackage reveal_type(mypackage.submodule) # revealed: -# error: [unresolved-attribute] "Submodule `nested` may not be available" +# error: [possibly-missing-attribute] "Submodule `nested` may not be available" reveal_type(mypackage.submodule.nested) # revealed: Unknown -# error: [unresolved-attribute] "Submodule `nested` may not be available" +# error: [possibly-missing-attribute] "Submodule `nested` may not be available" reveal_type(mypackage.submodule.nested.X) # revealed: Unknown # error: [unresolved-attribute] "has no member `nested`" reveal_type(mypackage.nested) # revealed: Unknown @@ -280,9 +280,9 @@ import mypackage reveal_type(mypackage.submodule) # revealed: # TODO: this would be nice to support -# error: [unresolved-attribute] "Submodule `nested` may not be available" +# error: [possibly-missing-attribute] "Submodule `nested` may not be available" reveal_type(mypackage.submodule.nested) # revealed: Unknown -# error: [unresolved-attribute] "Submodule `nested` may not be available" +# error: [possibly-missing-attribute] "Submodule `nested` may not be available" reveal_type(mypackage.submodule.nested.X) # revealed: Unknown reveal_type(mypackage.nested) # revealed: reveal_type(mypackage.nested.X) # revealed: int @@ -318,9 +318,9 @@ X: int = 42 import mypackage reveal_type(mypackage.submodule) # revealed: -# error: [unresolved-attribute] "Submodule `nested` may not be available" +# error: [possibly-missing-attribute] "Submodule `nested` may not be available" reveal_type(mypackage.submodule.nested) # revealed: Unknown -# error: [unresolved-attribute] "Submodule `nested` may not be available" +# error: [possibly-missing-attribute] "Submodule `nested` may not be available" reveal_type(mypackage.submodule.nested.X) # revealed: Unknown # error: [unresolved-attribute] "has no member `nested`" reveal_type(mypackage.nested) # revealed: Unknown @@ -356,9 +356,9 @@ import mypackage reveal_type(mypackage.submodule) # revealed: # TODO: this would be nice to support -# error: [unresolved-attribute] "Submodule `nested` may not be available" +# error: [possibly-missing-attribute] "Submodule `nested` may not be available" reveal_type(mypackage.submodule.nested) # revealed: Unknown -# error: [unresolved-attribute] "Submodule `nested` may not be available" +# error: [possibly-missing-attribute] "Submodule `nested` may not be available" reveal_type(mypackage.submodule.nested.X) # revealed: Unknown reveal_type(mypackage.nested) # revealed: reveal_type(mypackage.nested.X) # revealed: int @@ -393,11 +393,11 @@ X: int = 42 ```py import mypackage -# error: [unresolved-attribute] "Submodule `submodule` may not be available" +# error: [possibly-missing-attribute] "Submodule `submodule` may not be available" reveal_type(mypackage.submodule) # revealed: Unknown -# error: [unresolved-attribute] "Submodule `submodule` may not be available" +# error: [possibly-missing-attribute] "Submodule `submodule` may not be available" reveal_type(mypackage.submodule.nested) # revealed: Unknown -# error: [unresolved-attribute] "Submodule `submodule` may not be available" +# error: [possibly-missing-attribute] "Submodule `submodule` may not be available" reveal_type(mypackage.submodule.nested.X) # revealed: Unknown ``` @@ -429,11 +429,11 @@ X: int = 42 import mypackage # TODO: this would be nice to support -# error: [unresolved-attribute] "Submodule `submodule` may not be available" +# error: [possibly-missing-attribute] "Submodule `submodule` may not be available" reveal_type(mypackage.submodule) # revealed: Unknown -# error: [unresolved-attribute] "Submodule `submodule` may not be available" +# error: [possibly-missing-attribute] "Submodule `submodule` may not be available" reveal_type(mypackage.submodule.nested) # revealed: Unknown -# error: [unresolved-attribute] "Submodule `submodule` may not be available" +# error: [possibly-missing-attribute] "Submodule `submodule` may not be available" reveal_type(mypackage.submodule.nested.X) # revealed: Unknown ``` @@ -460,7 +460,7 @@ X: int = 42 ```py import mypackage -# error: [unresolved-attribute] "Submodule `imported` may not be available" +# error: [possibly-missing-attribute] "Submodule `imported` may not be available" reveal_type(mypackage.imported.X) # revealed: Unknown # error: [unresolved-attribute] "has no member `imported_m`" reveal_type(mypackage.imported_m.X) # revealed: Unknown @@ -486,7 +486,7 @@ X: int = 42 import mypackage # TODO: this would be nice to support, as it works at runtime -# error: [unresolved-attribute] "Submodule `imported` may not be available" +# error: [possibly-missing-attribute] "Submodule `imported` may not be available" reveal_type(mypackage.imported.X) # revealed: Unknown reveal_type(mypackage.imported_m.X) # revealed: int ``` @@ -673,7 +673,7 @@ reveal_type(imported.X) # revealed: int # TODO: this would be nice to support, but it's dangerous with available_submodule_attributes # for details, see: https://github.com/astral-sh/ty/issues/1488 -# error: [unresolved-attribute] "Submodule `imported` may not be available" +# error: [possibly-missing-attribute] "Submodule `imported` may not be available" reveal_type(mypackage.imported.X) # revealed: Unknown ``` @@ -699,7 +699,7 @@ from mypackage import imported reveal_type(imported.X) # revealed: int # TODO: this would be nice to support, as it works at runtime -# error: [unresolved-attribute] "Submodule `imported` may not be available" +# error: [possibly-missing-attribute] "Submodule `imported` may not be available" reveal_type(mypackage.imported.X) # revealed: Unknown ``` @@ -737,7 +737,7 @@ from mypackage import imported reveal_type(imported.X) # revealed: int # error: [unresolved-attribute] "has no member `fails`" reveal_type(imported.fails.Y) # revealed: Unknown -# error: [unresolved-attribute] "Submodule `fails` may not be available" +# error: [possibly-missing-attribute] "Submodule `fails` may not be available" reveal_type(mypackage.fails.Y) # revealed: Unknown ``` @@ -770,7 +770,7 @@ from mypackage import imported reveal_type(imported.X) # revealed: int reveal_type(imported.fails.Y) # revealed: int -# error: [unresolved-attribute] "Submodule `fails`" +# error: [possibly-missing-attribute] "Submodule `fails`" reveal_type(mypackage.fails.Y) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/resources/mdtest/import/relative.md b/crates/ty_python_semantic/resources/mdtest/import/relative.md index fdfe4b16fc3f8b..09eb7e640f411c 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/relative.md +++ b/crates/ty_python_semantic/resources/mdtest/import/relative.md @@ -247,7 +247,7 @@ X: int = 42 from . import foo import package -# error: [unresolved-attribute] +# error: [possibly-missing-attribute] reveal_type(package.foo.X) # revealed: Unknown ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Unimported_submodule\342\200\246_(2b6da09ed380b2).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Unimported_submodule\342\200\246_(2b6da09ed380b2).snap" index 7ec8f5814e7958..b60183e3e93c97 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Unimported_submodule\342\200\246_(2b6da09ed380b2).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/attributes.md_-_Attributes_-_Unimported_submodule\342\200\246_(2b6da09ed380b2).snap" @@ -30,39 +30,39 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/attributes.md 1 | import foo 2 | import baz 3 | -4 | # error: [unresolved-attribute] +4 | # error: [possibly-missing-attribute] 5 | reveal_type(foo.bar) # revealed: Unknown -6 | # error: [unresolved-attribute] +6 | # error: [possibly-missing-attribute] 7 | reveal_type(baz.bar) # revealed: Unknown ``` # Diagnostics ``` -error[unresolved-attribute]: Submodule `bar` may not be available as an attribute on module `foo` +warning[possibly-missing-attribute]: Submodule `bar` may not be available as an attribute on module `foo` --> src/main.py:5:13 | -4 | # error: [unresolved-attribute] +4 | # error: [possibly-missing-attribute] 5 | reveal_type(foo.bar) # revealed: Unknown | ^^^^^^^ -6 | # error: [unresolved-attribute] +6 | # error: [possibly-missing-attribute] 7 | reveal_type(baz.bar) # revealed: Unknown | help: Consider explicitly importing `foo.bar` -info: rule `unresolved-attribute` is enabled by default +info: rule `possibly-missing-attribute` is enabled by default ``` ``` -error[unresolved-attribute]: Submodule `bar` may not be available as an attribute on module `baz` +warning[possibly-missing-attribute]: Submodule `bar` may not be available as an attribute on module `baz` --> src/main.py:7:13 | 5 | reveal_type(foo.bar) # revealed: Unknown -6 | # error: [unresolved-attribute] +6 | # error: [possibly-missing-attribute] 7 | reveal_type(baz.bar) # revealed: Unknown | ^^^^^^^ | help: Consider explicitly importing `baz.bar` -info: rule `unresolved-attribute` is enabled by default +info: rule `possibly-missing-attribute` is enabled by default ``` diff --git a/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md b/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md index 4ebb7c6bbd9668..71d53f44a36c13 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md +++ b/crates/ty_python_semantic/resources/mdtest/type_properties/is_equivalent_to.md @@ -628,7 +628,7 @@ import imported from module2 import imported as other_imported from ty_extensions import TypeOf, static_assert, is_equivalent_to -# error: [unresolved-attribute] +# error: [possibly-missing-attribute] reveal_type(imported.abc) # revealed: Unknown reveal_type(other_imported.abc) # revealed: diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 3451bb76388367..505c0758946358 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -62,9 +62,9 @@ use crate::types::diagnostic::{ INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD, INVALID_PARAMETER_DEFAULT, INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, NON_SUBSCRIPTABLE, - POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, SUBCLASS_OF_FINAL_CLASS, - UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT, - UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY, + POSSIBLY_MISSING_ATTRIBUTE, POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, + SUBCLASS_OF_FINAL_CLASS, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, + UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, USELESS_OVERLOAD_BODY, hint_if_stdlib_attribute_exists_on_other_versions, hint_if_stdlib_submodule_exists_on_other_versions, report_attempted_protocol_instantiation, report_bad_dunder_set_call, report_cannot_pop_required_field_on_typed_dict, @@ -9083,6 +9083,32 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { _ => false, }; + if let Type::ModuleLiteral(module) = value_type { + let module = module.module(db); + let module_name = module.name(db); + if module.kind(db).is_package() + && let Some(relative_submodule) = ModuleName::new(attr_name) + { + let mut maybe_submodule_name = module_name.clone(); + maybe_submodule_name.extend(&relative_submodule); + if resolve_module(db, &maybe_submodule_name).is_some() { + if let Some(builder) = self + .context + .report_lint(&POSSIBLY_MISSING_ATTRIBUTE, attribute) + { + let mut diag = builder.into_diagnostic(format_args!( + "Submodule `{attr_name}` may not be available as an attribute \ + on module `{module_name}`" + )); + diag.help(format_args!( + "Consider explicitly importing `{maybe_submodule_name}`" + )); + } + return fallback(); + } + } + } + let Some(builder) = self.context.report_lint(&UNRESOLVED_ATTRIBUTE, attribute) else { return fallback(); @@ -9098,30 +9124,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } let diagnostic = match value_type { - Type::ModuleLiteral(module) => { - let module = module.module(db); - let module_name = module.name(db); - if module.kind(db).is_package() - && let Some(relative_submodule) = ModuleName::new(attr_name) - { - let mut maybe_submodule_name = module_name.clone(); - maybe_submodule_name.extend(&relative_submodule); - if resolve_module(db, &maybe_submodule_name).is_some() { - let mut diag = builder.into_diagnostic(format_args!( - "Submodule `{attr_name}` may not be available as an attribute \ - on module `{module_name}`" - )); - diag.help(format_args!( - "Consider explicitly importing `{maybe_submodule_name}`" - )); - return fallback(); - } - } - - builder.into_diagnostic(format_args!( - "Module `{module_name}` has no member `{attr_name}`", - )) - } + Type::ModuleLiteral(module) => builder.into_diagnostic(format_args!( + "Module `{module_name}` has no member `{attr_name}`", + module_name = module.module(db).name(db), + )), Type::ClassLiteral(class) => builder.into_diagnostic(format_args!( "Class `{}` has no attribute `{attr_name}`", class.name(db),