mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-10-31 03:55:09 +00:00 
			
		
		
		
	[ty] display variance on hover over type variables (#20900)
This commit is contained in:
		
							parent
							
								
									0520d11a66
								
							
						
					
					
						commit
						a7c38eb122
					
				
					 6 changed files with 528 additions and 48 deletions
				
			
		|  | @ -284,13 +284,13 @@ impl GotoTarget<'_> { | ||||||
|             // When asking the type of a callable, usually you want the callable itself?
 |             // When asking the type of a callable, usually you want the callable itself?
 | ||||||
|             // (i.e. the type of `MyClass` in `MyClass()` is `<class MyClass>` and not `() -> MyClass`)
 |             // (i.e. the type of `MyClass` in `MyClass()` is `<class MyClass>` and not `() -> MyClass`)
 | ||||||
|             GotoTarget::Call { callable, .. } => callable.inferred_type(model), |             GotoTarget::Call { callable, .. } => callable.inferred_type(model), | ||||||
|  |             GotoTarget::TypeParamTypeVarName(typevar) => typevar.inferred_type(model), | ||||||
|             // TODO: Support identifier targets
 |             // TODO: Support identifier targets
 | ||||||
|             GotoTarget::PatternMatchRest(_) |             GotoTarget::PatternMatchRest(_) | ||||||
|             | GotoTarget::PatternKeywordArgument(_) |             | GotoTarget::PatternKeywordArgument(_) | ||||||
|             | GotoTarget::PatternMatchStarName(_) |             | GotoTarget::PatternMatchStarName(_) | ||||||
|             | GotoTarget::PatternMatchAsName(_) |             | GotoTarget::PatternMatchAsName(_) | ||||||
|             | GotoTarget::ImportModuleComponent { .. } |             | GotoTarget::ImportModuleComponent { .. } | ||||||
|             | GotoTarget::TypeParamTypeVarName(_) |  | ||||||
|             | GotoTarget::TypeParamParamSpecName(_) |             | GotoTarget::TypeParamParamSpecName(_) | ||||||
|             | GotoTarget::TypeParamTypeVarTupleName(_) |             | GotoTarget::TypeParamTypeVarTupleName(_) | ||||||
|             | GotoTarget::NonLocal { .. } |             | GotoTarget::NonLocal { .. } | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ use ruff_db::parsed::parsed_module; | ||||||
| use ruff_text_size::{Ranged, TextSize}; | use ruff_text_size::{Ranged, TextSize}; | ||||||
| use std::fmt; | use std::fmt; | ||||||
| use std::fmt::Formatter; | use std::fmt::Formatter; | ||||||
| use ty_python_semantic::types::Type; | use ty_python_semantic::types::{KnownInstanceType, Type, TypeVarVariance}; | ||||||
| use ty_python_semantic::{DisplaySettings, SemanticModel}; | use ty_python_semantic::{DisplaySettings, SemanticModel}; | ||||||
| 
 | 
 | ||||||
| pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Hover<'_>>> { | pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Hover<'_>>> { | ||||||
|  | @ -20,7 +20,7 @@ pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Ho | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     let model = SemanticModel::new(db, file); |     let model = SemanticModel::new(db, file); | ||||||
|     let ty = goto_target.inferred_type(&model).map(HoverContent::Type); |     let ty = goto_target.inferred_type(&model); | ||||||
|     let docs = goto_target |     let docs = goto_target | ||||||
|         .get_definition_targets( |         .get_definition_targets( | ||||||
|             file, |             file, | ||||||
|  | @ -30,13 +30,20 @@ pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Ho | ||||||
|         .and_then(|definitions| definitions.docstring(db)) |         .and_then(|definitions| definitions.docstring(db)) | ||||||
|         .map(HoverContent::Docstring); |         .map(HoverContent::Docstring); | ||||||
| 
 | 
 | ||||||
|     if let Some(HoverContent::Type(ty)) = ty { |  | ||||||
|         tracing::debug!("Inferred type of covering node is {}", ty.display(db)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // TODO: Render the symbol's signature instead of just its type.
 |     // TODO: Render the symbol's signature instead of just its type.
 | ||||||
|     let mut contents = Vec::new(); |     let mut contents = Vec::new(); | ||||||
|     contents.extend(ty); |     if let Some(ty) = ty { | ||||||
|  |         tracing::debug!("Inferred type of covering node is {}", ty.display(db)); | ||||||
|  |         contents.push(match ty { | ||||||
|  |             Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => typevar | ||||||
|  |                 .bind_pep695(db) | ||||||
|  |                 .map_or(HoverContent::Type(ty, None), |typevar| { | ||||||
|  |                     HoverContent::Type(Type::TypeVar(typevar), Some(typevar.variance(db))) | ||||||
|  |                 }), | ||||||
|  |             Type::TypeVar(typevar) => HoverContent::Type(ty, Some(typevar.variance(db))), | ||||||
|  |             _ => HoverContent::Type(ty, None), | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|     contents.extend(docs); |     contents.extend(docs); | ||||||
| 
 | 
 | ||||||
|     if contents.is_empty() { |     if contents.is_empty() { | ||||||
|  | @ -110,7 +117,7 @@ impl fmt::Display for DisplayHover<'_> { | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone, Eq, PartialEq)] | #[derive(Debug, Clone, Eq, PartialEq)] | ||||||
| pub enum HoverContent<'db> { | pub enum HoverContent<'db> { | ||||||
|     Type(Type<'db>), |     Type(Type<'db>, Option<TypeVarVariance>), | ||||||
|     Docstring(Docstring), |     Docstring(Docstring), | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -133,13 +140,24 @@ pub(crate) struct DisplayHoverContent<'a, 'db> { | ||||||
| impl fmt::Display for DisplayHoverContent<'_, '_> { | impl fmt::Display for DisplayHoverContent<'_, '_> { | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||
|         match self.content { |         match self.content { | ||||||
|             HoverContent::Type(ty) => self |             HoverContent::Type(ty, variance) => { | ||||||
|                 .kind |                 let variance = match variance { | ||||||
|  |                     Some(TypeVarVariance::Covariant) => " (covariant)", | ||||||
|  |                     Some(TypeVarVariance::Contravariant) => " (contravariant)", | ||||||
|  |                     Some(TypeVarVariance::Invariant) => " (invariant)", | ||||||
|  |                     Some(TypeVarVariance::Bivariant) => " (bivariant)", | ||||||
|  |                     None => "", | ||||||
|  |                 }; | ||||||
|  |                 self.kind | ||||||
|                     .fenced_code_block( |                     .fenced_code_block( | ||||||
|                     ty.display_with(self.db, DisplaySettings::default().multiline()), |                         format!( | ||||||
|  |                             "{}{variance}", | ||||||
|  |                             ty.display_with(self.db, DisplaySettings::default().multiline()) | ||||||
|  |                         ), | ||||||
|                         "python", |                         "python", | ||||||
|                     ) |                     ) | ||||||
|                 .fmt(f), |                     .fmt(f) | ||||||
|  |             } | ||||||
|             HoverContent::Docstring(docstring) => docstring.render(self.kind).fmt(f), |             HoverContent::Docstring(docstring) => docstring.render(self.kind).fmt(f), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -1590,10 +1608,10 @@ def ab(a: int, *, c: int): | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         assert_snapshot!(test.hover(), @r" |         assert_snapshot!(test.hover(), @r" | ||||||
|         T@Alias |         T@Alias (invariant) | ||||||
|         --------------------------------------------- |         --------------------------------------------- | ||||||
|         ```python |         ```python | ||||||
|         T@Alias |         T@Alias (invariant) | ||||||
|         ``` |         ``` | ||||||
|         --------------------------------------------- |         --------------------------------------------- | ||||||
|         info[hover]: Hovered content is |         info[hover]: Hovered content is | ||||||
|  | @ -2058,6 +2076,444 @@ def ab(a: int, *, c: int): | ||||||
|         assert_snapshot!(test.hover(), @"Hover provided no content"); |         assert_snapshot!(test.hover(), @"Hover provided no content"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn hover_class_typevar_variance() { | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         class Covariant[T<CURSOR>]: | ||||||
|  |             def get(self) -> T: | ||||||
|  |                 raise ValueError | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@Covariant (covariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@Covariant (covariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:2:17 | ||||||
|  |           | | ||||||
|  |         2 | class Covariant[T]: | ||||||
|  |           |                 ^- Cursor offset | ||||||
|  |           |                 | | ||||||
|  |           |                 source | ||||||
|  |         3 |     def get(self) -> T: | ||||||
|  |         4 |         raise ValueError | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  | 
 | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         class Covariant[T]: | ||||||
|  |             def get(self) -> T<CURSOR>: | ||||||
|  |                 raise ValueError | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@Covariant (covariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@Covariant (covariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:3:22 | ||||||
|  |           | | ||||||
|  |         2 | class Covariant[T]: | ||||||
|  |         3 |     def get(self) -> T: | ||||||
|  |           |                      ^- Cursor offset | ||||||
|  |           |                      | | ||||||
|  |           |                      source | ||||||
|  |         4 |         raise ValueError | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  | 
 | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         class Contravariant[T<CURSOR>]: | ||||||
|  |             def set(self, x: T): | ||||||
|  |                 pass | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@Contravariant (contravariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@Contravariant (contravariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:2:21 | ||||||
|  |           | | ||||||
|  |         2 | class Contravariant[T]: | ||||||
|  |           |                     ^- Cursor offset | ||||||
|  |           |                     | | ||||||
|  |           |                     source | ||||||
|  |         3 |     def set(self, x: T): | ||||||
|  |         4 |         pass | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  | 
 | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         class Contravariant[T]: | ||||||
|  |             def set(self, x: T<CURSOR>): | ||||||
|  |                 pass | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@Contravariant (contravariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@Contravariant (contravariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:3:22 | ||||||
|  |           | | ||||||
|  |         2 | class Contravariant[T]: | ||||||
|  |         3 |     def set(self, x: T): | ||||||
|  |           |                      ^- Cursor offset | ||||||
|  |           |                      | | ||||||
|  |           |                      source | ||||||
|  |         4 |         pass | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn hover_function_typevar_variance() { | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         def covariant[T<CURSOR>]() -> T: | ||||||
|  |             raise ValueError | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@covariant (covariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@covariant (covariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:2:15 | ||||||
|  |           | | ||||||
|  |         2 | def covariant[T]() -> T: | ||||||
|  |           |               ^- Cursor offset | ||||||
|  |           |               | | ||||||
|  |           |               source | ||||||
|  |         3 |     raise ValueError | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  | 
 | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         def covariant[T]() -> T<CURSOR>: | ||||||
|  |             raise ValueError | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@covariant (covariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@covariant (covariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:2:23 | ||||||
|  |           | | ||||||
|  |         2 | def covariant[T]() -> T: | ||||||
|  |           |                       ^- Cursor offset | ||||||
|  |           |                       | | ||||||
|  |           |                       source | ||||||
|  |         3 |     raise ValueError | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  | 
 | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         def contravariant[T<CURSOR>](x: T): | ||||||
|  |             pass | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@contravariant (contravariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@contravariant (contravariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:2:19 | ||||||
|  |           | | ||||||
|  |         2 | def contravariant[T](x: T): | ||||||
|  |           |                   ^- Cursor offset | ||||||
|  |           |                   | | ||||||
|  |           |                   source | ||||||
|  |         3 |     pass | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  | 
 | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         def contravariant[T](x: T<CURSOR>): | ||||||
|  |             pass | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@contravariant (contravariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@contravariant (contravariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:2:25 | ||||||
|  |           | | ||||||
|  |         2 | def contravariant[T](x: T): | ||||||
|  |           |                         ^- Cursor offset | ||||||
|  |           |                         | | ||||||
|  |           |                         source | ||||||
|  |         3 |     pass | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn hover_type_alias_typevar_variance() { | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         type List[T<CURSOR>] = list[T] | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@List (invariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@List (invariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:2:11 | ||||||
|  |           | | ||||||
|  |         2 | type List[T] = list[T] | ||||||
|  |           |           ^- Cursor offset | ||||||
|  |           |           | | ||||||
|  |           |           source | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  | 
 | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         type List[T] = list[T<CURSOR>] | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@List (invariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@List (invariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:2:21 | ||||||
|  |           | | ||||||
|  |         2 | type List[T] = list[T] | ||||||
|  |           |                     ^- Cursor offset | ||||||
|  |           |                     | | ||||||
|  |           |                     source | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  | 
 | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         type Tuple[T<CURSOR>] = tuple[T] | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@Tuple (covariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@Tuple (covariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:2:12 | ||||||
|  |           | | ||||||
|  |         2 | type Tuple[T] = tuple[T] | ||||||
|  |           |            ^- Cursor offset | ||||||
|  |           |            | | ||||||
|  |           |            source | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  | 
 | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         type Tuple[T] = tuple[T<CURSOR>] | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@Tuple (covariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@Tuple (covariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:2:23 | ||||||
|  |           | | ||||||
|  |         2 | type Tuple[T] = tuple[T] | ||||||
|  |           |                       ^- Cursor offset | ||||||
|  |           |                       | | ||||||
|  |           |                       source | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn hover_legacy_typevar_variance() { | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         from typing import TypeVar | ||||||
|  | 
 | ||||||
|  |         T<CURSOR> = TypeVar('T', covariant=True) | ||||||
|  | 
 | ||||||
|  |         def covariant() -> T: | ||||||
|  |             raise ValueError | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         typing.TypeVar | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         typing.TypeVar | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:4:1 | ||||||
|  |           | | ||||||
|  |         2 | from typing import TypeVar | ||||||
|  |         3 | | ||||||
|  |         4 | T = TypeVar('T', covariant=True) | ||||||
|  |           | ^- Cursor offset | ||||||
|  |           | | | ||||||
|  |           | source | ||||||
|  |         5 | | ||||||
|  |         6 | def covariant() -> T: | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  | 
 | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         from typing import TypeVar | ||||||
|  | 
 | ||||||
|  |         T = TypeVar('T', covariant=True) | ||||||
|  | 
 | ||||||
|  |         def covariant() -> T<CURSOR>: | ||||||
|  |             raise ValueError | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@covariant (covariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@covariant (covariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:6:20 | ||||||
|  |           | | ||||||
|  |         4 | T = TypeVar('T', covariant=True) | ||||||
|  |         5 | | ||||||
|  |         6 | def covariant() -> T: | ||||||
|  |           |                    ^- Cursor offset | ||||||
|  |           |                    | | ||||||
|  |           |                    source | ||||||
|  |         7 |     raise ValueError | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  | 
 | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         from typing import TypeVar | ||||||
|  | 
 | ||||||
|  |         T<CURSOR> = TypeVar('T', contravariant=True) | ||||||
|  | 
 | ||||||
|  |         def contravariant(x: T): | ||||||
|  |             pass | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         typing.TypeVar | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         typing.TypeVar | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:4:1 | ||||||
|  |           | | ||||||
|  |         2 | from typing import TypeVar | ||||||
|  |         3 | | ||||||
|  |         4 | T = TypeVar('T', contravariant=True) | ||||||
|  |           | ^- Cursor offset | ||||||
|  |           | | | ||||||
|  |           | source | ||||||
|  |         5 | | ||||||
|  |         6 | def contravariant(x: T): | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  | 
 | ||||||
|  |         let test = cursor_test( | ||||||
|  |             r#" | ||||||
|  |         from typing import TypeVar | ||||||
|  | 
 | ||||||
|  |         T = TypeVar('T', contravariant=True) | ||||||
|  | 
 | ||||||
|  |         def contravariant(x: T<CURSOR>): | ||||||
|  |             pass | ||||||
|  |         "#,
 | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         assert_snapshot!(test.hover(), @r" | ||||||
|  |         T@contravariant (contravariant) | ||||||
|  |         --------------------------------------------- | ||||||
|  |         ```python | ||||||
|  |         T@contravariant (contravariant) | ||||||
|  |         ``` | ||||||
|  |         --------------------------------------------- | ||||||
|  |         info[hover]: Hovered content is | ||||||
|  |          --> main.py:6:22 | ||||||
|  |           | | ||||||
|  |         4 | T = TypeVar('T', contravariant=True) | ||||||
|  |         5 | | ||||||
|  |         6 | def contravariant(x: T): | ||||||
|  |           |                      ^- Cursor offset | ||||||
|  |           |                      | | ||||||
|  |           |                      source | ||||||
|  |         7 |     pass | ||||||
|  |           | | ||||||
|  |         ");
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     impl CursorTest { |     impl CursorTest { | ||||||
|         fn hover(&self) -> String { |         fn hover(&self) -> String { | ||||||
|             use std::fmt::Write; |             use std::fmt::Write; | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ use crate::{ | ||||||
|     semantic_index::{ |     semantic_index::{ | ||||||
|         SemanticIndex, reachability_constraints::ScopedReachabilityConstraintId, semantic_index, |         SemanticIndex, reachability_constraints::ScopedReachabilityConstraintId, semantic_index, | ||||||
|     }, |     }, | ||||||
|  |     types::{GenericContext, binding_type, infer_definition_types}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// A cross-module identifier of a scope that can be used as a salsa query parameter.
 | /// A cross-module identifier of a scope that can be used as a salsa query parameter.
 | ||||||
|  | @ -430,6 +431,38 @@ impl NodeWithScopeKind { | ||||||
|     pub(crate) fn expect_type_alias(&self) -> &AstNodeRef<ast::StmtTypeAlias> { |     pub(crate) fn expect_type_alias(&self) -> &AstNodeRef<ast::StmtTypeAlias> { | ||||||
|         self.as_type_alias().expect("expected type alias") |         self.as_type_alias().expect("expected type alias") | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     pub(crate) fn generic_context<'db>( | ||||||
|  |         &self, | ||||||
|  |         db: &'db dyn Db, | ||||||
|  |         index: &SemanticIndex<'db>, | ||||||
|  |     ) -> Option<GenericContext<'db>> { | ||||||
|  |         match self { | ||||||
|  |             NodeWithScopeKind::Class(class) => { | ||||||
|  |                 let definition = index.expect_single_definition(class); | ||||||
|  |                 binding_type(db, definition) | ||||||
|  |                     .as_class_literal()? | ||||||
|  |                     .generic_context(db) | ||||||
|  |             } | ||||||
|  |             NodeWithScopeKind::Function(function) => { | ||||||
|  |                 let definition = index.expect_single_definition(function); | ||||||
|  |                 infer_definition_types(db, definition) | ||||||
|  |                     .undecorated_type() | ||||||
|  |                     .expect("function should have undecorated type") | ||||||
|  |                     .as_function_literal()? | ||||||
|  |                     .last_definition_signature(db) | ||||||
|  |                     .generic_context | ||||||
|  |             } | ||||||
|  |             NodeWithScopeKind::TypeAlias(type_alias) => { | ||||||
|  |                 let definition = index.expect_single_definition(type_alias); | ||||||
|  |                 binding_type(db, definition) | ||||||
|  |                     .as_type_alias()? | ||||||
|  |                     .as_pep_695_type_alias()? | ||||||
|  |                     .generic_context(db) | ||||||
|  |             } | ||||||
|  |             _ => None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)] | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)] | ||||||
|  |  | ||||||
|  | @ -479,6 +479,7 @@ impl_binding_has_ty_def!(ast::StmtClassDef); | ||||||
| impl_binding_has_ty_def!(ast::Parameter); | impl_binding_has_ty_def!(ast::Parameter); | ||||||
| impl_binding_has_ty_def!(ast::ParameterWithDefault); | impl_binding_has_ty_def!(ast::ParameterWithDefault); | ||||||
| impl_binding_has_ty_def!(ast::ExceptHandlerExceptHandler); | impl_binding_has_ty_def!(ast::ExceptHandlerExceptHandler); | ||||||
|  | impl_binding_has_ty_def!(ast::TypeParamTypeVar); | ||||||
| 
 | 
 | ||||||
| impl HasType for ast::Alias { | impl HasType for ast::Alias { | ||||||
|     fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> { |     fn inferred_type<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> { | ||||||
|  |  | ||||||
|  | @ -67,7 +67,8 @@ pub(crate) use crate::types::narrow::infer_narrowing_constraint; | ||||||
| use crate::types::signatures::{ParameterForm, walk_signature}; | use crate::types::signatures::{ParameterForm, walk_signature}; | ||||||
| use crate::types::tuple::{TupleSpec, TupleSpecBuilder}; | use crate::types::tuple::{TupleSpec, TupleSpecBuilder}; | ||||||
| pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type}; | pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type}; | ||||||
| use crate::types::variance::{TypeVarVariance, VarianceInferable}; | pub use crate::types::variance::TypeVarVariance; | ||||||
|  | use crate::types::variance::VarianceInferable; | ||||||
| use crate::types::visitor::any_over_type; | use crate::types::visitor::any_over_type; | ||||||
| use crate::unpack::EvaluationMode; | use crate::unpack::EvaluationMode; | ||||||
| use crate::{Db, FxOrderSet, Module, Program}; | use crate::{Db, FxOrderSet, Module, Program}; | ||||||
|  | @ -8394,6 +8395,21 @@ impl<'db> TypeVarInstance<'db> { | ||||||
|             _ => None, |             _ => None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     pub fn bind_pep695(self, db: &'db dyn Db) -> Option<BoundTypeVarInstance<'db>> { | ||||||
|  |         if self.identity(db).kind(db) != TypeVarKind::Pep695 { | ||||||
|  |             return None; | ||||||
|  |         } | ||||||
|  |         let typevar_definition = self.definition(db)?; | ||||||
|  |         let index = semantic_index(db, typevar_definition.file(db)); | ||||||
|  |         let (_, child) = index | ||||||
|  |             .child_scopes(typevar_definition.file_scope(db)) | ||||||
|  |             .next()?; | ||||||
|  |         child | ||||||
|  |             .node() | ||||||
|  |             .generic_context(db, index)? | ||||||
|  |             .binds_typevar(db, self) | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[allow(clippy::ref_option)] | #[allow(clippy::ref_option)] | ||||||
|  | @ -8544,7 +8560,7 @@ impl<'db> BoundTypeVarInstance<'db> { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub(crate) fn variance(self, db: &'db dyn Db) -> TypeVarVariance { |     pub fn variance(self, db: &'db dyn Db) -> TypeVarVariance { | ||||||
|         self.variance_with_polarity(db, TypeVarVariance::Covariant) |         self.variance_with_polarity(db, TypeVarVariance::Covariant) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -11,7 +11,6 @@ use crate::semantic_index::{SemanticIndex, semantic_index}; | ||||||
| use crate::types::class::ClassType; | use crate::types::class::ClassType; | ||||||
| use crate::types::class_base::ClassBase; | use crate::types::class_base::ClassBase; | ||||||
| use crate::types::constraints::ConstraintSet; | use crate::types::constraints::ConstraintSet; | ||||||
| use crate::types::infer::infer_definition_types; |  | ||||||
| use crate::types::instance::{Protocol, ProtocolInstanceType}; | use crate::types::instance::{Protocol, ProtocolInstanceType}; | ||||||
| use crate::types::signatures::{Parameter, Parameters, Signature}; | use crate::types::signatures::{Parameter, Parameters, Signature}; | ||||||
| use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type}; | use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type}; | ||||||
|  | @ -21,8 +20,7 @@ use crate::types::{ | ||||||
|     FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, |     FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, | ||||||
|     KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeContext, |     KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, Type, TypeContext, | ||||||
|     TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity, TypeVarInstance, |     TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarIdentity, TypeVarInstance, | ||||||
|     TypeVarKind, TypeVarVariance, UnionType, binding_type, declaration_type, |     TypeVarKind, TypeVarVariance, UnionType, declaration_type, walk_bound_type_var_type, | ||||||
|     walk_bound_type_var_type, |  | ||||||
| }; | }; | ||||||
| use crate::{Db, FxIndexSet, FxOrderMap, FxOrderSet}; | use crate::{Db, FxIndexSet, FxOrderMap, FxOrderSet}; | ||||||
| 
 | 
 | ||||||
|  | @ -35,31 +33,7 @@ pub(crate) fn enclosing_generic_contexts<'db>( | ||||||
| ) -> impl Iterator<Item = GenericContext<'db>> { | ) -> impl Iterator<Item = GenericContext<'db>> { | ||||||
|     index |     index | ||||||
|         .ancestor_scopes(scope) |         .ancestor_scopes(scope) | ||||||
|         .filter_map(|(_, ancestor_scope)| match ancestor_scope.node() { |         .filter_map(|(_, ancestor_scope)| ancestor_scope.node().generic_context(db, index)) | ||||||
|             NodeWithScopeKind::Class(class) => { |  | ||||||
|                 let definition = index.expect_single_definition(class); |  | ||||||
|                 binding_type(db, definition) |  | ||||||
|                     .as_class_literal()? |  | ||||||
|                     .generic_context(db) |  | ||||||
|             } |  | ||||||
|             NodeWithScopeKind::Function(function) => { |  | ||||||
|                 let definition = index.expect_single_definition(function); |  | ||||||
|                 infer_definition_types(db, definition) |  | ||||||
|                     .undecorated_type() |  | ||||||
|                     .expect("function should have undecorated type") |  | ||||||
|                     .as_function_literal()? |  | ||||||
|                     .last_definition_signature(db) |  | ||||||
|                     .generic_context |  | ||||||
|             } |  | ||||||
|             NodeWithScopeKind::TypeAlias(type_alias) => { |  | ||||||
|                 let definition = index.expect_single_definition(type_alias); |  | ||||||
|                 binding_type(db, definition) |  | ||||||
|                     .as_type_alias()? |  | ||||||
|                     .as_pep_695_type_alias()? |  | ||||||
|                     .generic_context(db) |  | ||||||
|             } |  | ||||||
|             _ => None, |  | ||||||
|         }) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Binds an unbound typevar.
 | /// Binds an unbound typevar.
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Shunsuke Shibayama
						Shunsuke Shibayama