Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/ty_python_semantic/resources/mdtest/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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
```

Expand All @@ -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
```

Expand Down Expand Up @@ -242,9 +242,9 @@ X: int = 42
import mypackage

reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
# 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
Expand Down Expand Up @@ -280,9 +280,9 @@ import mypackage

reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
# 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: <module 'mypackage.submodule.nested'>
reveal_type(mypackage.nested.X) # revealed: int
Expand Down Expand Up @@ -318,9 +318,9 @@ X: int = 42
import mypackage

reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
# 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
Expand Down Expand Up @@ -356,9 +356,9 @@ import mypackage

reveal_type(mypackage.submodule) # revealed: <module 'mypackage.submodule'>
# 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: <module 'mypackage.submodule.nested'>
reveal_type(mypackage.nested.X) # revealed: int
Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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
```

Expand All @@ -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
Expand All @@ -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
```
Expand Down Expand Up @@ -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
```

Expand All @@ -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
```

Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Original file line number Diff line number Diff line change
Expand Up @@ -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: <module 'imported.abc'>
Expand Down
60 changes: 33 additions & 27 deletions crates/ty_python_semantic/src/types/infer/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand All @@ -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),
Expand Down
Loading