Compare commits
16 Commits
0.6.5
...
cjm/infere
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3140beb6a4 | ||
|
|
09812b3c23 | ||
|
|
1cb570cdd2 | ||
|
|
dcfebaa4a8 | ||
|
|
d86e5ad031 | ||
|
|
bb12fe9d0c | ||
|
|
3b57faf19b | ||
|
|
c9f7c3d652 | ||
|
|
489dbbaadc | ||
|
|
47e9ea2d5d | ||
|
|
7919a7122a | ||
|
|
a70d693b1c | ||
|
|
1365b0806d | ||
|
|
f4de49ab37 | ||
|
|
8b49845537 | ||
|
|
d988204b1b |
@@ -59,7 +59,7 @@ repos:
|
||||
pass_filenames: false # This makes it a lot faster
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.6.4
|
||||
rev: v0.6.5
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
|
||||
@@ -23,4 +23,3 @@ mod stdlib;
|
||||
pub mod types;
|
||||
|
||||
type FxOrderSet<V> = ordermap::set::OrderSet<V, BuildHasherDefault<FxHasher>>;
|
||||
type FxOrderMap<K, V> = ordermap::map::OrderMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
@@ -27,7 +27,9 @@ pub mod expression;
|
||||
pub mod symbol;
|
||||
mod use_def;
|
||||
|
||||
pub(crate) use self::use_def::{DefinitionWithConstraints, DefinitionWithConstraintsIterator};
|
||||
pub(crate) use self::use_def::{
|
||||
BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator,
|
||||
};
|
||||
|
||||
type SymbolMap = hashbrown::HashMap<ScopedSymbolId, (), ()>;
|
||||
|
||||
@@ -326,16 +328,16 @@ mod tests {
|
||||
use crate::Db;
|
||||
|
||||
impl UseDefMap<'_> {
|
||||
fn first_public_definition(&self, symbol: ScopedSymbolId) -> Option<Definition<'_>> {
|
||||
self.public_definitions(symbol)
|
||||
fn first_public_binding(&self, symbol: ScopedSymbolId) -> Option<Definition<'_>> {
|
||||
self.public_bindings(symbol)
|
||||
.next()
|
||||
.map(|constrained_definition| constrained_definition.definition)
|
||||
.map(|constrained_binding| constrained_binding.binding)
|
||||
}
|
||||
|
||||
fn first_use_definition(&self, use_id: ScopedUseId) -> Option<Definition<'_>> {
|
||||
self.use_definitions(use_id)
|
||||
fn first_binding_at_use(&self, use_id: ScopedUseId) -> Option<Definition<'_>> {
|
||||
self.bindings_at_use(use_id)
|
||||
.next()
|
||||
.map(|constrained_definition| constrained_definition.definition)
|
||||
.map(|constrained_binding| constrained_binding.binding)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,8 +399,8 @@ mod tests {
|
||||
let foo = global_table.symbol_id_by_name("foo").unwrap();
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let definition = use_def.first_public_definition(foo).unwrap();
|
||||
assert!(matches!(definition.node(&db), DefinitionKind::Import(_)));
|
||||
let binding = use_def.first_public_binding(foo).unwrap();
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::Import(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -427,22 +429,19 @@ mod tests {
|
||||
assert!(
|
||||
global_table
|
||||
.symbol_by_name("foo")
|
||||
.is_some_and(|symbol| { symbol.is_defined() && !symbol.is_used() }),
|
||||
.is_some_and(|symbol| { symbol.is_bound() && !symbol.is_used() }),
|
||||
"symbols that are defined get the defined flag"
|
||||
);
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
let binding = use_def
|
||||
.first_public_binding(
|
||||
global_table
|
||||
.symbol_id_by_name("foo")
|
||||
.expect("symbol to exist"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
DefinitionKind::ImportFrom(_)
|
||||
));
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::ImportFrom(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -455,17 +454,14 @@ mod tests {
|
||||
assert!(
|
||||
global_table
|
||||
.symbol_by_name("foo")
|
||||
.is_some_and(|symbol| { !symbol.is_defined() && symbol.is_used() }),
|
||||
"a symbol used but not defined in a scope should have only the used flag"
|
||||
.is_some_and(|symbol| { !symbol.is_bound() && symbol.is_used() }),
|
||||
"a symbol used but not bound in a scope should have only the used flag"
|
||||
);
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let definition = use_def
|
||||
.first_public_definition(global_table.symbol_id_by_name("x").expect("symbol exists"))
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name("x").expect("symbol exists"))
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
DefinitionKind::Assignment(_)
|
||||
));
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -477,12 +473,12 @@ mod tests {
|
||||
assert_eq!(names(&global_table), vec!["x"]);
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let definition = use_def
|
||||
.first_public_definition(global_table.symbol_id_by_name("x").unwrap())
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name("x").unwrap())
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
binding.kind(&db),
|
||||
DefinitionKind::AugmentedAssignment(_)
|
||||
));
|
||||
}
|
||||
@@ -515,13 +511,10 @@ y = 2
|
||||
assert_eq!(names(&class_table), vec!["x"]);
|
||||
|
||||
let use_def = index.use_def_map(class_scope_id);
|
||||
let definition = use_def
|
||||
.first_public_definition(class_table.symbol_id_by_name("x").expect("symbol exists"))
|
||||
let binding = use_def
|
||||
.first_public_binding(class_table.symbol_id_by_name("x").expect("symbol exists"))
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
DefinitionKind::Assignment(_)
|
||||
));
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -551,17 +544,14 @@ y = 2
|
||||
assert_eq!(names(&function_table), vec!["x"]);
|
||||
|
||||
let use_def = index.use_def_map(function_scope_id);
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
let binding = use_def
|
||||
.first_public_binding(
|
||||
function_table
|
||||
.symbol_id_by_name("x")
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
DefinitionKind::Assignment(_)
|
||||
));
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -593,27 +583,27 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
|
||||
let use_def = index.use_def_map(function_scope_id);
|
||||
for name in ["a", "b", "c", "d"] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
let binding = use_def
|
||||
.first_public_binding(
|
||||
function_table
|
||||
.symbol_id_by_name(name)
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
binding.kind(&db),
|
||||
DefinitionKind::ParameterWithDefault(_)
|
||||
));
|
||||
}
|
||||
for name in ["args", "kwargs"] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
let binding = use_def
|
||||
.first_public_binding(
|
||||
function_table
|
||||
.symbol_id_by_name(name)
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(definition.node(&db), DefinitionKind::Parameter(_)));
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::Parameter(_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -641,23 +631,19 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
|
||||
let use_def = index.use_def_map(lambda_scope_id);
|
||||
for name in ["a", "b", "c", "d"] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
lambda_table.symbol_id_by_name(name).expect("symbol exists"),
|
||||
)
|
||||
let binding = use_def
|
||||
.first_public_binding(lambda_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
binding.kind(&db),
|
||||
DefinitionKind::ParameterWithDefault(_)
|
||||
));
|
||||
}
|
||||
for name in ["args", "kwargs"] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
lambda_table.symbol_id_by_name(name).expect("symbol exists"),
|
||||
)
|
||||
let binding = use_def
|
||||
.first_public_binding(lambda_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
.unwrap();
|
||||
assert!(matches!(definition.node(&db), DefinitionKind::Parameter(_)));
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::Parameter(_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,15 +681,15 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
|
||||
let use_def = index.use_def_map(comprehension_scope_id);
|
||||
for name in ["x", "y"] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
let binding = use_def
|
||||
.first_public_binding(
|
||||
comprehension_symbol_table
|
||||
.symbol_id_by_name(name)
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
definition.node(&db),
|
||||
binding.kind(&db),
|
||||
DefinitionKind::Comprehension(_)
|
||||
));
|
||||
}
|
||||
@@ -742,8 +728,8 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
|
||||
let element_use_id =
|
||||
element.scoped_use_id(&db, comprehension_scope_id.to_scope_id(&db, file));
|
||||
|
||||
let definition = use_def.first_use_definition(element_use_id).unwrap();
|
||||
let DefinitionKind::Comprehension(comprehension) = definition.node(&db) else {
|
||||
let binding = use_def.first_binding_at_use(element_use_id).unwrap();
|
||||
let DefinitionKind::Comprehension(comprehension) = binding.kind(&db) else {
|
||||
panic!("expected generator definition")
|
||||
};
|
||||
let target = comprehension.target();
|
||||
@@ -822,12 +808,10 @@ with item1 as x, item2 as y:
|
||||
|
||||
let use_def = index.use_def_map(FileScopeId::global());
|
||||
for name in ["x", "y"] {
|
||||
let Some(definition) = use_def.first_public_definition(
|
||||
global_table.symbol_id_by_name(name).expect("symbol exists"),
|
||||
) else {
|
||||
panic!("Expected with item definition for {name}");
|
||||
};
|
||||
assert!(matches!(definition.node(&db), DefinitionKind::WithItem(_)));
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
.expect("Expected with item definition for {name}");
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -847,12 +831,10 @@ with context() as (x, y):
|
||||
|
||||
let use_def = index.use_def_map(FileScopeId::global());
|
||||
for name in ["x", "y"] {
|
||||
let Some(definition) = use_def.first_public_definition(
|
||||
global_table.symbol_id_by_name(name).expect("symbol exists"),
|
||||
) else {
|
||||
panic!("Expected with item definition for {name}");
|
||||
};
|
||||
assert!(matches!(definition.node(&db), DefinitionKind::WithItem(_)));
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
.expect("Expected with item definition for {name}");
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -889,14 +871,14 @@ def func():
|
||||
assert_eq!(names(&func2_table), vec!["y"]);
|
||||
|
||||
let use_def = index.use_def_map(FileScopeId::global());
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
let binding = use_def
|
||||
.first_public_binding(
|
||||
global_table
|
||||
.symbol_id_by_name("func")
|
||||
.expect("symbol exists"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(matches!(definition.node(&db), DefinitionKind::Function(_)));
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::Function(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -964,7 +946,7 @@ class C[T]:
|
||||
assert!(
|
||||
ann_table
|
||||
.symbol_by_name("T")
|
||||
.is_some_and(|s| s.is_defined() && !s.is_used()),
|
||||
.is_some_and(|s| s.is_bound() && !s.is_used()),
|
||||
"type parameters are defined by the scope that introduces them"
|
||||
);
|
||||
|
||||
@@ -996,8 +978,8 @@ class C[T]:
|
||||
};
|
||||
let x_use_id = x_use_expr_name.scoped_use_id(&db, scope);
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let definition = use_def.first_use_definition(x_use_id).unwrap();
|
||||
let DefinitionKind::Assignment(assignment) = definition.node(&db) else {
|
||||
let binding = use_def.first_binding_at_use(x_use_id).unwrap();
|
||||
let DefinitionKind::Assignment(assignment) = binding.kind(&db) else {
|
||||
panic!("should be an assignment definition")
|
||||
};
|
||||
let ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
|
||||
@@ -1127,12 +1109,10 @@ match subject:
|
||||
("k", 0),
|
||||
("l", 1),
|
||||
] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
global_table.symbol_id_by_name(name).expect("symbol exists"),
|
||||
)
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
.expect("Expected with item definition for {name}");
|
||||
if let DefinitionKind::MatchPattern(pattern) = definition.node(&db) {
|
||||
if let DefinitionKind::MatchPattern(pattern) = binding.kind(&db) {
|
||||
assert_eq!(pattern.index(), expected_index);
|
||||
} else {
|
||||
panic!("Expected match pattern definition for {name}");
|
||||
@@ -1159,12 +1139,10 @@ match 1:
|
||||
|
||||
let use_def = use_def_map(&db, global_scope_id);
|
||||
for (name, expected_index) in [("first", 0), ("second", 0)] {
|
||||
let definition = use_def
|
||||
.first_public_definition(
|
||||
global_table.symbol_id_by_name(name).expect("symbol exists"),
|
||||
)
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name(name).expect("symbol exists"))
|
||||
.expect("Expected with item definition for {name}");
|
||||
if let DefinitionKind::MatchPattern(pattern) = definition.node(&db) {
|
||||
if let DefinitionKind::MatchPattern(pattern) = binding.kind(&db) {
|
||||
assert_eq!(pattern.index(), expected_index);
|
||||
} else {
|
||||
panic!("Expected match pattern definition for {name}");
|
||||
@@ -1181,11 +1159,11 @@ match 1:
|
||||
assert_eq!(&names(&global_table), &["a", "x"]);
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let definition = use_def
|
||||
.first_public_definition(global_table.symbol_id_by_name("x").unwrap())
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name("x").unwrap())
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(definition.node(&db), DefinitionKind::For(_)));
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1197,15 +1175,15 @@ match 1:
|
||||
assert_eq!(&names(&global_table), &["a", "x", "y"]);
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let x_definition = use_def
|
||||
.first_public_definition(global_table.symbol_id_by_name("x").unwrap())
|
||||
let x_binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name("x").unwrap())
|
||||
.unwrap();
|
||||
let y_definition = use_def
|
||||
.first_public_definition(global_table.symbol_id_by_name("y").unwrap())
|
||||
let y_binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name("y").unwrap())
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(x_definition.node(&db), DefinitionKind::For(_)));
|
||||
assert!(matches!(y_definition.node(&db), DefinitionKind::For(_)));
|
||||
assert!(matches!(x_binding.kind(&db), DefinitionKind::For(_)));
|
||||
assert!(matches!(y_binding.kind(&db), DefinitionKind::For(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1217,10 +1195,10 @@ match 1:
|
||||
assert_eq!(&names(&global_table), &["e", "a", "b", "c", "d"]);
|
||||
|
||||
let use_def = use_def_map(&db, scope);
|
||||
let definition = use_def
|
||||
.first_public_definition(global_table.symbol_id_by_name("a").unwrap())
|
||||
let binding = use_def
|
||||
.first_public_binding(global_table.symbol_id_by_name("a").unwrap())
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(definition.node(&db), DefinitionKind::For(_)));
|
||||
assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::semantic_index::definition::{
|
||||
};
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopedSymbolId, SymbolFlags,
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopedSymbolId,
|
||||
SymbolTableBuilder,
|
||||
};
|
||||
use crate::semantic_index::use_def::{FlowSnapshot, UseDefMapBuilder};
|
||||
@@ -28,7 +28,8 @@ use crate::Db;
|
||||
|
||||
use super::constraint::{Constraint, PatternConstraint};
|
||||
use super::definition::{
|
||||
ExceptHandlerDefinitionNodeRef, MatchPatternDefinitionNodeRef, WithItemDefinitionNodeRef,
|
||||
DefinitionCategory, ExceptHandlerDefinitionNodeRef, MatchPatternDefinitionNodeRef,
|
||||
WithItemDefinitionNodeRef,
|
||||
};
|
||||
|
||||
pub(super) struct SemanticIndexBuilder<'db> {
|
||||
@@ -168,31 +169,38 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
self.current_use_def_map_mut().merge(state);
|
||||
}
|
||||
|
||||
fn add_or_update_symbol(&mut self, name: Name, flags: SymbolFlags) -> ScopedSymbolId {
|
||||
let symbol_table = self.current_symbol_table();
|
||||
let (symbol_id, added) = symbol_table.add_or_update_symbol(name, flags);
|
||||
fn add_symbol(&mut self, name: Name) -> ScopedSymbolId {
|
||||
let (symbol_id, added) = self.current_symbol_table().add_symbol(name);
|
||||
if added {
|
||||
let use_def_map = self.current_use_def_map_mut();
|
||||
use_def_map.add_symbol(symbol_id);
|
||||
self.current_use_def_map_mut().add_symbol(symbol_id);
|
||||
}
|
||||
symbol_id
|
||||
}
|
||||
|
||||
fn mark_symbol_bound(&mut self, id: ScopedSymbolId) {
|
||||
self.current_symbol_table().mark_symbol_bound(id);
|
||||
}
|
||||
|
||||
fn mark_symbol_used(&mut self, id: ScopedSymbolId) {
|
||||
self.current_symbol_table().mark_symbol_used(id);
|
||||
}
|
||||
|
||||
fn add_definition<'a>(
|
||||
&mut self,
|
||||
symbol: ScopedSymbolId,
|
||||
definition_node: impl Into<DefinitionNodeRef<'a>>,
|
||||
) -> Definition<'db> {
|
||||
let definition_node: DefinitionNodeRef<'_> = definition_node.into();
|
||||
#[allow(unsafe_code)]
|
||||
// SAFETY: `definition_node` is guaranteed to be a child of `self.module`
|
||||
let kind = unsafe { definition_node.into_owned(self.module.clone()) };
|
||||
let category = kind.category();
|
||||
let definition = Definition::new(
|
||||
self.db,
|
||||
self.file,
|
||||
self.current_scope(),
|
||||
symbol,
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
definition_node.into_owned(self.module.clone())
|
||||
},
|
||||
kind,
|
||||
countme::Count::default(),
|
||||
);
|
||||
|
||||
@@ -201,8 +209,18 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
.insert(definition_node.key(), definition);
|
||||
debug_assert_eq!(existing_definition, None);
|
||||
|
||||
self.current_use_def_map_mut()
|
||||
.record_definition(symbol, definition);
|
||||
if category.is_binding() {
|
||||
self.mark_symbol_bound(symbol);
|
||||
}
|
||||
|
||||
let use_def = self.current_use_def_map_mut();
|
||||
match category {
|
||||
DefinitionCategory::DeclarationAndBinding => {
|
||||
use_def.record_declaration_and_binding(symbol, definition);
|
||||
}
|
||||
DefinitionCategory::Declaration => use_def.record_declaration(symbol, definition),
|
||||
DefinitionCategory::Binding => use_def.record_binding(symbol, definition),
|
||||
}
|
||||
|
||||
definition
|
||||
}
|
||||
@@ -284,10 +302,13 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
..
|
||||
}) => (name, &None, default),
|
||||
};
|
||||
// TODO create Definition for typevars
|
||||
self.add_or_update_symbol(name.id.clone(), SymbolFlags::IS_DEFINED);
|
||||
if let Some(bound) = bound {
|
||||
self.visit_expr(bound);
|
||||
let symbol = self.add_symbol(name.id.clone());
|
||||
// TODO create Definition for PEP 695 typevars
|
||||
// note that the "bound" on the typevar is a totally different thing than whether
|
||||
// or not a name is "bound" by a typevar declaration; the latter is always true.
|
||||
self.mark_symbol_bound(symbol);
|
||||
if let Some(bounds) = bound {
|
||||
self.visit_expr(bounds);
|
||||
}
|
||||
if let Some(default) = default {
|
||||
self.visit_expr(default);
|
||||
@@ -304,11 +325,23 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
nested_scope
|
||||
}
|
||||
|
||||
/// Visit a list of [`Comprehension`] nodes, assumed to be the "generators" that compose a
|
||||
/// comprehension (that is, the `for x in y` and `for y in z` parts of `x for x in y for y in z`.)
|
||||
/// This method does several things:
|
||||
/// - It pushes a new scope onto the stack for visiting
|
||||
/// a list/dict/set comprehension or generator expression
|
||||
/// - Inside that scope, it visits a list of [`Comprehension`] nodes,
|
||||
/// assumed to be the "generators" that compose a comprehension
|
||||
/// (that is, the `for x in y` and `for y in z` parts of `x for x in y for y in z`).
|
||||
/// - Inside that scope, it also calls a closure for visiting the outer `elt`
|
||||
/// of a list/dict/set comprehension or generator expression
|
||||
/// - It then pops the new scope off the stack
|
||||
///
|
||||
/// [`Comprehension`]: ast::Comprehension
|
||||
fn visit_generators(&mut self, scope: NodeWithScopeRef, generators: &'db [ast::Comprehension]) {
|
||||
fn with_generators_scope(
|
||||
&mut self,
|
||||
scope: NodeWithScopeRef,
|
||||
generators: &'db [ast::Comprehension],
|
||||
visit_outer_elt: impl FnOnce(&mut Self),
|
||||
) {
|
||||
let mut generators_iter = generators.iter();
|
||||
|
||||
let Some(generator) = generators_iter.next() else {
|
||||
@@ -347,11 +380,13 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
|
||||
visit_outer_elt(self);
|
||||
self.pop_scope();
|
||||
}
|
||||
|
||||
fn declare_parameter(&mut self, parameter: AnyParameterRef) {
|
||||
let symbol =
|
||||
self.add_or_update_symbol(parameter.name().id().clone(), SymbolFlags::IS_DEFINED);
|
||||
let symbol = self.add_symbol(parameter.name().id().clone());
|
||||
|
||||
let definition = self.add_definition(symbol, parameter);
|
||||
|
||||
@@ -462,8 +497,7 @@ where
|
||||
// The symbol for the function name itself has to be evaluated
|
||||
// at the end to match the runtime evaluation of parameter defaults
|
||||
// and return-type annotations.
|
||||
let symbol = self
|
||||
.add_or_update_symbol(function_def.name.id.clone(), SymbolFlags::IS_DEFINED);
|
||||
let symbol = self.add_symbol(function_def.name.id.clone());
|
||||
self.add_definition(symbol, function_def);
|
||||
}
|
||||
ast::Stmt::ClassDef(class) => {
|
||||
@@ -471,8 +505,7 @@ where
|
||||
self.visit_decorator(decorator);
|
||||
}
|
||||
|
||||
let symbol =
|
||||
self.add_or_update_symbol(class.name.id.clone(), SymbolFlags::IS_DEFINED);
|
||||
let symbol = self.add_symbol(class.name.id.clone());
|
||||
self.add_definition(symbol, class);
|
||||
|
||||
self.with_type_params(
|
||||
@@ -498,7 +531,7 @@ where
|
||||
Name::new(alias.name.id.split('.').next().unwrap())
|
||||
};
|
||||
|
||||
let symbol = self.add_or_update_symbol(symbol_name, SymbolFlags::IS_DEFINED);
|
||||
let symbol = self.add_symbol(symbol_name);
|
||||
self.add_definition(symbol, alias);
|
||||
}
|
||||
}
|
||||
@@ -510,8 +543,7 @@ where
|
||||
&alias.name.id
|
||||
};
|
||||
|
||||
let symbol =
|
||||
self.add_or_update_symbol(symbol_name.clone(), SymbolFlags::IS_DEFINED);
|
||||
let symbol = self.add_symbol(symbol_name.clone());
|
||||
self.add_definition(symbol, ImportFromDefinitionNodeRef { node, alias_index });
|
||||
}
|
||||
}
|
||||
@@ -725,8 +757,7 @@ where
|
||||
// which is invalid syntax. However, it's still pretty obvious here that the user
|
||||
// *wanted* `e` to be bound, so we should still create a definition here nonetheless.
|
||||
if let Some(symbol_name) = symbol_name {
|
||||
let symbol = self
|
||||
.add_or_update_symbol(symbol_name.id.clone(), SymbolFlags::IS_DEFINED);
|
||||
let symbol = self.add_symbol(symbol_name.id.clone());
|
||||
|
||||
self.add_definition(
|
||||
symbol,
|
||||
@@ -756,24 +787,18 @@ where
|
||||
|
||||
match expr {
|
||||
ast::Expr::Name(name_node @ ast::ExprName { id, ctx, .. }) => {
|
||||
let flags = match (ctx, self.current_assignment) {
|
||||
let (is_use, is_definition) = match (ctx, self.current_assignment) {
|
||||
(ast::ExprContext::Store, Some(CurrentAssignment::AugAssign(_))) => {
|
||||
// For augmented assignment, the target expression is also used.
|
||||
SymbolFlags::IS_DEFINED | SymbolFlags::IS_USED
|
||||
(true, true)
|
||||
}
|
||||
(ast::ExprContext::Store, Some(CurrentAssignment::AnnAssign(ann_assign)))
|
||||
if ann_assign.value.is_none() =>
|
||||
{
|
||||
// An annotated assignment that doesn't assign a value is not a Definition
|
||||
SymbolFlags::empty()
|
||||
}
|
||||
(ast::ExprContext::Load, _) => SymbolFlags::IS_USED,
|
||||
(ast::ExprContext::Store, _) => SymbolFlags::IS_DEFINED,
|
||||
(ast::ExprContext::Del, _) => SymbolFlags::IS_DEFINED,
|
||||
(ast::ExprContext::Invalid, _) => SymbolFlags::empty(),
|
||||
(ast::ExprContext::Load, _) => (true, false),
|
||||
(ast::ExprContext::Store, _) => (false, true),
|
||||
(ast::ExprContext::Del, _) => (false, true),
|
||||
(ast::ExprContext::Invalid, _) => (false, false),
|
||||
};
|
||||
let symbol = self.add_or_update_symbol(id.clone(), flags);
|
||||
if flags.contains(SymbolFlags::IS_DEFINED) {
|
||||
let symbol = self.add_symbol(id.clone());
|
||||
if is_definition {
|
||||
match self.current_assignment {
|
||||
Some(CurrentAssignment::Assign(assignment)) => {
|
||||
self.add_definition(
|
||||
@@ -830,7 +855,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if flags.contains(SymbolFlags::IS_USED) {
|
||||
if is_use {
|
||||
self.mark_symbol_used(symbol);
|
||||
let use_id = self.current_ast_ids().record_use(expr);
|
||||
self.current_use_def_map_mut().record_use(symbol, use_id);
|
||||
}
|
||||
@@ -867,6 +893,7 @@ where
|
||||
}
|
||||
|
||||
self.visit_expr(lambda.body.as_ref());
|
||||
self.pop_scope();
|
||||
}
|
||||
ast::Expr::If(ast::ExprIf {
|
||||
body, test, orelse, ..
|
||||
@@ -887,30 +914,33 @@ where
|
||||
elt, generators, ..
|
||||
},
|
||||
) => {
|
||||
self.visit_generators(
|
||||
self.with_generators_scope(
|
||||
NodeWithScopeRef::ListComprehension(list_comprehension),
|
||||
generators,
|
||||
|builder| builder.visit_expr(elt),
|
||||
);
|
||||
self.visit_expr(elt);
|
||||
}
|
||||
ast::Expr::SetComp(
|
||||
set_comprehension @ ast::ExprSetComp {
|
||||
elt, generators, ..
|
||||
},
|
||||
) => {
|
||||
self.visit_generators(
|
||||
self.with_generators_scope(
|
||||
NodeWithScopeRef::SetComprehension(set_comprehension),
|
||||
generators,
|
||||
|builder| builder.visit_expr(elt),
|
||||
);
|
||||
self.visit_expr(elt);
|
||||
}
|
||||
ast::Expr::Generator(
|
||||
generator @ ast::ExprGenerator {
|
||||
elt, generators, ..
|
||||
},
|
||||
) => {
|
||||
self.visit_generators(NodeWithScopeRef::GeneratorExpression(generator), generators);
|
||||
self.visit_expr(elt);
|
||||
self.with_generators_scope(
|
||||
NodeWithScopeRef::GeneratorExpression(generator),
|
||||
generators,
|
||||
|builder| builder.visit_expr(elt),
|
||||
);
|
||||
}
|
||||
ast::Expr::DictComp(
|
||||
dict_comprehension @ ast::ExprDictComp {
|
||||
@@ -920,28 +950,19 @@ where
|
||||
..
|
||||
},
|
||||
) => {
|
||||
self.visit_generators(
|
||||
self.with_generators_scope(
|
||||
NodeWithScopeRef::DictComprehension(dict_comprehension),
|
||||
generators,
|
||||
|builder| {
|
||||
builder.visit_expr(key);
|
||||
builder.visit_expr(value);
|
||||
},
|
||||
);
|
||||
self.visit_expr(key);
|
||||
self.visit_expr(value);
|
||||
}
|
||||
_ => {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(
|
||||
expr,
|
||||
ast::Expr::Lambda(_)
|
||||
| ast::Expr::ListComp(_)
|
||||
| ast::Expr::SetComp(_)
|
||||
| ast::Expr::Generator(_)
|
||||
| ast::Expr::DictComp(_)
|
||||
) {
|
||||
self.pop_scope();
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_parameters(&mut self, parameters: &'ast ast::Parameters) {
|
||||
@@ -970,7 +991,7 @@ where
|
||||
range: _,
|
||||
}) = pattern
|
||||
{
|
||||
let symbol = self.add_or_update_symbol(name.id().clone(), SymbolFlags::IS_DEFINED);
|
||||
let symbol = self.add_symbol(name.id().clone());
|
||||
let state = self.current_match_case.as_ref().unwrap();
|
||||
self.add_definition(
|
||||
symbol,
|
||||
@@ -991,7 +1012,7 @@ where
|
||||
rest: Some(name), ..
|
||||
}) = pattern
|
||||
{
|
||||
let symbol = self.add_or_update_symbol(name.id().clone(), SymbolFlags::IS_DEFINED);
|
||||
let symbol = self.add_symbol(name.id().clone());
|
||||
let state = self.current_match_case.as_ref().unwrap();
|
||||
self.add_definition(
|
||||
symbol,
|
||||
|
||||
@@ -23,7 +23,7 @@ pub struct Definition<'db> {
|
||||
|
||||
#[no_eq]
|
||||
#[return_ref]
|
||||
pub(crate) node: DefinitionKind,
|
||||
pub(crate) kind: DefinitionKind,
|
||||
|
||||
#[no_eq]
|
||||
count: countme::Count<Definition<'static>>,
|
||||
@@ -33,6 +33,18 @@ impl<'db> Definition<'db> {
|
||||
pub(crate) fn scope(self, db: &'db dyn Db) -> ScopeId<'db> {
|
||||
self.file_scope(db).to_scope_id(db, self.file(db))
|
||||
}
|
||||
|
||||
pub(crate) fn category(self, db: &'db dyn Db) -> DefinitionCategory {
|
||||
self.kind(db).category()
|
||||
}
|
||||
|
||||
pub(crate) fn is_declaration(self, db: &'db dyn Db) -> bool {
|
||||
self.kind(db).category().is_declaration()
|
||||
}
|
||||
|
||||
pub(crate) fn is_binding(self, db: &'db dyn Db) -> bool {
|
||||
self.kind(db).category().is_binding()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@@ -302,6 +314,41 @@ impl DefinitionNodeRef<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum DefinitionCategory {
|
||||
/// A Definition which binds a value to a name (e.g. `x = 1`).
|
||||
Binding,
|
||||
/// A Definition which declares the upper-bound of acceptable types for this name (`x: int`).
|
||||
Declaration,
|
||||
/// A Definition which both declares a type and binds a value (e.g. `x: int = 1`).
|
||||
DeclarationAndBinding,
|
||||
}
|
||||
|
||||
impl DefinitionCategory {
|
||||
/// True if this definition establishes a "declared type" for the symbol.
|
||||
///
|
||||
/// If so, any assignments reached by this definition are in error if they assign a value of a
|
||||
/// type not assignable to the declared type.
|
||||
///
|
||||
/// Annotations establish a declared type. So do function and class definition.
|
||||
pub(crate) fn is_declaration(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
DefinitionCategory::Declaration | DefinitionCategory::DeclarationAndBinding
|
||||
)
|
||||
}
|
||||
|
||||
/// True if this definition assigns a value to the symbol.
|
||||
///
|
||||
/// False only for annotated assignments without a RHS.
|
||||
pub(crate) fn is_binding(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
DefinitionCategory::Binding | DefinitionCategory::DeclarationAndBinding
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DefinitionKind {
|
||||
Import(AstNodeRef<ast::Alias>),
|
||||
@@ -321,6 +368,52 @@ pub enum DefinitionKind {
|
||||
ExceptHandler(ExceptHandlerDefinitionKind),
|
||||
}
|
||||
|
||||
impl DefinitionKind {
|
||||
pub(crate) fn category(&self) -> DefinitionCategory {
|
||||
match self {
|
||||
// functions and classes always bind a value, and we always consider them declarations
|
||||
DefinitionKind::Function(_) | DefinitionKind::Class(_) => {
|
||||
DefinitionCategory::DeclarationAndBinding
|
||||
}
|
||||
// a parameter always binds a value, but is only a declaration if annotated
|
||||
DefinitionKind::Parameter(parameter) => {
|
||||
if parameter.annotation.is_some() {
|
||||
DefinitionCategory::DeclarationAndBinding
|
||||
} else {
|
||||
DefinitionCategory::Binding
|
||||
}
|
||||
}
|
||||
// presence of a default is irrelevant, same logic as for a no-default parameter
|
||||
DefinitionKind::ParameterWithDefault(parameter_with_default) => {
|
||||
if parameter_with_default.parameter.annotation.is_some() {
|
||||
DefinitionCategory::DeclarationAndBinding
|
||||
} else {
|
||||
DefinitionCategory::Binding
|
||||
}
|
||||
}
|
||||
// annotated assignment is always a declaration, only a binding if there is a RHS
|
||||
DefinitionKind::AnnotatedAssignment(ann_assign) => {
|
||||
if ann_assign.value.is_some() {
|
||||
DefinitionCategory::DeclarationAndBinding
|
||||
} else {
|
||||
DefinitionCategory::Declaration
|
||||
}
|
||||
}
|
||||
// all of these bind values without declaring a type
|
||||
DefinitionKind::Import(_)
|
||||
| DefinitionKind::ImportFrom(_)
|
||||
| DefinitionKind::NamedExpression(_)
|
||||
| DefinitionKind::Assignment(_)
|
||||
| DefinitionKind::AugmentedAssignment(_)
|
||||
| DefinitionKind::For(_)
|
||||
| DefinitionKind::Comprehension(_)
|
||||
| DefinitionKind::WithItem(_)
|
||||
| DefinitionKind::MatchPattern(_)
|
||||
| DefinitionKind::ExceptHandler(_) => DefinitionCategory::Binding,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct MatchPatternDefinitionKind {
|
||||
@@ -441,8 +534,12 @@ pub struct ExceptHandlerDefinitionKind {
|
||||
}
|
||||
|
||||
impl ExceptHandlerDefinitionKind {
|
||||
pub(crate) fn node(&self) -> &ast::ExceptHandlerExceptHandler {
|
||||
self.handler.node()
|
||||
}
|
||||
|
||||
pub(crate) fn handled_exceptions(&self) -> Option<&ast::Expr> {
|
||||
self.handler.node().type_.as_deref()
|
||||
self.node().type_.as_deref()
|
||||
}
|
||||
|
||||
pub(crate) fn is_star(&self) -> bool {
|
||||
|
||||
@@ -44,16 +44,16 @@ impl Symbol {
|
||||
}
|
||||
|
||||
/// Is the symbol defined in its containing scope?
|
||||
pub fn is_defined(&self) -> bool {
|
||||
self.flags.contains(SymbolFlags::IS_DEFINED)
|
||||
pub fn is_bound(&self) -> bool {
|
||||
self.flags.contains(SymbolFlags::IS_BOUND)
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub(super) struct SymbolFlags: u8 {
|
||||
struct SymbolFlags: u8 {
|
||||
const IS_USED = 1 << 0;
|
||||
const IS_DEFINED = 1 << 1;
|
||||
const IS_BOUND = 1 << 1;
|
||||
/// TODO: This flag is not yet set by anything
|
||||
const MARKED_GLOBAL = 1 << 2;
|
||||
/// TODO: This flag is not yet set by anything
|
||||
@@ -272,11 +272,7 @@ impl SymbolTableBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_or_update_symbol(
|
||||
&mut self,
|
||||
name: Name,
|
||||
flags: SymbolFlags,
|
||||
) -> (ScopedSymbolId, bool) {
|
||||
pub(super) fn add_symbol(&mut self, name: Name) -> (ScopedSymbolId, bool) {
|
||||
let hash = SymbolTable::hash_name(&name);
|
||||
let entry = self
|
||||
.table
|
||||
@@ -285,15 +281,9 @@ impl SymbolTableBuilder {
|
||||
.from_hash(hash, |id| self.table.symbols[*id].name() == &name);
|
||||
|
||||
match entry {
|
||||
RawEntryMut::Occupied(entry) => {
|
||||
let symbol = &mut self.table.symbols[*entry.key()];
|
||||
symbol.insert_flags(flags);
|
||||
|
||||
(*entry.key(), false)
|
||||
}
|
||||
RawEntryMut::Occupied(entry) => (*entry.key(), false),
|
||||
RawEntryMut::Vacant(entry) => {
|
||||
let mut symbol = Symbol::new(name);
|
||||
symbol.insert_flags(flags);
|
||||
let symbol = Symbol::new(name);
|
||||
|
||||
let id = self.table.symbols.push(symbol);
|
||||
entry.insert_with_hasher(hash, id, (), |id| {
|
||||
@@ -304,6 +294,14 @@ impl SymbolTableBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn mark_symbol_bound(&mut self, id: ScopedSymbolId) {
|
||||
self.table.symbols[id].insert_flags(SymbolFlags::IS_BOUND);
|
||||
}
|
||||
|
||||
pub(super) fn mark_symbol_used(&mut self, id: ScopedSymbolId) {
|
||||
self.table.symbols[id].insert_flags(SymbolFlags::IS_USED);
|
||||
}
|
||||
|
||||
pub(super) fn finish(mut self) -> SymbolTable {
|
||||
self.table.shrink_to_fit();
|
||||
self.table
|
||||
|
||||
@@ -1,5 +1,79 @@
|
||||
//! Build a map from each use of a symbol to the definitions visible from that use, and the
|
||||
//! type-narrowing constraints that apply to each definition.
|
||||
//! First, some terminology:
|
||||
//!
|
||||
//! * A "binding" gives a new value to a variable. This includes many different Python statements
|
||||
//! (assignment statements of course, but also imports, `def` and `class` statements, `as`
|
||||
//! clauses in `with` and `except` statements, match patterns, and others) and even one
|
||||
//! expression kind (named expressions). It notably does not include annotated assignment
|
||||
//! statements without a right-hand side value; these do not assign any new value to the
|
||||
//! variable. We consider function parameters to be bindings as well, since (from the perspective
|
||||
//! of the function's internal scope), a function parameter begins the scope bound to a value.
|
||||
//!
|
||||
//! * A "declaration" establishes an upper bound type for the values that a variable may be
|
||||
//! permitted to take on. Annotated assignment statements (with or without an RHS value) are
|
||||
//! declarations; annotated function parameters are also declarations. We consider `def` and
|
||||
//! `class` statements to also be declarations, so as to prohibit accidentally shadowing them.
|
||||
//!
|
||||
//! Annotated assignments with a right-hand side, and annotated function parameters, are both
|
||||
//! bindings and declarations.
|
||||
//!
|
||||
//! We use [`Definition`] as the universal term (and Salsa tracked struct) encompassing both
|
||||
//! bindings and declarations. (This sacrifices a bit of type safety in exchange for improved
|
||||
//! performance via fewer Salsa tracked structs and queries, since most declarations -- typed
|
||||
//! parameters and annotated assignments with RHS -- are both bindings and declarations.)
|
||||
//!
|
||||
//! At any given use of a variable, we can ask about both its "declared type" and its "inferred
|
||||
//! type". These may be different, but the inferred type must always be assignable to the declared
|
||||
//! type; that is, the declared type is always wider, and the inferred type may be more precise. If
|
||||
//! we see an invalid assignment, we emit a diagnostic and abandon our inferred type, deferring to
|
||||
//! the declared type (this allows an explicit annotation to override bad inference, without a
|
||||
//! cast), maintaining the invariant.
|
||||
//!
|
||||
//! The **inferred type** represents the most precise type we believe encompasses all possible
|
||||
//! values for the variable at a given use. It is based on a union of the bindings which can reach
|
||||
//! that use through some control flow path, and the narrowing constraints that control flow must
|
||||
//! have passed through between the binding and the use. For example, in this code:
|
||||
//!
|
||||
//! ```python
|
||||
//! x = 1 if flag else None
|
||||
//! if x is not None:
|
||||
//! use(x)
|
||||
//! ```
|
||||
//!
|
||||
//! For the use of `x` on the third line, the inferred type should be `Literal[1]`. This is based
|
||||
//! on the binding on the first line, which assigns the type `Literal[1] | None`, and the narrowing
|
||||
//! constraint on the second line, which rules out the type `None`, since control flow must pass
|
||||
//! through this constraint to reach the use in question.
|
||||
//!
|
||||
//! The **declared type** represents the code author's declaration (usually through a type
|
||||
//! annotation) that a given variable should not be assigned any type outside the declared type. In
|
||||
//! our model, declared types are also control-flow-sensitive; we allow the code author to
|
||||
//! explicitly re-declare the same variable with a different type. So for a given binding of a
|
||||
//! variable, we will want to ask which declarations of that variable can reach that binding, in
|
||||
//! order to determine whether the binding is permitted, or should be a type error. For example:
|
||||
//!
|
||||
//! ```python
|
||||
//! from pathlib import Path
|
||||
//! def f(path: str):
|
||||
//! path: Path = Path(path)
|
||||
//! ```
|
||||
//!
|
||||
//! In this function, the initial declared type of `path` is `str`, meaning that the assignment
|
||||
//! `path = Path(path)` would be a type error, since it assigns to `path` a value whose type is not
|
||||
//! assignable to `str`. This is the purpose of declared types: they prevent accidental assignment
|
||||
//! of the wrong type to a variable.
|
||||
//!
|
||||
//! But in some cases it is useful to "shadow" or "re-declare" a variable with a new type, and we
|
||||
//! permit this, as long as it is done with an explicit re-annotation. So `path: Path =
|
||||
//! Path(path)`, with the explicit `: Path` annotation, is permitted.
|
||||
//!
|
||||
//! The general rule is that whatever declaration(s) can reach a given binding determine the
|
||||
//! validity of that binding. If there is a path in which the symbol is not declared, that is a
|
||||
//! declaration of `Unknown`. If multiple declarations can reach a binding, we union them, but by
|
||||
//! default we also issue a type error, since this implicit union of declared types may hide an
|
||||
//! error.
|
||||
//!
|
||||
//! To support type inference, we build a map from each use of a symbol to the bindings live at
|
||||
//! that use, and the type narrowing constraints that apply to each binding.
|
||||
//!
|
||||
//! Let's take this code sample:
|
||||
//!
|
||||
@@ -7,147 +81,155 @@
|
||||
//! x = 1
|
||||
//! x = 2
|
||||
//! y = x
|
||||
//! if y is not None:
|
||||
//! if flag:
|
||||
//! x = 3
|
||||
//! else:
|
||||
//! x = 4
|
||||
//! z = x
|
||||
//! ```
|
||||
//!
|
||||
//! In this snippet, we have four definitions of `x` (the statements assigning `1`, `2`, `3`,
|
||||
//! and `4` to it), and two uses of `x` (the `y = x` and `z = x` assignments). The first
|
||||
//! [`Definition`] of `x` is never visible to any use, because it's immediately replaced by the
|
||||
//! second definition, before any use happens. (A linter could thus flag the statement `x = 1`
|
||||
//! as likely superfluous.)
|
||||
//! In this snippet, we have four bindings of `x` (the statements assigning `1`, `2`, `3`, and `4`
|
||||
//! to it), and two uses of `x` (the `y = x` and `z = x` assignments). The first binding of `x`
|
||||
//! does not reach any use, because it's immediately replaced by the second binding, before any use
|
||||
//! happens. (A linter could thus flag the statement `x = 1` as likely superfluous.)
|
||||
//!
|
||||
//! The first use of `x` has one definition visible to it: the assignment `x = 2`.
|
||||
//! The first use of `x` has one live binding: the assignment `x = 2`.
|
||||
//!
|
||||
//! Things get a bit more complex when we have branches. We will definitely take either the `if` or
|
||||
//! the `else` branch. Thus, the second use of `x` has two definitions visible to it: `x = 3` and
|
||||
//! `x = 4`. The `x = 2` definition is no longer visible, because it must be replaced by either `x
|
||||
//! = 3` or `x = 4`, no matter which branch was taken. We don't know which branch was taken, so we
|
||||
//! must consider both definitions as visible, which means eventually we would (in type inference)
|
||||
//! look at these two definitions and infer a type of `Literal[3, 4]` -- the union of `Literal[3]`
|
||||
//! and `Literal[4]` -- for the second use of `x`.
|
||||
//! the `else` branch. Thus, the second use of `x` has two live bindings: `x = 3` and `x = 4`. The
|
||||
//! `x = 2` assignment is no longer visible, because it must be replaced by either `x = 3` or `x =
|
||||
//! 4`, no matter which branch was taken. We don't know which branch was taken, so we must consider
|
||||
//! both bindings as live, which means eventually we would (in type inference) look at these two
|
||||
//! bindings and infer a type of `Literal[3, 4]` -- the union of `Literal[3]` and `Literal[4]` --
|
||||
//! for the second use of `x`.
|
||||
//!
|
||||
//! So that's one question our use-def map needs to answer: given a specific use of a symbol, which
|
||||
//! definition(s) is/are visible from that use. In
|
||||
//! [`AstIds`](crate::semantic_index::ast_ids::AstIds) we number all uses (that means a `Name` node
|
||||
//! with `Load` context) so we have a `ScopedUseId` to efficiently represent each use.
|
||||
//! binding(s) can reach that use. In [`AstIds`](crate::semantic_index::ast_ids::AstIds) we number
|
||||
//! all uses (that means a `Name` node with `Load` context) so we have a `ScopedUseId` to
|
||||
//! efficiently represent each use.
|
||||
//!
|
||||
//! Another case we need to handle is when a symbol is referenced from a different scope (the most
|
||||
//! obvious example of this is an import). We call this "public" use of a symbol. So the other
|
||||
//! question we need to be able to answer is, what are the publicly-visible definitions of each
|
||||
//! symbol?
|
||||
//!
|
||||
//! Technically, public use of a symbol could also occur from any point in control flow of the
|
||||
//! scope where the symbol is defined (via inline imports and import cycles, in the case of an
|
||||
//! import, or via a function call partway through the local scope that ends up using a symbol from
|
||||
//! the scope via a global or nonlocal reference.) But modeling this fully accurately requires
|
||||
//! whole-program analysis that isn't tractable for an efficient incremental compiler, since it
|
||||
//! means a given symbol could have a different type every place it's referenced throughout the
|
||||
//! program, depending on the shape of arbitrarily-sized call/import graphs. So we follow other
|
||||
//! Python type-checkers in making the simplifying assumption that usually the scope will finish
|
||||
//! execution before its symbols are made visible to other scopes; for instance, most imports will
|
||||
//! import from a complete module, not a partially-executed module. (We may want to get a little
|
||||
//! smarter than this in the future, in particular for closures, but for now this is where we
|
||||
//! start.)
|
||||
//!
|
||||
//! So this means that the publicly-visible definitions of a symbol are the definitions still
|
||||
//! visible at the end of the scope; effectively we have an implicit "use" of every symbol at the
|
||||
//! end of the scope.
|
||||
//!
|
||||
//! We also need to know, for a given definition of a symbol, what type-narrowing constraints apply
|
||||
//! We also need to know, for a given definition of a symbol, what type narrowing constraints apply
|
||||
//! to it. For instance, in this code sample:
|
||||
//!
|
||||
//! ```python
|
||||
//! x = 1 if flag else None
|
||||
//! if x is not None:
|
||||
//! y = x
|
||||
//! use(x)
|
||||
//! ```
|
||||
//!
|
||||
//! At the use of `x` in `y = x`, the visible definition of `x` is `1 if flag else None`, which
|
||||
//! would infer as the type `Literal[1] | None`. But the constraint `x is not None` dominates this
|
||||
//! use, which means we can rule out the possibility that `x` is `None` here, which should give us
|
||||
//! the type `Literal[1]` for this use.
|
||||
//! At the use of `x`, the live binding of `x` is `1 if flag else None`, which would infer as the
|
||||
//! type `Literal[1] | None`. But the constraint `x is not None` dominates this use, which means we
|
||||
//! can rule out the possibility that `x` is `None` here, which should give us the type
|
||||
//! `Literal[1]` for this use.
|
||||
//!
|
||||
//! For declared types, we need to be able to answer the question "given a binding to a symbol,
|
||||
//! which declarations of that symbol can reach the binding?" This allows us to emit a diagnostic
|
||||
//! if the binding is attempting to bind a value of a type that is not assignable to the declared
|
||||
//! type for that symbol, at that point in control flow.
|
||||
//!
|
||||
//! We also need to know, given a declaration of a symbol, what the inferred type of that symbol is
|
||||
//! at that point. This allows us to emit a diagnostic in a case like `x = "foo"; x: int`. The
|
||||
//! binding `x = "foo"` occurs before the declaration `x: int`, so according to our
|
||||
//! control-flow-sensitive interpretation of declarations, the assignment is not an error. But the
|
||||
//! declaration is an error, since it would violate the "inferred type must be assignable to
|
||||
//! declared type" rule.
|
||||
//!
|
||||
//! Another case we need to handle is when a symbol is referenced from a different scope (for
|
||||
//! example, an import or a nonlocal reference). We call this "public" use of a symbol. For public
|
||||
//! use of a symbol, we prefer the declared type, if there are any declarations of that symbol; if
|
||||
//! not, we fall back to the inferred type. So we also need to know which declarations and bindings
|
||||
//! can reach the end of the scope.
|
||||
//!
|
||||
//! Technically, public use of a symbol could occur from any point in control flow of the scope
|
||||
//! where the symbol is defined (via inline imports and import cycles, in the case of an import, or
|
||||
//! via a function call partway through the local scope that ends up using a symbol from the scope
|
||||
//! via a global or nonlocal reference.) But modeling this fully accurately requires whole-program
|
||||
//! analysis that isn't tractable for an efficient analysis, since it means a given symbol could
|
||||
//! have a different type every place it's referenced throughout the program, depending on the
|
||||
//! shape of arbitrarily-sized call/import graphs. So we follow other Python type checkers in
|
||||
//! making the simplifying assumption that usually the scope will finish execution before its
|
||||
//! symbols are made visible to other scopes; for instance, most imports will import from a
|
||||
//! complete module, not a partially-executed module. (We may want to get a little smarter than
|
||||
//! this in the future for some closures, but for now this is where we start.)
|
||||
//!
|
||||
//! The data structure we build to answer these questions is the `UseDefMap`. It has a
|
||||
//! `definitions_by_use` vector indexed by [`ScopedUseId`] and a `public_definitions` vector
|
||||
//! indexed by [`ScopedSymbolId`]. The values in each of these vectors are (in principle) a list of
|
||||
//! visible definitions at that use, or at the end of the scope for that symbol, with a list of the
|
||||
//! dominating constraints for each of those definitions.
|
||||
//! `bindings_by_use` vector of [`SymbolBindings`] indexed by [`ScopedUseId`], a
|
||||
//! `declarations_by_binding` vector of [`SymbolDeclarations`] indexed by [`ScopedDefinitionId`], a
|
||||
//! `bindings_by_declaration` vector of [`SymbolBindings`] indexed by [`ScopedDefinitionId`], and
|
||||
//! `public_bindings` and `public_definitions` vectors indexed by [`ScopedSymbolId`]. The values in
|
||||
//! each of these vectors are (in principle) a list of live bindings at that use/definition, or at
|
||||
//! the end of the scope for that symbol, with a list of the dominating constraints for each
|
||||
//! binding.
|
||||
//!
|
||||
//! In order to avoid vectors-of-vectors-of-vectors and all the allocations that would entail, we
|
||||
//! don't actually store these "list of visible definitions" as a vector of [`Definition`].
|
||||
//! Instead, the values in `definitions_by_use` and `public_definitions` are a [`SymbolState`]
|
||||
//! struct which uses bit-sets to track definitions and constraints in terms of
|
||||
//! [`ScopedDefinitionId`] and [`ScopedConstraintId`], which are indices into the `all_definitions`
|
||||
//! and `all_constraints` indexvecs in the [`UseDefMap`].
|
||||
//! Instead, [`SymbolBindings`] and [`SymbolDeclarations`] are structs which use bit-sets to track
|
||||
//! definitions (and constraints, in the case of bindings) in terms of [`ScopedDefinitionId`] and
|
||||
//! [`ScopedConstraintId`], which are indices into the `all_definitions` and `all_constraints`
|
||||
//! indexvecs in the [`UseDefMap`].
|
||||
//!
|
||||
//! There is another special kind of possible "definition" for a symbol: there might be a path from
|
||||
//! the scope entry to a given use in which the symbol is never bound.
|
||||
//!
|
||||
//! The simplest way to model "unbound" would be as an actual [`Definition`] itself: the initial
|
||||
//! visible [`Definition`] for each symbol in a scope. But actually modeling it this way would
|
||||
//! unnecessarily increase the number of [`Definition`] that Salsa must track. Since "unbound" is a
|
||||
//! special definition in that all symbols share it, and it doesn't have any additional per-symbol
|
||||
//! state, and constraints are irrelevant to it, we can represent it more efficiently: we use the
|
||||
//! `may_be_unbound` boolean on the [`SymbolState`] struct. If this flag is `true`, it means the
|
||||
//! symbol/use really has one additional visible "definition", which is the unbound state. If this
|
||||
//! flag is `false`, it means we've eliminated the possibility of unbound: every path we've
|
||||
//! followed includes a definition for this symbol.
|
||||
//! The simplest way to model "unbound" would be as a "binding" itself: the initial "binding" for
|
||||
//! each symbol in a scope. But actually modeling it this way would unnecessarily increase the
|
||||
//! number of [`Definition`]s that Salsa must track. Since "unbound" is special in that all symbols
|
||||
//! share it, and it doesn't have any additional per-symbol state, and constraints are irrelevant
|
||||
//! to it, we can represent it more efficiently: we use the `may_be_unbound` boolean on the
|
||||
//! [`SymbolBindings`] struct. If this flag is `true` for a use of a symbol, it means the symbol
|
||||
//! has a path to the use in which it is never bound. If this flag is `false`, it means we've
|
||||
//! eliminated the possibility of unbound: every control flow path to the use includes a binding
|
||||
//! for this symbol.
|
||||
//!
|
||||
//! To build a [`UseDefMap`], the [`UseDefMapBuilder`] is notified of each new use, definition, and
|
||||
//! constraint as they are encountered by the
|
||||
//! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder) AST visit. For
|
||||
//! each symbol, the builder tracks the `SymbolState` for that symbol. When we hit a use of a
|
||||
//! symbol, it records the current state for that symbol for that use. When we reach the end of the
|
||||
//! scope, it records the state for each symbol as the public definitions of that symbol.
|
||||
//! each symbol, the builder tracks the `SymbolState` (`SymbolBindings` and `SymbolDeclarations`)
|
||||
//! for that symbol. When we hit a use or definition of a symbol, we record the necessary parts of
|
||||
//! the current state for that symbol that we need for that use or definition. When we reach the
|
||||
//! end of the scope, it records the state for each symbol as the public definitions of that
|
||||
//! symbol.
|
||||
//!
|
||||
//! Let's walk through the above example. Initially we record for `x` that it has no visible
|
||||
//! definitions, and may be unbound. When we see `x = 1`, we record that as the sole visible
|
||||
//! definition of `x`, and flip `may_be_unbound` to `false`. Then we see `x = 2`, and it replaces
|
||||
//! `x = 1` as the sole visible definition of `x`. When we get to `y = x`, we record that the
|
||||
//! visible definitions for that use of `x` are just the `x = 2` definition.
|
||||
//! Let's walk through the above example. Initially we record for `x` that it has no bindings, and
|
||||
//! may be unbound. When we see `x = 1`, we record that as the sole live binding of `x`, and flip
|
||||
//! `may_be_unbound` to `false`. Then we see `x = 2`, and we replace `x = 1` as the sole live
|
||||
//! binding of `x`. When we get to `y = x`, we record that the live bindings for that use of `x`
|
||||
//! are just the `x = 2` definition.
|
||||
//!
|
||||
//! Then we hit the `if` branch. We visit the `test` node (`flag` in this case), since that will
|
||||
//! happen regardless. Then we take a pre-branch snapshot of the currently visible definitions for
|
||||
//! all symbols, which we'll need later. Then we record `flag` as a possible constraint on the
|
||||
//! currently visible definition (`x = 2`), and go ahead and visit the `if` body. When we see `x =
|
||||
//! 3`, it replaces `x = 2` (constrained by `flag`) as the sole visible definition of `x`. At the
|
||||
//! end of the `if` body, we take another snapshot of the currently-visible definitions; we'll call
|
||||
//! this the post-if-body snapshot.
|
||||
//! happen regardless. Then we take a pre-branch snapshot of the current state for all symbols,
|
||||
//! which we'll need later. Then we record `flag` as a possible constraint on the current binding
|
||||
//! (`x = 2`), and go ahead and visit the `if` body. When we see `x = 3`, it replaces `x = 2`
|
||||
//! (constrained by `flag`) as the sole live binding of `x`. At the end of the `if` body, we take
|
||||
//! another snapshot of the current symbol state; we'll call this the post-if-body snapshot.
|
||||
//!
|
||||
//! Now we need to visit the `else` clause. The conditions when entering the `else` clause should
|
||||
//! be the pre-if conditions; if we are entering the `else` clause, we know that the `if` test
|
||||
//! failed and we didn't execute the `if` body. So we first reset the builder to the pre-if state,
|
||||
//! using the snapshot we took previously (meaning we now have `x = 2` as the sole visible
|
||||
//! definition for `x` again), then visit the `else` clause, where `x = 4` replaces `x = 2` as the
|
||||
//! sole visible definition of `x`.
|
||||
//! using the snapshot we took previously (meaning we now have `x = 2` as the sole binding for `x`
|
||||
//! again), then visit the `else` clause, where `x = 4` replaces `x = 2` as the sole live binding
|
||||
//! of `x`.
|
||||
//!
|
||||
//! Now we reach the end of the if/else, and want to visit the following code. The state here needs
|
||||
//! to reflect that we might have gone through the `if` branch, or we might have gone through the
|
||||
//! `else` branch, and we don't know which. So we need to "merge" our current builder state
|
||||
//! (reflecting the end-of-else state, with `x = 4` as the only visible definition) with our
|
||||
//! post-if-body snapshot (which has `x = 3` as the only visible definition). The result of this
|
||||
//! merge is that we now have two visible definitions of `x`: `x = 3` and `x = 4`.
|
||||
//! (reflecting the end-of-else state, with `x = 4` as the only live binding) with our post-if-body
|
||||
//! snapshot (which has `x = 3` as the only live binding). The result of this merge is that we now
|
||||
//! have two live bindings of `x`: `x = 3` and `x = 4`.
|
||||
//!
|
||||
//! The [`UseDefMapBuilder`] itself just exposes methods for taking a snapshot, resetting to a
|
||||
//! snapshot, and merging a snapshot into the current state. The logic using these methods lives in
|
||||
//! [`SemanticIndexBuilder`](crate::semantic_index::builder::SemanticIndexBuilder), e.g. where it
|
||||
//! visits a `StmtIf` node.
|
||||
//!
|
||||
//! (In the future we may have some other questions we want to answer as well, such as "is this
|
||||
//! definition used?", which will require tracking a bit more info in our map, e.g. a "used" bit
|
||||
//! for each [`Definition`] which is flipped to true when we record that definition for a use.)
|
||||
use self::symbol_state::{
|
||||
ConstraintIdIterator, DefinitionIdWithConstraintsIterator, ScopedConstraintId,
|
||||
ScopedDefinitionId, SymbolState,
|
||||
BindingIdWithConstraintsIterator, ConstraintIdIterator, DeclarationIdIterator,
|
||||
ScopedConstraintId, ScopedDefinitionId, SymbolBindings, SymbolDeclarations, SymbolState,
|
||||
};
|
||||
use crate::semantic_index::ast_ids::ScopedUseId;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::symbol::ScopedSymbolId;
|
||||
use ruff_index::IndexVec;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use super::constraint::Constraint;
|
||||
|
||||
@@ -163,60 +245,132 @@ pub(crate) struct UseDefMap<'db> {
|
||||
/// Array of [`Constraint`] in this scope.
|
||||
all_constraints: IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
|
||||
/// [`SymbolState`] visible at a [`ScopedUseId`].
|
||||
definitions_by_use: IndexVec<ScopedUseId, SymbolState>,
|
||||
/// [`SymbolBindings`] reaching a [`ScopedUseId`].
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
|
||||
|
||||
/// [`SymbolBindings`] or [`SymbolDeclarations`] reaching a given [`Definition`].
|
||||
///
|
||||
/// If the definition is a binding (only) -- `x = 1` for example -- then we need
|
||||
/// [`SymbolDeclarations`] to know whether this binding is permitted by the live declarations.
|
||||
///
|
||||
/// If the definition is a declaration (only) -- `x: int` for example -- then we need
|
||||
/// [`SymbolBindings`] to know whether this declaration is consistent with the previously
|
||||
/// inferred type.
|
||||
///
|
||||
/// If the definition is both a declaration and a binding -- `x: int = 1` for example -- then
|
||||
/// we don't actually need anything here, all we'll need to validate is that our own RHS is a
|
||||
/// valid assignment to our own annotation.
|
||||
definitions_by_definition: FxHashMap<Definition<'db>, SymbolDefinitions>,
|
||||
|
||||
/// [`SymbolState`] visible at end of scope for each symbol.
|
||||
public_definitions: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
public_symbols: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
}
|
||||
|
||||
impl<'db> UseDefMap<'db> {
|
||||
pub(crate) fn use_definitions(
|
||||
pub(crate) fn bindings_at_use(
|
||||
&self,
|
||||
use_id: ScopedUseId,
|
||||
) -> DefinitionWithConstraintsIterator<'_, 'db> {
|
||||
DefinitionWithConstraintsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
all_constraints: &self.all_constraints,
|
||||
inner: self.definitions_by_use[use_id].visible_definitions(),
|
||||
}
|
||||
) -> BindingWithConstraintsIterator<'_, 'db> {
|
||||
self.bindings_iterator(&self.bindings_by_use[use_id])
|
||||
}
|
||||
|
||||
pub(crate) fn use_may_be_unbound(&self, use_id: ScopedUseId) -> bool {
|
||||
self.definitions_by_use[use_id].may_be_unbound()
|
||||
self.bindings_by_use[use_id].may_be_unbound()
|
||||
}
|
||||
|
||||
pub(crate) fn public_definitions(
|
||||
pub(crate) fn public_bindings(
|
||||
&self,
|
||||
symbol: ScopedSymbolId,
|
||||
) -> DefinitionWithConstraintsIterator<'_, 'db> {
|
||||
DefinitionWithConstraintsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
all_constraints: &self.all_constraints,
|
||||
inner: self.public_definitions[symbol].visible_definitions(),
|
||||
}
|
||||
) -> BindingWithConstraintsIterator<'_, 'db> {
|
||||
self.bindings_iterator(self.public_symbols[symbol].bindings())
|
||||
}
|
||||
|
||||
pub(crate) fn public_may_be_unbound(&self, symbol: ScopedSymbolId) -> bool {
|
||||
self.public_definitions[symbol].may_be_unbound()
|
||||
self.public_symbols[symbol].may_be_unbound()
|
||||
}
|
||||
|
||||
pub(crate) fn bindings_at_declaration(
|
||||
&self,
|
||||
declaration: Definition<'db>,
|
||||
) -> BindingWithConstraintsIterator<'_, 'db> {
|
||||
if let SymbolDefinitions::Bindings(bindings) = &self.definitions_by_definition[&declaration]
|
||||
{
|
||||
self.bindings_iterator(bindings)
|
||||
} else {
|
||||
unreachable!("Declaration has non-Bindings in definitions_by_definition");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn declarations_at_binding(
|
||||
&self,
|
||||
binding: Definition<'db>,
|
||||
) -> DeclarationsIterator<'_, 'db> {
|
||||
if let SymbolDefinitions::Declarations(declarations) =
|
||||
&self.definitions_by_definition[&binding]
|
||||
{
|
||||
self.declarations_iterator(declarations)
|
||||
} else {
|
||||
unreachable!("Binding has non-Declarations in definitions_by_definition");
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn public_declarations(
|
||||
&self,
|
||||
symbol: ScopedSymbolId,
|
||||
) -> DeclarationsIterator<'_, 'db> {
|
||||
let declarations = self.public_symbols[symbol].declarations();
|
||||
self.declarations_iterator(declarations)
|
||||
}
|
||||
|
||||
pub(crate) fn has_public_declarations(&self, symbol: ScopedSymbolId) -> bool {
|
||||
!self.public_symbols[symbol].declarations().is_empty()
|
||||
}
|
||||
|
||||
fn bindings_iterator<'a>(
|
||||
&'a self,
|
||||
bindings: &'a SymbolBindings,
|
||||
) -> BindingWithConstraintsIterator<'a, 'db> {
|
||||
BindingWithConstraintsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
all_constraints: &self.all_constraints,
|
||||
inner: bindings.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
fn declarations_iterator<'a>(
|
||||
&'a self,
|
||||
declarations: &'a SymbolDeclarations,
|
||||
) -> DeclarationsIterator<'a, 'db> {
|
||||
DeclarationsIterator {
|
||||
all_definitions: &self.all_definitions,
|
||||
inner: declarations.iter(),
|
||||
may_be_undeclared: declarations.may_be_undeclared(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DefinitionWithConstraintsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
inner: DefinitionIdWithConstraintsIterator<'map>,
|
||||
/// Either live bindings or live declarations for a symbol.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum SymbolDefinitions {
|
||||
Bindings(SymbolBindings),
|
||||
Declarations(SymbolDeclarations),
|
||||
}
|
||||
|
||||
impl<'map, 'db> Iterator for DefinitionWithConstraintsIterator<'map, 'db> {
|
||||
type Item = DefinitionWithConstraints<'map, 'db>;
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct BindingWithConstraintsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
all_constraints: &'map IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
inner: BindingIdWithConstraintsIterator<'map>,
|
||||
}
|
||||
|
||||
impl<'map, 'db> Iterator for BindingWithConstraintsIterator<'map, 'db> {
|
||||
type Item = BindingWithConstraints<'map, 'db>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner
|
||||
.next()
|
||||
.map(|def_id_with_constraints| DefinitionWithConstraints {
|
||||
definition: self.all_definitions[def_id_with_constraints.definition],
|
||||
.map(|def_id_with_constraints| BindingWithConstraints {
|
||||
binding: self.all_definitions[def_id_with_constraints.definition],
|
||||
constraints: ConstraintsIterator {
|
||||
all_constraints: self.all_constraints,
|
||||
constraint_ids: def_id_with_constraints.constraint_ids,
|
||||
@@ -225,10 +379,10 @@ impl<'map, 'db> Iterator for DefinitionWithConstraintsIterator<'map, 'db> {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for DefinitionWithConstraintsIterator<'_, '_> {}
|
||||
impl std::iter::FusedIterator for BindingWithConstraintsIterator<'_, '_> {}
|
||||
|
||||
pub(crate) struct DefinitionWithConstraints<'map, 'db> {
|
||||
pub(crate) definition: Definition<'db>,
|
||||
pub(crate) struct BindingWithConstraints<'map, 'db> {
|
||||
pub(crate) binding: Definition<'db>,
|
||||
pub(crate) constraints: ConstraintsIterator<'map, 'db>,
|
||||
}
|
||||
|
||||
@@ -249,25 +403,50 @@ impl<'map, 'db> Iterator for ConstraintsIterator<'map, 'db> {
|
||||
|
||||
impl std::iter::FusedIterator for ConstraintsIterator<'_, '_> {}
|
||||
|
||||
pub(crate) struct DeclarationsIterator<'map, 'db> {
|
||||
all_definitions: &'map IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
inner: DeclarationIdIterator<'map>,
|
||||
may_be_undeclared: bool,
|
||||
}
|
||||
|
||||
impl DeclarationsIterator<'_, '_> {
|
||||
pub(crate) fn may_be_undeclared(&self) -> bool {
|
||||
self.may_be_undeclared
|
||||
}
|
||||
}
|
||||
|
||||
impl<'map, 'db> Iterator for DeclarationsIterator<'map, 'db> {
|
||||
type Item = Definition<'db>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(|def_id| self.all_definitions[def_id])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for DeclarationsIterator<'_, '_> {}
|
||||
|
||||
/// A snapshot of the definitions and constraints state at a particular point in control flow.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct FlowSnapshot {
|
||||
definitions_by_symbol: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct UseDefMapBuilder<'db> {
|
||||
/// Append-only array of [`Definition`]; None is unbound.
|
||||
/// Append-only array of [`Definition`].
|
||||
all_definitions: IndexVec<ScopedDefinitionId, Definition<'db>>,
|
||||
|
||||
/// Append-only array of [`Constraint`].
|
||||
all_constraints: IndexVec<ScopedConstraintId, Constraint<'db>>,
|
||||
|
||||
/// Visible definitions at each so-far-recorded use.
|
||||
definitions_by_use: IndexVec<ScopedUseId, SymbolState>,
|
||||
/// Live bindings at each so-far-recorded use.
|
||||
bindings_by_use: IndexVec<ScopedUseId, SymbolBindings>,
|
||||
|
||||
/// Currently visible definitions for each symbol.
|
||||
definitions_by_symbol: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
/// Live bindings or declarations for each so-far-recorded definition.
|
||||
definitions_by_definition: FxHashMap<Definition<'db>, SymbolDefinitions>,
|
||||
|
||||
/// Currently live bindings and declarations for each symbol.
|
||||
symbol_states: IndexVec<ScopedSymbolId, SymbolState>,
|
||||
}
|
||||
|
||||
impl<'db> UseDefMapBuilder<'db> {
|
||||
@@ -276,86 +455,104 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
}
|
||||
|
||||
pub(super) fn add_symbol(&mut self, symbol: ScopedSymbolId) {
|
||||
let new_symbol = self.definitions_by_symbol.push(SymbolState::unbound());
|
||||
let new_symbol = self.symbol_states.push(SymbolState::undefined());
|
||||
debug_assert_eq!(symbol, new_symbol);
|
||||
}
|
||||
|
||||
pub(super) fn record_definition(
|
||||
&mut self,
|
||||
symbol: ScopedSymbolId,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
// We have a new definition of a symbol; this replaces any previous definitions in this
|
||||
// path.
|
||||
let def_id = self.all_definitions.push(definition);
|
||||
self.definitions_by_symbol[symbol] = SymbolState::with(def_id);
|
||||
pub(super) fn record_binding(&mut self, symbol: ScopedSymbolId, binding: Definition<'db>) {
|
||||
let def_id = self.all_definitions.push(binding);
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
self.definitions_by_definition.insert(
|
||||
binding,
|
||||
SymbolDefinitions::Declarations(symbol_state.declarations().clone()),
|
||||
);
|
||||
symbol_state.record_binding(def_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_constraint(&mut self, constraint: Constraint<'db>) {
|
||||
let constraint_id = self.all_constraints.push(constraint);
|
||||
for definitions in &mut self.definitions_by_symbol {
|
||||
definitions.add_constraint(constraint_id);
|
||||
for state in &mut self.symbol_states {
|
||||
state.record_constraint(constraint_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration(
|
||||
&mut self,
|
||||
symbol: ScopedSymbolId,
|
||||
declaration: Definition<'db>,
|
||||
) {
|
||||
let def_id = self.all_definitions.push(declaration);
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
self.definitions_by_definition.insert(
|
||||
declaration,
|
||||
SymbolDefinitions::Bindings(symbol_state.bindings().clone()),
|
||||
);
|
||||
symbol_state.record_declaration(def_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration_and_binding(
|
||||
&mut self,
|
||||
symbol: ScopedSymbolId,
|
||||
definition: Definition<'db>,
|
||||
) {
|
||||
// We don't need to store anything in self.definitions_by_definition.
|
||||
let def_id = self.all_definitions.push(definition);
|
||||
let symbol_state = &mut self.symbol_states[symbol];
|
||||
symbol_state.record_declaration(def_id);
|
||||
symbol_state.record_binding(def_id);
|
||||
}
|
||||
|
||||
pub(super) fn record_use(&mut self, symbol: ScopedSymbolId, use_id: ScopedUseId) {
|
||||
// We have a use of a symbol; clone the currently visible definitions for that symbol, and
|
||||
// record them as the visible definitions for this use.
|
||||
// We have a use of a symbol; clone the current bindings for that symbol, and record them
|
||||
// as the live bindings for this use.
|
||||
let new_use = self
|
||||
.definitions_by_use
|
||||
.push(self.definitions_by_symbol[symbol].clone());
|
||||
.bindings_by_use
|
||||
.push(self.symbol_states[symbol].bindings().clone());
|
||||
debug_assert_eq!(use_id, new_use);
|
||||
}
|
||||
|
||||
/// Take a snapshot of the current visible-symbols state.
|
||||
pub(super) fn snapshot(&self) -> FlowSnapshot {
|
||||
FlowSnapshot {
|
||||
definitions_by_symbol: self.definitions_by_symbol.clone(),
|
||||
symbol_states: self.symbol_states.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore the current builder visible-definitions state to the given snapshot.
|
||||
/// Restore the current builder symbols state to the given snapshot.
|
||||
pub(super) fn restore(&mut self, snapshot: FlowSnapshot) {
|
||||
// We never remove symbols from `definitions_by_symbol` (it's an IndexVec, and the symbol
|
||||
// We never remove symbols from `symbol_states` (it's an IndexVec, and the symbol
|
||||
// IDs must line up), so the current number of known symbols must always be equal to or
|
||||
// greater than the number of known symbols in a previously-taken snapshot.
|
||||
let num_symbols = self.definitions_by_symbol.len();
|
||||
debug_assert!(num_symbols >= snapshot.definitions_by_symbol.len());
|
||||
let num_symbols = self.symbol_states.len();
|
||||
debug_assert!(num_symbols >= snapshot.symbol_states.len());
|
||||
|
||||
// Restore the current visible-definitions state to the given snapshot.
|
||||
self.definitions_by_symbol = snapshot.definitions_by_symbol;
|
||||
self.symbol_states = snapshot.symbol_states;
|
||||
|
||||
// If the snapshot we are restoring is missing some symbols we've recorded since, we need
|
||||
// to fill them in so the symbol IDs continue to line up. Since they don't exist in the
|
||||
// snapshot, the correct state to fill them in with is "unbound".
|
||||
self.definitions_by_symbol
|
||||
.resize(num_symbols, SymbolState::unbound());
|
||||
// snapshot, the correct state to fill them in with is "undefined".
|
||||
self.symbol_states
|
||||
.resize(num_symbols, SymbolState::undefined());
|
||||
}
|
||||
|
||||
/// Merge the given snapshot into the current state, reflecting that we might have taken either
|
||||
/// path to get here. The new visible-definitions state for each symbol should include
|
||||
/// definitions from both the prior state and the snapshot.
|
||||
/// path to get here. The new state for each symbol should include definitions from both the
|
||||
/// prior state and the snapshot.
|
||||
pub(super) fn merge(&mut self, snapshot: FlowSnapshot) {
|
||||
// The tricky thing about merging two Ranges pointing into `all_definitions` is that if the
|
||||
// two Ranges aren't already adjacent in `all_definitions`, we will have to copy at least
|
||||
// one or the other of the ranges to the end of `all_definitions` so as to make them
|
||||
// adjacent. We can't ever move things around in `all_definitions` because previously
|
||||
// recorded uses may still have ranges pointing to any part of it; all we can do is append.
|
||||
// It's possible we may end up with some old entries in `all_definitions` that nobody is
|
||||
// pointing to, but that's OK.
|
||||
|
||||
// We never remove symbols from `definitions_by_symbol` (it's an IndexVec, and the symbol
|
||||
// We never remove symbols from `symbol_states` (it's an IndexVec, and the symbol
|
||||
// IDs must line up), so the current number of known symbols must always be equal to or
|
||||
// greater than the number of known symbols in a previously-taken snapshot.
|
||||
debug_assert!(self.definitions_by_symbol.len() >= snapshot.definitions_by_symbol.len());
|
||||
debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len());
|
||||
|
||||
let mut snapshot_definitions_iter = snapshot.definitions_by_symbol.into_iter();
|
||||
for current in &mut self.definitions_by_symbol {
|
||||
let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter();
|
||||
for current in &mut self.symbol_states {
|
||||
if let Some(snapshot) = snapshot_definitions_iter.next() {
|
||||
current.merge(snapshot);
|
||||
} else {
|
||||
// Symbol not present in snapshot, so it's unbound from that path.
|
||||
current.add_unbound();
|
||||
// Symbol not present in snapshot, so it's unbound/undeclared from that path.
|
||||
current.set_may_be_unbound();
|
||||
current.set_may_be_undeclared();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,14 +560,16 @@ impl<'db> UseDefMapBuilder<'db> {
|
||||
pub(super) fn finish(mut self) -> UseDefMap<'db> {
|
||||
self.all_definitions.shrink_to_fit();
|
||||
self.all_constraints.shrink_to_fit();
|
||||
self.definitions_by_symbol.shrink_to_fit();
|
||||
self.definitions_by_use.shrink_to_fit();
|
||||
self.symbol_states.shrink_to_fit();
|
||||
self.bindings_by_use.shrink_to_fit();
|
||||
self.definitions_by_definition.shrink_to_fit();
|
||||
|
||||
UseDefMap {
|
||||
all_definitions: self.all_definitions,
|
||||
all_constraints: self.all_constraints,
|
||||
definitions_by_use: self.definitions_by_use,
|
||||
public_definitions: self.definitions_by_symbol,
|
||||
bindings_by_use: self.bindings_by_use,
|
||||
public_symbols: self.symbol_states,
|
||||
definitions_by_definition: self.definitions_by_definition,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ impl<const B: usize> BitSet<B> {
|
||||
bitset
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
self.blocks().iter().all(|&b| b == 0)
|
||||
}
|
||||
@@ -99,7 +98,6 @@ impl<const B: usize> BitSet<B> {
|
||||
}
|
||||
|
||||
/// Union in-place with another [`BitSet`].
|
||||
#[allow(unused)]
|
||||
pub(super) fn union(&mut self, other: &BitSet<B>) {
|
||||
let mut max_len = self.blocks().len();
|
||||
let other_len = other.blocks().len();
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
//! Track visible definitions of a symbol, and applicable constraints per definition.
|
||||
//! Track live bindings per symbol, applicable constraints per binding, and live declarations.
|
||||
//!
|
||||
//! These data structures operate entirely on scope-local newtype-indices for definitions and
|
||||
//! constraints, referring to their location in the `all_definitions` and `all_constraints`
|
||||
//! indexvecs in [`super::UseDefMapBuilder`].
|
||||
//!
|
||||
//! We need to track arbitrary associations between definitions and constraints, not just a single
|
||||
//! set of currently dominating constraints (where "dominating" means "control flow must have
|
||||
//! passed through it to reach this point"), because we can have dominating constraints that apply
|
||||
//! to some definitions but not others, as in this code:
|
||||
//! We need to track arbitrary associations between bindings and constraints, not just a single set
|
||||
//! of currently dominating constraints (where "dominating" means "control flow must have passed
|
||||
//! through it to reach this point"), because we can have dominating constraints that apply to some
|
||||
//! bindings but not others, as in this code:
|
||||
//!
|
||||
//! ```python
|
||||
//! x = 1 if flag else None
|
||||
@@ -18,11 +18,11 @@
|
||||
//! ```
|
||||
//!
|
||||
//! The `x is not None` constraint dominates the final use of `x`, but it applies only to the first
|
||||
//! definition of `x`, not the second, so `None` is a possible value for `x`.
|
||||
//! binding of `x`, not the second, so `None` is a possible value for `x`.
|
||||
//!
|
||||
//! And we can't just track, for each definition, an index into a list of dominating constraints,
|
||||
//! either, because we can have definitions which are still visible, but subject to constraints
|
||||
//! that are no longer dominating, as in this code:
|
||||
//! And we can't just track, for each binding, an index into a list of dominating constraints,
|
||||
//! either, because we can have bindings which are still visible, but subject to constraints that
|
||||
//! are no longer dominating, as in this code:
|
||||
//!
|
||||
//! ```python
|
||||
//! x = 0
|
||||
@@ -33,13 +33,16 @@
|
||||
//! ```
|
||||
//!
|
||||
//! From the point of view of the final use of `x`, the `x is not None` constraint no longer
|
||||
//! dominates, but it does dominate the `x = 1 if flag2 else None` definition, so we have to keep
|
||||
//! dominates, but it does dominate the `x = 1 if flag2 else None` binding, so we have to keep
|
||||
//! track of that.
|
||||
//!
|
||||
//! The data structures used here ([`BitSet`] and [`smallvec::SmallVec`]) optimize for keeping all
|
||||
//! data inline (avoiding lots of scattered allocations) in small-to-medium cases, and falling back
|
||||
//! to heap allocation to be able to scale to arbitrary numbers of definitions and constraints when
|
||||
//! needed.
|
||||
//! to heap allocation to be able to scale to arbitrary numbers of live bindings and constraints
|
||||
//! when needed.
|
||||
//!
|
||||
//! Tracking live declarations is simpler, since constraints are not involved, but otherwise very
|
||||
//! similar to tracking live bindings.
|
||||
use super::bitset::{BitSet, BitSetIterator};
|
||||
use ruff_index::newtype_index;
|
||||
use smallvec::SmallVec;
|
||||
@@ -53,93 +56,200 @@ pub(super) struct ScopedDefinitionId;
|
||||
pub(super) struct ScopedConstraintId;
|
||||
|
||||
/// Can reference this * 64 total definitions inline; more will fall back to the heap.
|
||||
const INLINE_DEFINITION_BLOCKS: usize = 3;
|
||||
const INLINE_BINDING_BLOCKS: usize = 3;
|
||||
|
||||
/// A [`BitSet`] of [`ScopedDefinitionId`], representing visible definitions of a symbol in a scope.
|
||||
type Definitions = BitSet<INLINE_DEFINITION_BLOCKS>;
|
||||
type DefinitionsIterator<'a> = BitSetIterator<'a, INLINE_DEFINITION_BLOCKS>;
|
||||
/// A [`BitSet`] of [`ScopedDefinitionId`], representing live bindings of a symbol in a scope.
|
||||
type Bindings = BitSet<INLINE_BINDING_BLOCKS>;
|
||||
type BindingsIterator<'a> = BitSetIterator<'a, INLINE_BINDING_BLOCKS>;
|
||||
|
||||
/// Can reference this * 64 total declarations inline; more will fall back to the heap.
|
||||
const INLINE_DECLARATION_BLOCKS: usize = 3;
|
||||
|
||||
/// A [`BitSet`] of [`ScopedDefinitionId`], representing live declarations of a symbol in a scope.
|
||||
type Declarations = BitSet<INLINE_DECLARATION_BLOCKS>;
|
||||
type DeclarationsIterator<'a> = BitSetIterator<'a, INLINE_DECLARATION_BLOCKS>;
|
||||
|
||||
/// Can reference this * 64 total constraints inline; more will fall back to the heap.
|
||||
const INLINE_CONSTRAINT_BLOCKS: usize = 2;
|
||||
|
||||
/// Can keep inline this many visible definitions per symbol at a given time; more will go to heap.
|
||||
const INLINE_VISIBLE_DEFINITIONS_PER_SYMBOL: usize = 4;
|
||||
/// Can keep inline this many live bindings per symbol at a given time; more will go to heap.
|
||||
const INLINE_BINDINGS_PER_SYMBOL: usize = 4;
|
||||
|
||||
/// One [`BitSet`] of applicable [`ScopedConstraintId`] per visible definition.
|
||||
type InlineConstraintArray =
|
||||
[BitSet<INLINE_CONSTRAINT_BLOCKS>; INLINE_VISIBLE_DEFINITIONS_PER_SYMBOL];
|
||||
/// One [`BitSet`] of applicable [`ScopedConstraintId`] per live binding.
|
||||
type InlineConstraintArray = [BitSet<INLINE_CONSTRAINT_BLOCKS>; INLINE_BINDINGS_PER_SYMBOL];
|
||||
type Constraints = SmallVec<InlineConstraintArray>;
|
||||
type ConstraintsIterator<'a> = std::slice::Iter<'a, BitSet<INLINE_CONSTRAINT_BLOCKS>>;
|
||||
type ConstraintsIntoIterator = smallvec::IntoIter<InlineConstraintArray>;
|
||||
|
||||
/// Visible definitions and narrowing constraints for a single symbol at some point in control flow.
|
||||
/// Live declarations for a single symbol at some point in control flow.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct SymbolState {
|
||||
/// [`BitSet`]: which [`ScopedDefinitionId`] are visible for this symbol?
|
||||
visible_definitions: Definitions,
|
||||
pub(super) struct SymbolDeclarations {
|
||||
/// [`BitSet`]: which declarations (as [`ScopedDefinitionId`]) can reach the current location?
|
||||
live_declarations: Declarations,
|
||||
|
||||
/// For each definition, which [`ScopedConstraintId`] apply?
|
||||
/// Could the symbol be un-declared at this point?
|
||||
may_be_undeclared: bool,
|
||||
}
|
||||
|
||||
impl SymbolDeclarations {
|
||||
fn undeclared() -> Self {
|
||||
Self {
|
||||
live_declarations: Declarations::default(),
|
||||
may_be_undeclared: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration for this symbol.
|
||||
fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
|
||||
self.live_declarations = Declarations::with(declaration_id.into());
|
||||
self.may_be_undeclared = false;
|
||||
}
|
||||
|
||||
/// Add undeclared as a possibility for this symbol.
|
||||
fn set_may_be_undeclared(&mut self) {
|
||||
self.may_be_undeclared = true;
|
||||
}
|
||||
|
||||
/// Return an iterator over live declarations for this symbol.
|
||||
pub(super) fn iter(&self) -> DeclarationIdIterator {
|
||||
DeclarationIdIterator {
|
||||
inner: self.live_declarations.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
self.live_declarations.is_empty()
|
||||
}
|
||||
|
||||
pub(super) fn may_be_undeclared(&self) -> bool {
|
||||
self.may_be_undeclared
|
||||
}
|
||||
}
|
||||
|
||||
/// Live bindings and narrowing constraints for a single symbol at some point in control flow.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct SymbolBindings {
|
||||
/// [`BitSet`]: which bindings (as [`ScopedDefinitionId`]) can reach the current location?
|
||||
live_bindings: Bindings,
|
||||
|
||||
/// For each live binding, which [`ScopedConstraintId`] apply?
|
||||
///
|
||||
/// This is a [`smallvec::SmallVec`] which should always have one [`BitSet`] of constraints per
|
||||
/// definition in `visible_definitions`.
|
||||
/// binding in `live_bindings`.
|
||||
constraints: Constraints,
|
||||
|
||||
/// Could the symbol be unbound at this point?
|
||||
may_be_unbound: bool,
|
||||
}
|
||||
|
||||
/// A single [`ScopedDefinitionId`] with an iterator of its applicable [`ScopedConstraintId`].
|
||||
#[derive(Debug)]
|
||||
pub(super) struct DefinitionIdWithConstraints<'a> {
|
||||
pub(super) definition: ScopedDefinitionId,
|
||||
pub(super) constraint_ids: ConstraintIdIterator<'a>,
|
||||
}
|
||||
|
||||
impl SymbolState {
|
||||
/// Return a new [`SymbolState`] representing an unbound symbol.
|
||||
pub(super) fn unbound() -> Self {
|
||||
impl SymbolBindings {
|
||||
fn unbound() -> Self {
|
||||
Self {
|
||||
visible_definitions: Definitions::default(),
|
||||
live_bindings: Bindings::default(),
|
||||
constraints: Constraints::default(),
|
||||
may_be_unbound: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a new [`SymbolState`] representing a symbol with a single visible definition.
|
||||
pub(super) fn with(definition_id: ScopedDefinitionId) -> Self {
|
||||
let mut constraints = Constraints::with_capacity(1);
|
||||
constraints.push(BitSet::default());
|
||||
Self {
|
||||
visible_definitions: Definitions::with(definition_id.into()),
|
||||
constraints,
|
||||
may_be_unbound: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add Unbound as a possibility for this symbol.
|
||||
pub(super) fn add_unbound(&mut self) {
|
||||
fn set_may_be_unbound(&mut self) {
|
||||
self.may_be_unbound = true;
|
||||
}
|
||||
|
||||
/// Add given constraint to all currently-visible definitions.
|
||||
pub(super) fn add_constraint(&mut self, constraint_id: ScopedConstraintId) {
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
|
||||
// The new binding replaces all previous live bindings in this path, and has no
|
||||
// constraints.
|
||||
self.live_bindings = Bindings::with(binding_id.into());
|
||||
self.constraints = Constraints::with_capacity(1);
|
||||
self.constraints.push(BitSet::default());
|
||||
self.may_be_unbound = false;
|
||||
}
|
||||
|
||||
/// Add given constraint to all live bindings.
|
||||
pub(super) fn record_constraint(&mut self, constraint_id: ScopedConstraintId) {
|
||||
for bitset in &mut self.constraints {
|
||||
bitset.insert(constraint_id.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over currently live bindings for this symbol.
|
||||
pub(super) fn iter(&self) -> BindingIdWithConstraintsIterator {
|
||||
BindingIdWithConstraintsIterator {
|
||||
definitions: self.live_bindings.iter(),
|
||||
constraints: self.constraints.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn may_be_unbound(&self) -> bool {
|
||||
self.may_be_unbound
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct SymbolState {
|
||||
declarations: SymbolDeclarations,
|
||||
bindings: SymbolBindings,
|
||||
}
|
||||
|
||||
impl SymbolState {
|
||||
/// Return a new [`SymbolState`] representing an unbound, undeclared symbol.
|
||||
pub(super) fn undefined() -> Self {
|
||||
Self {
|
||||
declarations: SymbolDeclarations::undeclared(),
|
||||
bindings: SymbolBindings::unbound(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add Unbound as a possibility for this symbol.
|
||||
pub(super) fn set_may_be_unbound(&mut self) {
|
||||
self.bindings.set_may_be_unbound();
|
||||
}
|
||||
|
||||
/// Record a newly-encountered binding for this symbol.
|
||||
pub(super) fn record_binding(&mut self, binding_id: ScopedDefinitionId) {
|
||||
self.bindings.record_binding(binding_id);
|
||||
}
|
||||
|
||||
/// Add given constraint to all live bindings.
|
||||
pub(super) fn record_constraint(&mut self, constraint_id: ScopedConstraintId) {
|
||||
self.bindings.record_constraint(constraint_id);
|
||||
}
|
||||
|
||||
/// Add undeclared as a possibility for this symbol.
|
||||
pub(super) fn set_may_be_undeclared(&mut self) {
|
||||
self.declarations.set_may_be_undeclared();
|
||||
}
|
||||
|
||||
/// Record a newly-encountered declaration of this symbol.
|
||||
pub(super) fn record_declaration(&mut self, declaration_id: ScopedDefinitionId) {
|
||||
self.declarations.record_declaration(declaration_id);
|
||||
}
|
||||
|
||||
/// Merge another [`SymbolState`] into this one.
|
||||
pub(super) fn merge(&mut self, b: SymbolState) {
|
||||
let mut a = Self {
|
||||
visible_definitions: Definitions::default(),
|
||||
constraints: Constraints::default(),
|
||||
may_be_unbound: self.may_be_unbound || b.may_be_unbound,
|
||||
bindings: SymbolBindings {
|
||||
live_bindings: Bindings::default(),
|
||||
constraints: Constraints::default(),
|
||||
may_be_unbound: self.bindings.may_be_unbound || b.bindings.may_be_unbound,
|
||||
},
|
||||
declarations: SymbolDeclarations {
|
||||
live_declarations: self.declarations.live_declarations.clone(),
|
||||
may_be_undeclared: self.declarations.may_be_undeclared
|
||||
|| b.declarations.may_be_undeclared,
|
||||
},
|
||||
};
|
||||
|
||||
std::mem::swap(&mut a, self);
|
||||
let mut a_defs_iter = a.visible_definitions.iter();
|
||||
let mut b_defs_iter = b.visible_definitions.iter();
|
||||
let mut a_constraints_iter = a.constraints.into_iter();
|
||||
let mut b_constraints_iter = b.constraints.into_iter();
|
||||
self.declarations
|
||||
.live_declarations
|
||||
.union(&b.declarations.live_declarations);
|
||||
|
||||
let mut a_defs_iter = a.bindings.live_bindings.iter();
|
||||
let mut b_defs_iter = b.bindings.live_bindings.iter();
|
||||
let mut a_constraints_iter = a.bindings.constraints.into_iter();
|
||||
let mut b_constraints_iter = b.bindings.constraints.into_iter();
|
||||
|
||||
let mut opt_a_def: Option<u32> = a_defs_iter.next();
|
||||
let mut opt_b_def: Option<u32> = b_defs_iter.next();
|
||||
@@ -152,7 +262,7 @@ impl SymbolState {
|
||||
|
||||
// Helper to push `def`, with constraints in `constraints_iter`, onto `self`.
|
||||
let push = |def, constraints_iter: &mut ConstraintsIntoIterator, merged: &mut Self| {
|
||||
merged.visible_definitions.insert(def);
|
||||
merged.bindings.live_bindings.insert(def);
|
||||
// SAFETY: we only ever create SymbolState with either no definitions and no constraint
|
||||
// bitsets (`::unbound`) or one definition and one constraint bitset (`::with`), and
|
||||
// `::merge` always pushes one definition and one constraint bitset together (just
|
||||
@@ -161,7 +271,7 @@ impl SymbolState {
|
||||
let constraints = constraints_iter
|
||||
.next()
|
||||
.expect("definitions and constraints length mismatch");
|
||||
merged.constraints.push(constraints);
|
||||
merged.bindings.constraints.push(constraints);
|
||||
};
|
||||
|
||||
loop {
|
||||
@@ -191,7 +301,8 @@ impl SymbolState {
|
||||
// If the same definition is visible through both paths, any constraint
|
||||
// that applies on only one path is irrelevant to the resulting type from
|
||||
// unioning the two paths, so we intersect the constraints.
|
||||
self.constraints
|
||||
self.bindings
|
||||
.constraints
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.intersect(&a_constraints);
|
||||
@@ -214,40 +325,49 @@ impl SymbolState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get iterator over visible definitions with constraints.
|
||||
pub(super) fn visible_definitions(&self) -> DefinitionIdWithConstraintsIterator {
|
||||
DefinitionIdWithConstraintsIterator {
|
||||
definitions: self.visible_definitions.iter(),
|
||||
constraints: self.constraints.iter(),
|
||||
}
|
||||
pub(super) fn bindings(&self) -> &SymbolBindings {
|
||||
&self.bindings
|
||||
}
|
||||
|
||||
pub(super) fn declarations(&self) -> &SymbolDeclarations {
|
||||
&self.declarations
|
||||
}
|
||||
|
||||
/// Could the symbol be unbound?
|
||||
pub(super) fn may_be_unbound(&self) -> bool {
|
||||
self.may_be_unbound
|
||||
self.bindings.may_be_unbound()
|
||||
}
|
||||
}
|
||||
|
||||
/// The default state of a symbol (if we've seen no definitions of it) is unbound.
|
||||
/// The default state of a symbol, if we've seen no definitions of it, is undefined (that is,
|
||||
/// both unbound and undeclared).
|
||||
impl Default for SymbolState {
|
||||
fn default() -> Self {
|
||||
SymbolState::unbound()
|
||||
SymbolState::undefined()
|
||||
}
|
||||
}
|
||||
|
||||
/// A single binding (as [`ScopedDefinitionId`]) with an iterator of its applicable
|
||||
/// [`ScopedConstraintId`].
|
||||
#[derive(Debug)]
|
||||
pub(super) struct BindingIdWithConstraints<'a> {
|
||||
pub(super) definition: ScopedDefinitionId,
|
||||
pub(super) constraint_ids: ConstraintIdIterator<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct DefinitionIdWithConstraintsIterator<'a> {
|
||||
definitions: DefinitionsIterator<'a>,
|
||||
pub(super) struct BindingIdWithConstraintsIterator<'a> {
|
||||
definitions: BindingsIterator<'a>,
|
||||
constraints: ConstraintsIterator<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DefinitionIdWithConstraintsIterator<'a> {
|
||||
type Item = DefinitionIdWithConstraints<'a>;
|
||||
impl<'a> Iterator for BindingIdWithConstraintsIterator<'a> {
|
||||
type Item = BindingIdWithConstraints<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match (self.definitions.next(), self.constraints.next()) {
|
||||
(None, None) => None,
|
||||
(Some(def), Some(constraints)) => Some(DefinitionIdWithConstraints {
|
||||
(Some(def), Some(constraints)) => Some(BindingIdWithConstraints {
|
||||
definition: ScopedDefinitionId::from_u32(def),
|
||||
constraint_ids: ConstraintIdIterator {
|
||||
wrapped: constraints.iter(),
|
||||
@@ -259,7 +379,7 @@ impl<'a> Iterator for DefinitionIdWithConstraintsIterator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for DefinitionIdWithConstraintsIterator<'_> {}
|
||||
impl std::iter::FusedIterator for BindingIdWithConstraintsIterator<'_> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ConstraintIdIterator<'a> {
|
||||
@@ -276,99 +396,193 @@ impl Iterator for ConstraintIdIterator<'_> {
|
||||
|
||||
impl std::iter::FusedIterator for ConstraintIdIterator<'_> {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct DeclarationIdIterator<'a> {
|
||||
inner: DeclarationsIterator<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DeclarationIdIterator<'a> {
|
||||
type Item = ScopedDefinitionId;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(ScopedDefinitionId::from_u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FusedIterator for DeclarationIdIterator<'_> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ScopedConstraintId, ScopedDefinitionId, SymbolState};
|
||||
|
||||
impl SymbolState {
|
||||
pub(crate) fn assert(&self, may_be_unbound: bool, expected: &[&str]) {
|
||||
assert_eq!(self.may_be_unbound(), may_be_unbound);
|
||||
let actual = self
|
||||
.visible_definitions()
|
||||
.map(|def_id_with_constraints| {
|
||||
format!(
|
||||
"{}<{}>",
|
||||
def_id_with_constraints.definition.as_u32(),
|
||||
def_id_with_constraints
|
||||
.constraint_ids
|
||||
.map(ScopedConstraintId::as_u32)
|
||||
.map(|idx| idx.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
fn assert_bindings(symbol: &SymbolState, may_be_unbound: bool, expected: &[&str]) {
|
||||
assert_eq!(symbol.may_be_unbound(), may_be_unbound);
|
||||
let actual = symbol
|
||||
.bindings()
|
||||
.iter()
|
||||
.map(|def_id_with_constraints| {
|
||||
format!(
|
||||
"{}<{}>",
|
||||
def_id_with_constraints.definition.as_u32(),
|
||||
def_id_with_constraints
|
||||
.constraint_ids
|
||||
.map(ScopedConstraintId::as_u32)
|
||||
.map(|idx| idx.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
pub(crate) fn assert_declarations(
|
||||
symbol: &SymbolState,
|
||||
may_be_undeclared: bool,
|
||||
expected: &[u32],
|
||||
) {
|
||||
assert_eq!(symbol.declarations.may_be_undeclared(), may_be_undeclared);
|
||||
let actual = symbol
|
||||
.declarations()
|
||||
.iter()
|
||||
.map(ScopedDefinitionId::as_u32)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unbound() {
|
||||
let cd = SymbolState::unbound();
|
||||
let sym = SymbolState::undefined();
|
||||
|
||||
cd.assert(true, &[]);
|
||||
assert_bindings(&sym, true, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with() {
|
||||
let cd = SymbolState::with(ScopedDefinitionId::from_u32(0));
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
|
||||
cd.assert(false, &["0<>"]);
|
||||
assert_bindings(&sym, false, &["0<>"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_unbound() {
|
||||
let mut cd = SymbolState::with(ScopedDefinitionId::from_u32(0));
|
||||
cd.add_unbound();
|
||||
fn set_may_be_unbound() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym.set_may_be_unbound();
|
||||
|
||||
cd.assert(true, &["0<>"]);
|
||||
assert_bindings(&sym, true, &["0<>"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_constraint() {
|
||||
let mut cd = SymbolState::with(ScopedDefinitionId::from_u32(0));
|
||||
cd.add_constraint(ScopedConstraintId::from_u32(0));
|
||||
fn record_constraint() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
cd.assert(false, &["0<0>"]);
|
||||
assert_bindings(&sym, false, &["0<0>"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge() {
|
||||
// merging the same definition with the same constraint keeps the constraint
|
||||
let mut cd0a = SymbolState::with(ScopedDefinitionId::from_u32(0));
|
||||
cd0a.add_constraint(ScopedConstraintId::from_u32(0));
|
||||
let mut sym0a = SymbolState::undefined();
|
||||
sym0a.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym0a.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
let mut cd0b = SymbolState::with(ScopedDefinitionId::from_u32(0));
|
||||
cd0b.add_constraint(ScopedConstraintId::from_u32(0));
|
||||
let mut sym0b = SymbolState::undefined();
|
||||
sym0b.record_binding(ScopedDefinitionId::from_u32(0));
|
||||
sym0b.record_constraint(ScopedConstraintId::from_u32(0));
|
||||
|
||||
cd0a.merge(cd0b);
|
||||
let mut cd0 = cd0a;
|
||||
cd0.assert(false, &["0<0>"]);
|
||||
sym0a.merge(sym0b);
|
||||
let mut sym0 = sym0a;
|
||||
assert_bindings(&sym0, false, &["0<0>"]);
|
||||
|
||||
// merging the same definition with differing constraints drops all constraints
|
||||
let mut cd1a = SymbolState::with(ScopedDefinitionId::from_u32(1));
|
||||
cd1a.add_constraint(ScopedConstraintId::from_u32(1));
|
||||
let mut sym1a = SymbolState::undefined();
|
||||
sym1a.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
sym1a.record_constraint(ScopedConstraintId::from_u32(1));
|
||||
|
||||
let mut cd1b = SymbolState::with(ScopedDefinitionId::from_u32(1));
|
||||
cd1b.add_constraint(ScopedConstraintId::from_u32(2));
|
||||
let mut sym1b = SymbolState::undefined();
|
||||
sym1b.record_binding(ScopedDefinitionId::from_u32(1));
|
||||
sym1b.record_constraint(ScopedConstraintId::from_u32(2));
|
||||
|
||||
cd1a.merge(cd1b);
|
||||
let cd1 = cd1a;
|
||||
cd1.assert(false, &["1<>"]);
|
||||
sym1a.merge(sym1b);
|
||||
let sym1 = sym1a;
|
||||
assert_bindings(&sym1, false, &["1<>"]);
|
||||
|
||||
// merging a constrained definition with unbound keeps both
|
||||
let mut cd2a = SymbolState::with(ScopedDefinitionId::from_u32(2));
|
||||
cd2a.add_constraint(ScopedConstraintId::from_u32(3));
|
||||
let mut sym2a = SymbolState::undefined();
|
||||
sym2a.record_binding(ScopedDefinitionId::from_u32(2));
|
||||
sym2a.record_constraint(ScopedConstraintId::from_u32(3));
|
||||
|
||||
let cd2b = SymbolState::unbound();
|
||||
let sym2b = SymbolState::undefined();
|
||||
|
||||
cd2a.merge(cd2b);
|
||||
let cd2 = cd2a;
|
||||
cd2.assert(true, &["2<3>"]);
|
||||
sym2a.merge(sym2b);
|
||||
let sym2 = sym2a;
|
||||
assert_bindings(&sym2, true, &["2<3>"]);
|
||||
|
||||
// merging different definitions keeps them each with their existing constraints
|
||||
cd0.merge(cd2);
|
||||
let cd = cd0;
|
||||
cd.assert(true, &["0<0>", "2<3>"]);
|
||||
sym0.merge(sym2);
|
||||
let sym = sym0;
|
||||
assert_bindings(&sym, true, &["0<0>", "2<3>"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_declaration() {
|
||||
let sym = SymbolState::undefined();
|
||||
|
||||
assert_declarations(&sym, true, &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_declaration() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
assert_declarations(&sym, false, &[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_declaration_override() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
|
||||
assert_declarations(&sym, false, &[2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_declaration_merge() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
let mut sym2 = SymbolState::undefined();
|
||||
sym2.record_declaration(ScopedDefinitionId::from_u32(2));
|
||||
|
||||
sym.merge(sym2);
|
||||
|
||||
assert_declarations(&sym, false, &[1, 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_declaration_merge_partial_undeclared() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(1));
|
||||
|
||||
let sym2 = SymbolState::undefined();
|
||||
|
||||
sym.merge(sym2);
|
||||
|
||||
assert_declarations(&sym, true, &[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_may_be_undeclared() {
|
||||
let mut sym = SymbolState::undefined();
|
||||
sym.record_declaration(ScopedDefinitionId::from_u32(0));
|
||||
sym.set_may_be_undeclared();
|
||||
|
||||
assert_declarations(&sym, true, &[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::{resolve_module, Module};
|
||||
use crate::semantic_index::ast_ids::HasScopedAstId;
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::types::{definition_ty, global_symbol_ty, infer_scope_types, Type};
|
||||
use crate::types::{binding_ty, global_symbol_ty, infer_scope_types, Type};
|
||||
use crate::Db;
|
||||
|
||||
pub struct SemanticModel<'db> {
|
||||
@@ -147,24 +147,24 @@ impl HasTy for ast::Expr {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_definition_has_ty {
|
||||
macro_rules! impl_binding_has_ty {
|
||||
($ty: ty) => {
|
||||
impl HasTy for $ty {
|
||||
#[inline]
|
||||
fn ty<'db>(&self, model: &SemanticModel<'db>) -> Type<'db> {
|
||||
let index = semantic_index(model.db, model.file);
|
||||
let definition = index.definition(self);
|
||||
definition_ty(model.db, definition)
|
||||
let binding = index.definition(self);
|
||||
binding_ty(model.db, binding)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_definition_has_ty!(ast::StmtFunctionDef);
|
||||
impl_definition_has_ty!(ast::StmtClassDef);
|
||||
impl_definition_has_ty!(ast::Alias);
|
||||
impl_definition_has_ty!(ast::Parameter);
|
||||
impl_definition_has_ty!(ast::ParameterWithDefault);
|
||||
impl_binding_has_ty!(ast::StmtFunctionDef);
|
||||
impl_binding_has_ty!(ast::StmtClassDef);
|
||||
impl_binding_has_ty!(ast::Alias);
|
||||
impl_binding_has_ty!(ast::Parameter);
|
||||
impl_binding_has_ty!(ast::ParameterWithDefault);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use infer::TypeInferenceBuilder;
|
||||
use infer::TypeInferenceContext;
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast as ast;
|
||||
|
||||
@@ -7,8 +7,8 @@ use crate::semantic_index::ast_ids::HasScopedAstId;
|
||||
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId};
|
||||
use crate::semantic_index::{
|
||||
global_scope, semantic_index, symbol_table, use_def_map, DefinitionWithConstraints,
|
||||
DefinitionWithConstraintsIterator,
|
||||
global_scope, semantic_index, symbol_table, use_def_map, BindingWithConstraints,
|
||||
BindingWithConstraintsIterator, DeclarationsIterator,
|
||||
};
|
||||
use crate::stdlib::{builtins_symbol_ty, types_symbol_ty, typeshed_symbol_ty};
|
||||
use crate::types::narrow::narrowing_constraint;
|
||||
@@ -16,6 +16,7 @@ use crate::{Db, FxOrderSet};
|
||||
|
||||
pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder};
|
||||
pub(crate) use self::diagnostic::TypeCheckDiagnostics;
|
||||
pub(crate) use self::display::TypeArrayDisplay;
|
||||
pub(crate) use self::infer::{
|
||||
infer_deferred_types, infer_definition_types, infer_expression_types, infer_scope_types,
|
||||
};
|
||||
@@ -41,25 +42,31 @@ pub fn check_types(db: &dyn Db, file: File) -> TypeCheckDiagnostics {
|
||||
}
|
||||
|
||||
/// Infer the public type of a symbol (its type as seen from outside its scope).
|
||||
pub(crate) fn symbol_ty_by_id<'db>(
|
||||
db: &'db dyn Db,
|
||||
scope: ScopeId<'db>,
|
||||
symbol: ScopedSymbolId,
|
||||
) -> Type<'db> {
|
||||
let _span = tracing::trace_span!("symbol_ty", ?symbol).entered();
|
||||
fn symbol_ty_by_id<'db>(db: &'db dyn Db, scope: ScopeId<'db>, symbol: ScopedSymbolId) -> Type<'db> {
|
||||
let _span = tracing::trace_span!("symbol_ty_by_id", ?symbol).entered();
|
||||
|
||||
let use_def = use_def_map(db, scope);
|
||||
definitions_ty(
|
||||
db,
|
||||
use_def.public_definitions(symbol),
|
||||
use_def
|
||||
.public_may_be_unbound(symbol)
|
||||
.then_some(Type::Unbound),
|
||||
)
|
||||
|
||||
// If the symbol is declared, the public type is based on declarations; otherwise, it's based
|
||||
// on inference from bindings.
|
||||
if use_def.has_public_declarations(symbol) {
|
||||
let declarations = use_def.public_declarations(symbol);
|
||||
// Intentionally ignore conflicting declared types; that's not our problem, it's the
|
||||
// problem of the module we are importing from.
|
||||
declarations_ty(db, declarations).unwrap_or_else(|(ty, _)| ty)
|
||||
} else {
|
||||
bindings_ty(
|
||||
db,
|
||||
use_def.public_bindings(symbol),
|
||||
use_def
|
||||
.public_may_be_unbound(symbol)
|
||||
.then_some(Type::Unbound),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Shorthand for `symbol_ty` that takes a symbol name instead of an ID.
|
||||
pub(crate) fn symbol_ty<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Type<'db> {
|
||||
fn symbol_ty<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Type<'db> {
|
||||
let table = symbol_table(db, scope);
|
||||
table
|
||||
.symbol_id_by_name(name)
|
||||
@@ -72,17 +79,23 @@ pub(crate) fn global_symbol_ty<'db>(db: &'db dyn Db, file: File, name: &str) ->
|
||||
symbol_ty(db, global_scope(db, file), name)
|
||||
}
|
||||
|
||||
/// Infer the type of a [`Definition`].
|
||||
pub(crate) fn definition_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> Type<'db> {
|
||||
/// Infer the type of a binding.
|
||||
pub(crate) fn binding_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> Type<'db> {
|
||||
let inference = infer_definition_types(db, definition);
|
||||
inference.definition_ty(definition)
|
||||
inference.binding_ty(definition)
|
||||
}
|
||||
|
||||
/// Infer the type of a declaration.
|
||||
fn declaration_ty<'db>(db: &'db dyn Db, definition: Definition<'db>) -> Type<'db> {
|
||||
let inference = infer_definition_types(db, definition);
|
||||
inference.declaration_ty(definition)
|
||||
}
|
||||
|
||||
/// Infer the type of a (possibly deferred) sub-expression of a [`Definition`].
|
||||
///
|
||||
/// ## Panics
|
||||
/// If the given expression is not a sub-expression of the given [`Definition`].
|
||||
pub(crate) fn definition_expression_ty<'db>(
|
||||
fn definition_expression_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
expression: &ast::Expr,
|
||||
@@ -96,45 +109,45 @@ pub(crate) fn definition_expression_ty<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Infer the combined type of an array of [`Definition`]s, plus one optional "unbound type".
|
||||
/// Infer the combined type of an iterator of bindings, plus one optional "unbound type".
|
||||
///
|
||||
/// Will return a union if there is more than one definition, or at least one plus an unbound
|
||||
/// Will return a union if there is more than one binding, or at least one plus an unbound
|
||||
/// type.
|
||||
///
|
||||
/// The "unbound type" represents the type in case control flow may not have passed through any
|
||||
/// definitions in this scope. If this isn't possible, then it will be `None`. If it is possible,
|
||||
/// and the result in that case should be Unbound (e.g. an unbound function local), then it will be
|
||||
/// bindings in this scope. If this isn't possible, then it will be `None`. If it is possible, and
|
||||
/// the result in that case should be Unbound (e.g. an unbound function local), then it will be
|
||||
/// `Some(Type::Unbound)`. If it is possible and the result should be something else (e.g. an
|
||||
/// implicit global lookup), then `unbound_type` will be `Some(the_global_symbol_type)`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Will panic if called with zero definitions and no `unbound_ty`. This is a logic error,
|
||||
/// as any symbol with zero visible definitions clearly may be unbound, and the caller should
|
||||
/// provide an `unbound_ty`.
|
||||
pub(crate) fn definitions_ty<'db>(
|
||||
/// Will panic if called with zero bindings and no `unbound_ty`. This is a logic error, as any
|
||||
/// symbol with zero visible bindings clearly may be unbound, and the caller should provide an
|
||||
/// `unbound_ty`.
|
||||
fn bindings_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
definitions_with_constraints: DefinitionWithConstraintsIterator<'_, 'db>,
|
||||
bindings_with_constraints: BindingWithConstraintsIterator<'_, 'db>,
|
||||
unbound_ty: Option<Type<'db>>,
|
||||
) -> Type<'db> {
|
||||
let def_types = definitions_with_constraints.map(
|
||||
|DefinitionWithConstraints {
|
||||
definition,
|
||||
let def_types = bindings_with_constraints.map(
|
||||
|BindingWithConstraints {
|
||||
binding,
|
||||
constraints,
|
||||
}| {
|
||||
let mut constraint_tys = constraints
|
||||
.filter_map(|constraint| narrowing_constraint(db, constraint, definition));
|
||||
let definition_ty = definition_ty(db, definition);
|
||||
let mut constraint_tys =
|
||||
constraints.filter_map(|constraint| narrowing_constraint(db, constraint, binding));
|
||||
let binding_ty = binding_ty(db, binding);
|
||||
if let Some(first_constraint_ty) = constraint_tys.next() {
|
||||
let mut builder = IntersectionBuilder::new(db);
|
||||
builder = builder
|
||||
.add_positive(definition_ty)
|
||||
.add_positive(binding_ty)
|
||||
.add_positive(first_constraint_ty);
|
||||
for constraint_ty in constraint_tys {
|
||||
builder = builder.add_positive(constraint_ty);
|
||||
}
|
||||
builder.build()
|
||||
} else {
|
||||
definition_ty
|
||||
binding_ty
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -142,7 +155,7 @@ pub(crate) fn definitions_ty<'db>(
|
||||
|
||||
let first = all_types
|
||||
.next()
|
||||
.expect("definitions_ty should never be called with zero definitions and no unbound_ty.");
|
||||
.expect("bindings_ty should never be called with zero definitions and no unbound_ty.");
|
||||
|
||||
if let Some(second) = all_types.next() {
|
||||
UnionType::from_elements(db, [first, second].into_iter().chain(all_types))
|
||||
@@ -151,6 +164,63 @@ pub(crate) fn definitions_ty<'db>(
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of looking up a declared type from declarations; see [`declarations_ty`].
|
||||
type DeclaredTypeResult<'db> = Result<Type<'db>, (Type<'db>, Box<[Type<'db>]>)>;
|
||||
|
||||
/// Build a declared type from a [`DeclarationsIterator`].
|
||||
///
|
||||
/// If there is only one declaration, or all declarations declare the same type, returns
|
||||
/// `Ok(declared_type)`. If there are conflicting declarations, returns
|
||||
/// `Err((union_of_declared_types, conflicting_declared_types))`.
|
||||
///
|
||||
/// If undeclared is a possibility, `Unknown` type will be part of the return type (and may
|
||||
/// conflict with other declarations.)
|
||||
///
|
||||
/// # Panics
|
||||
/// Will panic if there are no declarations and no possibility of undeclared. This is a logic
|
||||
/// error, as any symbol with zero live declarations clearly must be undeclared.
|
||||
fn declarations_ty<'db>(
|
||||
db: &'db dyn Db,
|
||||
declarations: DeclarationsIterator<'_, 'db>,
|
||||
) -> DeclaredTypeResult<'db> {
|
||||
let may_be_undeclared = declarations.may_be_undeclared();
|
||||
let decl_types = declarations.map(|declaration| declaration_ty(db, declaration));
|
||||
|
||||
let mut all_types = (if may_be_undeclared {
|
||||
Some(Type::Unknown)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.into_iter()
|
||||
.chain(decl_types);
|
||||
|
||||
let first = all_types.next().expect(
|
||||
"declarations_ty must not be called with zero declarations and no may-be-undeclared.",
|
||||
);
|
||||
|
||||
let mut conflicting: Vec<Type<'db>> = vec![];
|
||||
let declared_ty = if let Some(second) = all_types.next() {
|
||||
let mut builder = UnionBuilder::new(db).add(first);
|
||||
for other in [second].into_iter().chain(all_types) {
|
||||
if !first.is_equivalent_to(db, other) {
|
||||
conflicting.push(other);
|
||||
}
|
||||
builder = builder.add(other);
|
||||
}
|
||||
builder.build()
|
||||
} else {
|
||||
first
|
||||
};
|
||||
if conflicting.is_empty() {
|
||||
DeclaredTypeResult::Ok(declared_ty)
|
||||
} else {
|
||||
DeclaredTypeResult::Err((
|
||||
declared_ty,
|
||||
[first].into_iter().chain(conflicting).collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Unique ID for a type.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Type<'db> {
|
||||
@@ -300,7 +370,6 @@ impl<'db> Type<'db> {
|
||||
/// Return true if this type is [assignable to] type `target`.
|
||||
///
|
||||
/// [assignable to]: https://typing.readthedocs.io/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation
|
||||
#[allow(unused)]
|
||||
pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool {
|
||||
if self.is_equivalent_to(db, target) {
|
||||
return true;
|
||||
@@ -324,13 +393,16 @@ impl<'db> Type<'db> {
|
||||
{
|
||||
true
|
||||
}
|
||||
(ty, Type::Union(union)) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.any(|&elem_ty| ty.is_assignable_to(db, elem_ty)),
|
||||
// TODO
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if this type is equivalent to type `other`.
|
||||
#[allow(unused)]
|
||||
pub(crate) fn is_equivalent_to(self, _db: &'db dyn Db, other: Type<'db>) -> bool {
|
||||
// TODO equivalent but not identical structural types, differently-ordered unions and
|
||||
// intersections, other cases?
|
||||
@@ -410,14 +482,14 @@ impl<'db> Type<'db> {
|
||||
///
|
||||
/// Returns `None` if `self` is not a callable type.
|
||||
#[must_use]
|
||||
pub fn call(&self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
fn call(&self, db: &'db dyn Db, _context: &mut TypeInferenceContext<'db>) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Type::Function(function_type) => Some(function_type.return_type(db)),
|
||||
|
||||
// TODO annotated return type on `__new__` or metaclass `__call__`
|
||||
Type::Class(class) => Some(Type::Instance(*class)),
|
||||
|
||||
// TODO: handle classes which implement the Callable protocol
|
||||
// TODO: handle classes which implement `__call__`
|
||||
Type::Instance(_instance_ty) => Some(Type::Unknown),
|
||||
|
||||
// `Any` is callable, and its return type is also `Any`.
|
||||
@@ -425,7 +497,7 @@ impl<'db> Type<'db> {
|
||||
|
||||
Type::Unknown => Some(Type::Unknown),
|
||||
|
||||
// TODO: union and intersection types, if they reduce to `Callable`
|
||||
// TODO: union and intersection types
|
||||
Type::Union(_) => Some(Type::Unknown),
|
||||
Type::Intersection(_) => Some(Type::Unknown),
|
||||
|
||||
@@ -441,11 +513,14 @@ impl<'db> Type<'db> {
|
||||
/// for y in x:
|
||||
/// pass
|
||||
/// ```
|
||||
fn iterate(&self, db: &'db dyn Db) -> IterationOutcome<'db> {
|
||||
/// Return None and emit a diagnostic if this type is not iterable.
|
||||
fn iterate(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
context: &mut TypeInferenceContext<'db>,
|
||||
) -> Option<Type<'db>> {
|
||||
if let Type::Tuple(tuple_type) = self {
|
||||
return IterationOutcome::Iterable {
|
||||
element_ty: UnionType::from_elements(db, &**tuple_type.elements(db)),
|
||||
};
|
||||
return Some(UnionType::from_elements(db, &**tuple_type.elements(db)));
|
||||
}
|
||||
|
||||
// `self` represents the type of the iterable;
|
||||
@@ -454,19 +529,16 @@ impl<'db> Type<'db> {
|
||||
|
||||
let dunder_iter_method = iterable_meta_type.member(db, "__iter__");
|
||||
if !dunder_iter_method.is_unbound() {
|
||||
let Some(iterator_ty) = dunder_iter_method.call(db) else {
|
||||
return IterationOutcome::NotIterable {
|
||||
not_iterable_ty: *self,
|
||||
};
|
||||
let Some(iterator_ty) = dunder_iter_method.call(db, context) else {
|
||||
context.not_iterable_diagnostic(*self);
|
||||
return None;
|
||||
};
|
||||
|
||||
let dunder_next_method = iterator_ty.to_meta_type(db).member(db, "__next__");
|
||||
return dunder_next_method
|
||||
.call(db)
|
||||
.map(|element_ty| IterationOutcome::Iterable { element_ty })
|
||||
.unwrap_or(IterationOutcome::NotIterable {
|
||||
not_iterable_ty: *self,
|
||||
});
|
||||
return dunder_next_method.call(db, context).or_else(|| {
|
||||
context.not_iterable_diagnostic(*self);
|
||||
None
|
||||
});
|
||||
}
|
||||
|
||||
// Although it's not considered great practice,
|
||||
@@ -477,12 +549,10 @@ impl<'db> Type<'db> {
|
||||
// accepting `int` or `SupportsIndex`
|
||||
let dunder_get_item_method = iterable_meta_type.member(db, "__getitem__");
|
||||
|
||||
dunder_get_item_method
|
||||
.call(db)
|
||||
.map(|element_ty| IterationOutcome::Iterable { element_ty })
|
||||
.unwrap_or(IterationOutcome::NotIterable {
|
||||
not_iterable_ty: *self,
|
||||
})
|
||||
dunder_get_item_method.call(db, context).or_else(|| {
|
||||
context.not_iterable_diagnostic(*self);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -547,28 +617,6 @@ impl<'db> From<&Type<'db>> for Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum IterationOutcome<'db> {
|
||||
Iterable { element_ty: Type<'db> },
|
||||
NotIterable { not_iterable_ty: Type<'db> },
|
||||
}
|
||||
|
||||
impl<'db> IterationOutcome<'db> {
|
||||
fn unwrap_with_diagnostic(
|
||||
self,
|
||||
iterable_node: ast::AnyNodeRef,
|
||||
inference_builder: &mut TypeInferenceBuilder<'db>,
|
||||
) -> Type<'db> {
|
||||
match self {
|
||||
Self::Iterable { element_ty } => element_ty,
|
||||
Self::NotIterable { not_iterable_ty } => {
|
||||
inference_builder.not_iterable_diagnostic(iterable_node, not_iterable_ty);
|
||||
Type::Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::interned]
|
||||
pub struct FunctionType<'db> {
|
||||
/// name of the function at definition
|
||||
@@ -578,7 +626,7 @@ pub struct FunctionType<'db> {
|
||||
definition: Definition<'db>,
|
||||
|
||||
/// types of all decorators on this function
|
||||
decorators: Vec<Type<'db>>,
|
||||
decorators: Box<[Type<'db>]>,
|
||||
}
|
||||
|
||||
impl<'db> FunctionType<'db> {
|
||||
@@ -589,7 +637,7 @@ impl<'db> FunctionType<'db> {
|
||||
/// inferred return type for this function
|
||||
pub fn return_type(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
let definition = self.definition(db);
|
||||
let DefinitionKind::Function(function_stmt_node) = definition.node(db) else {
|
||||
let DefinitionKind::Function(function_stmt_node) = definition.kind(db) else {
|
||||
panic!("Function type definition must have `DefinitionKind::Function`")
|
||||
};
|
||||
|
||||
@@ -630,7 +678,6 @@ pub struct ClassType<'db> {
|
||||
|
||||
impl<'db> ClassType<'db> {
|
||||
/// Return true if this class is a standard library type with given module name and name.
|
||||
#[allow(unused)]
|
||||
pub(crate) fn is_stdlib_symbol(self, db: &'db dyn Db, module_name: &str, name: &str) -> bool {
|
||||
name == self.name(db)
|
||||
&& file_to_module(db, self.body_scope(db).file(db)).is_some_and(|module| {
|
||||
@@ -644,7 +691,7 @@ impl<'db> ClassType<'db> {
|
||||
/// If `definition` is not a `DefinitionKind::Class`.
|
||||
pub fn bases(&self, db: &'db dyn Db) -> impl Iterator<Item = Type<'db>> {
|
||||
let definition = self.definition(db);
|
||||
let DefinitionKind::Class(class_stmt_node) = definition.node(db) else {
|
||||
let DefinitionKind::Class(class_stmt_node) = definition.kind(db) else {
|
||||
panic!("Class type definition must have DefinitionKind::Class");
|
||||
};
|
||||
class_stmt_node
|
||||
@@ -830,6 +877,8 @@ mod tests {
|
||||
#[test_case(Ty::StringLiteral("foo"), Ty::LiteralString)]
|
||||
#[test_case(Ty::LiteralString, Ty::BuiltinInstance("str"))]
|
||||
#[test_case(Ty::BytesLiteral("foo"), Ty::BuiltinInstance("bytes"))]
|
||||
#[test_case(Ty::IntLiteral(1), Ty::Union(vec![Ty::BuiltinInstance("int"), Ty::BuiltinInstance("str")]))]
|
||||
#[test_case(Ty::IntLiteral(1), Ty::Union(vec![Ty::Unknown, Ty::BuiltinInstance("str")]))]
|
||||
fn is_assignable_to(from: Ty, to: Ty) {
|
||||
let db = setup_db();
|
||||
assert!(from.into_type(&db).is_assignable_to(&db, to.into_type(&db)));
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
//! Display implementations for types.
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
use ruff_db::display::FormatterJoinExtension;
|
||||
use ruff_python_ast::str::Quote;
|
||||
use ruff_python_literal::escape::AsciiEscape;
|
||||
|
||||
use crate::types::{IntersectionType, Type, UnionType};
|
||||
use crate::{Db, FxOrderMap};
|
||||
use crate::Db;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
impl<'db> Type<'db> {
|
||||
pub fn display(&'db self, db: &'db dyn Db) -> DisplayType<'db> {
|
||||
pub fn display(&self, db: &'db dyn Db) -> DisplayType {
|
||||
DisplayType { ty: self, db }
|
||||
}
|
||||
|
||||
fn representation(&'db self, db: &'db dyn Db) -> DisplayRepresentation<'db> {
|
||||
fn representation(self, db: &'db dyn Db) -> DisplayRepresentation<'db> {
|
||||
DisplayRepresentation { db, ty: self }
|
||||
}
|
||||
}
|
||||
@@ -25,7 +26,7 @@ pub struct DisplayType<'db> {
|
||||
}
|
||||
|
||||
impl Display for DisplayType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let representation = self.ty.representation(self.db);
|
||||
if matches!(
|
||||
self.ty,
|
||||
@@ -43,9 +44,9 @@ impl Display for DisplayType<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DisplayType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
impl fmt::Debug for DisplayType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,12 +54,12 @@ impl std::fmt::Debug for DisplayType<'_> {
|
||||
/// `Literal[<repr>]` or `Literal[<repr1>, <repr2>]` for literal types or as `<repr>` for
|
||||
/// non literals
|
||||
struct DisplayRepresentation<'db> {
|
||||
ty: &'db Type<'db>,
|
||||
ty: Type<'db>,
|
||||
db: &'db dyn Db,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DisplayRepresentation<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
impl Display for DisplayRepresentation<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self.ty {
|
||||
Type::Any => f.write_str("Any"),
|
||||
Type::Never => f.write_str("Never"),
|
||||
@@ -74,8 +75,8 @@ impl std::fmt::Display for DisplayRepresentation<'_> {
|
||||
Type::Function(function) => f.write_str(function.name(self.db)),
|
||||
Type::Union(union) => union.display(self.db).fmt(f),
|
||||
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
|
||||
Type::IntLiteral(n) => write!(f, "{n}"),
|
||||
Type::BooleanLiteral(boolean) => f.write_str(if *boolean { "True" } else { "False" }),
|
||||
Type::IntLiteral(n) => n.fmt(f),
|
||||
Type::BooleanLiteral(boolean) => f.write_str(if boolean { "True" } else { "False" }),
|
||||
Type::StringLiteral(string) => {
|
||||
write!(f, r#""{}""#, string.value(self.db).replace('"', r#"\""#))
|
||||
}
|
||||
@@ -92,14 +93,7 @@ impl std::fmt::Display for DisplayRepresentation<'_> {
|
||||
if elements.is_empty() {
|
||||
f.write_str("()")?;
|
||||
} else {
|
||||
let mut first = true;
|
||||
for element in &**elements {
|
||||
if !first {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
first = false;
|
||||
element.display(self.db).fmt(f)?;
|
||||
}
|
||||
elements.display(self.db).fmt(f)?;
|
||||
}
|
||||
f.write_str("]")
|
||||
}
|
||||
@@ -119,11 +113,11 @@ struct DisplayUnionType<'db> {
|
||||
}
|
||||
|
||||
impl Display for DisplayUnionType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let elements = self.ty.elements(self.db);
|
||||
|
||||
// Group literal types by kind.
|
||||
let mut grouped_literals = FxOrderMap::default();
|
||||
let mut grouped_literals = FxHashMap::default();
|
||||
|
||||
for element in elements {
|
||||
if let Ok(literal_kind) = LiteralTypeKind::try_from(*element) {
|
||||
@@ -134,52 +128,51 @@ impl Display for DisplayUnionType<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut first = true;
|
||||
let mut join = f.join(" | ");
|
||||
|
||||
// Print all types, but write all literals together (while preserving their position).
|
||||
for ty in elements {
|
||||
if let Ok(literal_kind) = LiteralTypeKind::try_from(*ty) {
|
||||
for element in elements {
|
||||
if let Ok(literal_kind) = LiteralTypeKind::try_from(*element) {
|
||||
let Some(mut literals) = grouped_literals.remove(&literal_kind) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !first {
|
||||
f.write_str(" | ")?;
|
||||
};
|
||||
|
||||
f.write_str("Literal[")?;
|
||||
|
||||
if literal_kind == LiteralTypeKind::IntLiteral {
|
||||
literals.sort_unstable_by_key(|ty| ty.expect_int_literal());
|
||||
}
|
||||
|
||||
for (i, literal_ty) in literals.iter().enumerate() {
|
||||
if i > 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
literal_ty.representation(self.db).fmt(f)?;
|
||||
}
|
||||
f.write_str("]")?;
|
||||
join.entry(&DisplayLiteralGroup {
|
||||
literals,
|
||||
db: self.db,
|
||||
});
|
||||
} else {
|
||||
if !first {
|
||||
f.write_str(" | ")?;
|
||||
};
|
||||
|
||||
ty.display(self.db).fmt(f)?;
|
||||
join.entry(&element.display(self.db));
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
join.finish()?;
|
||||
|
||||
debug_assert!(grouped_literals.is_empty());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DisplayUnionType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
impl fmt::Debug for DisplayUnionType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayLiteralGroup<'db> {
|
||||
literals: Vec<Type<'db>>,
|
||||
db: &'db dyn Db,
|
||||
}
|
||||
|
||||
impl Display for DisplayLiteralGroup<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("Literal[")?;
|
||||
f.join(", ")
|
||||
.entries(self.literals.iter().map(|ty| ty.representation(self.db)))
|
||||
.finish()?;
|
||||
f.write_str("]")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,31 +212,77 @@ struct DisplayIntersectionType<'db> {
|
||||
}
|
||||
|
||||
impl Display for DisplayIntersectionType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let mut first = true;
|
||||
for (neg, ty) in self
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let tys = self
|
||||
.ty
|
||||
.positive(self.db)
|
||||
.iter()
|
||||
.map(|ty| (false, ty))
|
||||
.chain(self.ty.negative(self.db).iter().map(|ty| (true, ty)))
|
||||
{
|
||||
if !first {
|
||||
f.write_str(" & ")?;
|
||||
};
|
||||
first = false;
|
||||
if neg {
|
||||
f.write_str("~")?;
|
||||
};
|
||||
write!(f, "{}", ty.display(self.db))?;
|
||||
}
|
||||
Ok(())
|
||||
.map(|&ty| DisplayMaybeNegatedType {
|
||||
ty,
|
||||
db: self.db,
|
||||
negated: false,
|
||||
})
|
||||
.chain(
|
||||
self.ty
|
||||
.negative(self.db)
|
||||
.iter()
|
||||
.map(|&ty| DisplayMaybeNegatedType {
|
||||
ty,
|
||||
db: self.db,
|
||||
negated: true,
|
||||
}),
|
||||
);
|
||||
f.join(" & ").entries(tys).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for DisplayIntersectionType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self, f)
|
||||
impl fmt::Debug for DisplayIntersectionType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayMaybeNegatedType<'db> {
|
||||
ty: Type<'db>,
|
||||
db: &'db dyn Db,
|
||||
negated: bool,
|
||||
}
|
||||
|
||||
impl<'db> Display for DisplayMaybeNegatedType<'db> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
if self.negated {
|
||||
f.write_str("~")?;
|
||||
}
|
||||
self.ty.display(self.db).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait TypeArrayDisplay<'db> {
|
||||
fn display(&self, db: &'db dyn Db) -> DisplayTypeArray;
|
||||
}
|
||||
|
||||
impl<'db> TypeArrayDisplay<'db> for Box<[Type<'db>]> {
|
||||
fn display(&self, db: &'db dyn Db) -> DisplayTypeArray {
|
||||
DisplayTypeArray { types: self, db }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> TypeArrayDisplay<'db> for Vec<Type<'db>> {
|
||||
fn display(&self, db: &'db dyn Db) -> DisplayTypeArray {
|
||||
DisplayTypeArray { types: self, db }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DisplayTypeArray<'b, 'db> {
|
||||
types: &'b [Type<'db>],
|
||||
db: &'db dyn Db,
|
||||
}
|
||||
|
||||
impl<'db> Display for DisplayTypeArray<'_, 'db> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.join(", ")
|
||||
.entries(self.types.iter().map(|ty| ty.display(self.db)))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
23d867efb2df6de5600f64656f1aa8a83e06109e
|
||||
9e506eb5e8fc2823db8c60ad561b1145ff114947
|
||||
|
||||
@@ -41,7 +41,7 @@ _json: 3.0-
|
||||
_locale: 3.0-
|
||||
_lsprof: 3.0-
|
||||
_markupbase: 3.0-
|
||||
_msi: 3.0-
|
||||
_msi: 3.0-3.12
|
||||
_operator: 3.4-
|
||||
_osx_support: 3.0-
|
||||
_posixsubprocess: 3.2-
|
||||
|
||||
@@ -493,7 +493,7 @@ class _CursesWindow:
|
||||
def instr(self, y: int, x: int, n: int = ...) -> bytes: ...
|
||||
def is_linetouched(self, line: int, /) -> bool: ...
|
||||
def is_wintouched(self) -> bool: ...
|
||||
def keypad(self, yes: bool) -> None: ...
|
||||
def keypad(self, yes: bool, /) -> None: ...
|
||||
def leaveok(self, yes: bool) -> None: ...
|
||||
def move(self, new_y: int, new_x: int) -> None: ...
|
||||
def mvderwin(self, y: int, x: int) -> None: ...
|
||||
|
||||
@@ -1,17 +1,38 @@
|
||||
import sys
|
||||
from _typeshed import StrPath
|
||||
from collections.abc import Mapping
|
||||
from typing import Final, Literal, TypedDict, type_check_only
|
||||
|
||||
LC_CTYPE: int
|
||||
LC_COLLATE: int
|
||||
LC_TIME: int
|
||||
LC_MONETARY: int
|
||||
LC_NUMERIC: int
|
||||
LC_ALL: int
|
||||
CHAR_MAX: int
|
||||
@type_check_only
|
||||
class _LocaleConv(TypedDict):
|
||||
decimal_point: str
|
||||
grouping: list[int]
|
||||
thousands_sep: str
|
||||
int_curr_symbol: str
|
||||
currency_symbol: str
|
||||
p_cs_precedes: Literal[0, 1, 127]
|
||||
n_cs_precedes: Literal[0, 1, 127]
|
||||
p_sep_by_space: Literal[0, 1, 127]
|
||||
n_sep_by_space: Literal[0, 1, 127]
|
||||
mon_decimal_point: str
|
||||
frac_digits: int
|
||||
int_frac_digits: int
|
||||
mon_thousands_sep: str
|
||||
mon_grouping: list[int]
|
||||
positive_sign: str
|
||||
negative_sign: str
|
||||
p_sign_posn: Literal[0, 1, 2, 3, 4, 127]
|
||||
n_sign_posn: Literal[0, 1, 2, 3, 4, 127]
|
||||
|
||||
LC_CTYPE: Final[int]
|
||||
LC_COLLATE: Final[int]
|
||||
LC_TIME: Final[int]
|
||||
LC_MONETARY: Final[int]
|
||||
LC_NUMERIC: Final[int]
|
||||
LC_ALL: Final[int]
|
||||
CHAR_MAX: Final = 127
|
||||
|
||||
def setlocale(category: int, locale: str | None = None, /) -> str: ...
|
||||
def localeconv() -> Mapping[str, int | str | list[int]]: ...
|
||||
def localeconv() -> _LocaleConv: ...
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
def getencoding() -> str: ...
|
||||
@@ -25,67 +46,67 @@ def strxfrm(string: str, /) -> str: ...
|
||||
if sys.platform != "win32":
|
||||
LC_MESSAGES: int
|
||||
|
||||
ABDAY_1: int
|
||||
ABDAY_2: int
|
||||
ABDAY_3: int
|
||||
ABDAY_4: int
|
||||
ABDAY_5: int
|
||||
ABDAY_6: int
|
||||
ABDAY_7: int
|
||||
ABDAY_1: Final[int]
|
||||
ABDAY_2: Final[int]
|
||||
ABDAY_3: Final[int]
|
||||
ABDAY_4: Final[int]
|
||||
ABDAY_5: Final[int]
|
||||
ABDAY_6: Final[int]
|
||||
ABDAY_7: Final[int]
|
||||
|
||||
ABMON_1: int
|
||||
ABMON_2: int
|
||||
ABMON_3: int
|
||||
ABMON_4: int
|
||||
ABMON_5: int
|
||||
ABMON_6: int
|
||||
ABMON_7: int
|
||||
ABMON_8: int
|
||||
ABMON_9: int
|
||||
ABMON_10: int
|
||||
ABMON_11: int
|
||||
ABMON_12: int
|
||||
ABMON_1: Final[int]
|
||||
ABMON_2: Final[int]
|
||||
ABMON_3: Final[int]
|
||||
ABMON_4: Final[int]
|
||||
ABMON_5: Final[int]
|
||||
ABMON_6: Final[int]
|
||||
ABMON_7: Final[int]
|
||||
ABMON_8: Final[int]
|
||||
ABMON_9: Final[int]
|
||||
ABMON_10: Final[int]
|
||||
ABMON_11: Final[int]
|
||||
ABMON_12: Final[int]
|
||||
|
||||
DAY_1: int
|
||||
DAY_2: int
|
||||
DAY_3: int
|
||||
DAY_4: int
|
||||
DAY_5: int
|
||||
DAY_6: int
|
||||
DAY_7: int
|
||||
DAY_1: Final[int]
|
||||
DAY_2: Final[int]
|
||||
DAY_3: Final[int]
|
||||
DAY_4: Final[int]
|
||||
DAY_5: Final[int]
|
||||
DAY_6: Final[int]
|
||||
DAY_7: Final[int]
|
||||
|
||||
ERA: int
|
||||
ERA_D_T_FMT: int
|
||||
ERA_D_FMT: int
|
||||
ERA_T_FMT: int
|
||||
ERA: Final[int]
|
||||
ERA_D_T_FMT: Final[int]
|
||||
ERA_D_FMT: Final[int]
|
||||
ERA_T_FMT: Final[int]
|
||||
|
||||
MON_1: int
|
||||
MON_2: int
|
||||
MON_3: int
|
||||
MON_4: int
|
||||
MON_5: int
|
||||
MON_6: int
|
||||
MON_7: int
|
||||
MON_8: int
|
||||
MON_9: int
|
||||
MON_10: int
|
||||
MON_11: int
|
||||
MON_12: int
|
||||
MON_1: Final[int]
|
||||
MON_2: Final[int]
|
||||
MON_3: Final[int]
|
||||
MON_4: Final[int]
|
||||
MON_5: Final[int]
|
||||
MON_6: Final[int]
|
||||
MON_7: Final[int]
|
||||
MON_8: Final[int]
|
||||
MON_9: Final[int]
|
||||
MON_10: Final[int]
|
||||
MON_11: Final[int]
|
||||
MON_12: Final[int]
|
||||
|
||||
CODESET: int
|
||||
D_T_FMT: int
|
||||
D_FMT: int
|
||||
T_FMT: int
|
||||
T_FMT_AMPM: int
|
||||
AM_STR: int
|
||||
PM_STR: int
|
||||
CODESET: Final[int]
|
||||
D_T_FMT: Final[int]
|
||||
D_FMT: Final[int]
|
||||
T_FMT: Final[int]
|
||||
T_FMT_AMPM: Final[int]
|
||||
AM_STR: Final[int]
|
||||
PM_STR: Final[int]
|
||||
|
||||
RADIXCHAR: int
|
||||
THOUSEP: int
|
||||
YESEXPR: int
|
||||
NOEXPR: int
|
||||
CRNCYSTR: int
|
||||
ALT_DIGITS: int
|
||||
RADIXCHAR: Final[int]
|
||||
THOUSEP: Final[int]
|
||||
YESEXPR: Final[int]
|
||||
NOEXPR: Final[int]
|
||||
CRNCYSTR: Final[int]
|
||||
ALT_DIGITS: Final[int]
|
||||
|
||||
def nl_langinfo(key: int, /) -> str: ...
|
||||
|
||||
|
||||
@@ -99,6 +99,20 @@ if sys.platform == "win32":
|
||||
SEC_RESERVE: Final = 0x4000000
|
||||
SEC_WRITECOMBINE: Final = 0x40000000
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
STARTF_FORCEOFFFEEDBACK: Final = 0x80
|
||||
STARTF_FORCEONFEEDBACK: Final = 0x40
|
||||
STARTF_PREVENTPINNING: Final = 0x2000
|
||||
STARTF_RUNFULLSCREEN: Final = 0x20
|
||||
STARTF_TITLEISAPPID: Final = 0x1000
|
||||
STARTF_TITLEISLINKNAME: Final = 0x800
|
||||
STARTF_UNTRUSTEDSOURCE: Final = 0x8000
|
||||
STARTF_USECOUNTCHARS: Final = 0x8
|
||||
STARTF_USEFILLATTRIBUTE: Final = 0x10
|
||||
STARTF_USEHOTKEY: Final = 0x200
|
||||
STARTF_USEPOSITION: Final = 0x4
|
||||
STARTF_USESIZE: Final = 0x2
|
||||
|
||||
STARTF_USESHOWWINDOW: Final = 0x1
|
||||
STARTF_USESTDHANDLES: Final = 0x100
|
||||
|
||||
@@ -250,6 +264,20 @@ if sys.platform == "win32":
|
||||
def cancel(self) -> None: ...
|
||||
def getbuffer(self) -> bytes | None: ...
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
def BatchedWaitForMultipleObjects(
|
||||
handle_seq: Sequence[int], wait_all: bool, milliseconds: int = 0xFFFFFFFF
|
||||
) -> list[int]: ...
|
||||
def CreateEventW(security_attributes: int, manual_reset: bool, initial_state: bool, name: str | None) -> int: ...
|
||||
def CreateMutexW(security_attributes: int, initial_owner: bool, name: str) -> int: ...
|
||||
def GetLongPathName(path: str) -> str: ...
|
||||
def GetShortPathName(path: str) -> str: ...
|
||||
def OpenEventW(desired_access: int, inherit_handle: bool, name: str) -> int: ...
|
||||
def OpenMutexW(desired_access: int, inherit_handle: bool, name: str) -> int: ...
|
||||
def ReleaseMutex(mutex: int) -> None: ...
|
||||
def ResetEvent(event: int) -> None: ...
|
||||
def SetEvent(event: int) -> None: ...
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
def CopyFile2(existing_file_name: str, new_file_name: str, flags: int, progress_routine: int | None = None) -> int: ...
|
||||
def NeedCurrentDirectoryForExePath(exe_name: str, /) -> bool: ...
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# ruff: noqa: PYI036 # This is the module declaring BaseException
|
||||
import _ast
|
||||
import _typeshed
|
||||
import sys
|
||||
|
||||
@@ -80,7 +80,7 @@ class _Encoder(Protocol):
|
||||
def __call__(self, input: str, errors: str = ..., /) -> tuple[bytes, int]: ... # signature of Codec().encode
|
||||
|
||||
class _Decoder(Protocol):
|
||||
def __call__(self, input: bytes, errors: str = ..., /) -> tuple[str, int]: ... # signature of Codec().decode
|
||||
def __call__(self, input: ReadableBuffer, errors: str = ..., /) -> tuple[str, int]: ... # signature of Codec().decode
|
||||
|
||||
class _StreamReader(Protocol):
|
||||
def __call__(self, stream: _ReadableStream, errors: str = ..., /) -> StreamReader: ...
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import sys
|
||||
from typing import Any, Protocol, TypeVar
|
||||
from typing_extensions import ParamSpec, Self
|
||||
from typing_extensions import Self
|
||||
|
||||
__all__ = ["Error", "copy", "deepcopy"]
|
||||
|
||||
_T = TypeVar("_T")
|
||||
_SR = TypeVar("_SR", bound=_SupportsReplace[Any])
|
||||
_P = ParamSpec("_P")
|
||||
_SR = TypeVar("_SR", bound=_SupportsReplace)
|
||||
|
||||
class _SupportsReplace(Protocol[_P]):
|
||||
class _SupportsReplace(Protocol):
|
||||
# In reality doesn't support args, but there's no other great way to express this.
|
||||
def __replace__(self, *args: _P.args, **kwargs: _P.kwargs) -> Self: ...
|
||||
def __replace__(self, *args: Any, **kwargs: Any) -> Self: ...
|
||||
|
||||
# None in CPython but non-None in Jython
|
||||
PyStringMap: Any
|
||||
|
||||
@@ -270,7 +270,7 @@ class Distribution:
|
||||
def has_data_files(self) -> bool: ...
|
||||
def is_pure(self) -> bool: ...
|
||||
|
||||
# Getter methods generated in __init__
|
||||
# Default getter methods generated in __init__ from self.metadata._METHOD_BASENAMES
|
||||
def get_name(self) -> str: ...
|
||||
def get_version(self) -> str: ...
|
||||
def get_fullname(self) -> str: ...
|
||||
@@ -292,3 +292,26 @@ class Distribution:
|
||||
def get_requires(self) -> list[str]: ...
|
||||
def get_provides(self) -> list[str]: ...
|
||||
def get_obsoletes(self) -> list[str]: ...
|
||||
|
||||
# Default attributes generated in __init__ from self.display_option_names
|
||||
help_commands: bool | Literal[0]
|
||||
name: str | Literal[0]
|
||||
version: str | Literal[0]
|
||||
fullname: str | Literal[0]
|
||||
author: str | Literal[0]
|
||||
author_email: str | Literal[0]
|
||||
maintainer: str | Literal[0]
|
||||
maintainer_email: str | Literal[0]
|
||||
contact: str | Literal[0]
|
||||
contact_email: str | Literal[0]
|
||||
url: str | Literal[0]
|
||||
license: str | Literal[0]
|
||||
licence: str | Literal[0]
|
||||
description: str | Literal[0]
|
||||
long_description: str | Literal[0]
|
||||
platforms: str | list[str] | Literal[0]
|
||||
classifiers: str | list[str] | Literal[0]
|
||||
keywords: str | list[str] | Literal[0]
|
||||
provides: list[str] | Literal[0]
|
||||
requires: list[str] | Literal[0]
|
||||
obsoletes: list[str] | Literal[0]
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
from _typeshed import ExcInfo
|
||||
from collections.abc import Callable
|
||||
from typing import Any, NamedTuple
|
||||
from typing_extensions import TypeAlias
|
||||
from typing import Any, ClassVar, NamedTuple
|
||||
from typing_extensions import Self, TypeAlias
|
||||
|
||||
__all__ = [
|
||||
"register_optionflag",
|
||||
@@ -41,9 +42,22 @@ __all__ = [
|
||||
"debug",
|
||||
]
|
||||
|
||||
class TestResults(NamedTuple):
|
||||
failed: int
|
||||
attempted: int
|
||||
# MyPy errors on conditionals within named tuples.
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
class TestResults(NamedTuple):
|
||||
def __new__(cls, failed: int, attempted: int, *, skipped: int = 0) -> Self: ... # type: ignore[misc]
|
||||
skipped: int
|
||||
failed: int
|
||||
attempted: int
|
||||
_fields: ClassVar = ("failed", "attempted") # type: ignore[misc]
|
||||
__match_args__ = ("failed", "attempted") # type: ignore[misc]
|
||||
__doc__: None # type: ignore[misc]
|
||||
|
||||
else:
|
||||
class TestResults(NamedTuple):
|
||||
failed: int
|
||||
attempted: int
|
||||
|
||||
OPTIONFLAGS_BY_NAME: dict[str, int]
|
||||
|
||||
@@ -134,6 +148,8 @@ class DocTestRunner:
|
||||
original_optionflags: int
|
||||
tries: int
|
||||
failures: int
|
||||
if sys.version_info >= (3, 13):
|
||||
skips: int
|
||||
test: DocTest
|
||||
def __init__(self, checker: OutputChecker | None = None, verbose: bool | None = None, optionflags: int = 0) -> None: ...
|
||||
def report_start(self, out: _Out, test: DocTest, example: Example) -> None: ...
|
||||
|
||||
@@ -16,6 +16,10 @@ TOKEN_ENDS: Final[set[str]]
|
||||
ASPECIALS: Final[set[str]]
|
||||
ATTRIBUTE_ENDS: Final[set[str]]
|
||||
EXTENDED_ATTRIBUTE_ENDS: Final[set[str]]
|
||||
# Added in Python 3.8.20, 3.9.20, 3.10.15, 3.11.10, 3.12.5
|
||||
NLSET: Final[set[str]]
|
||||
# Added in Python 3.8.20, 3.9.20, 3.10.15, 3.11.10, 3.12.5
|
||||
SPECIALSNL: Final[set[str]]
|
||||
|
||||
def quote_string(value: Any) -> str: ...
|
||||
|
||||
|
||||
@@ -3,12 +3,34 @@ from collections.abc import Callable
|
||||
from email.errors import MessageDefect
|
||||
from email.header import Header
|
||||
from email.message import Message
|
||||
from typing import Any
|
||||
from typing_extensions import Self
|
||||
|
||||
class _PolicyBase:
|
||||
def __add__(self, other: Any) -> Self: ...
|
||||
def clone(self, **kw: Any) -> Self: ...
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
max_line_length: int | None = 78,
|
||||
linesep: str = "\n",
|
||||
cte_type: str = "8bit",
|
||||
raise_on_defect: bool = False,
|
||||
mangle_from_: bool = ..., # default depends on sub-class
|
||||
message_factory: Callable[[Policy], Message] | None = None,
|
||||
# Added in Python 3.8.20, 3.9.20, 3.10.15, 3.11.10, 3.12.5
|
||||
verify_generated_headers: bool = True,
|
||||
) -> None: ...
|
||||
def clone(
|
||||
self,
|
||||
*,
|
||||
max_line_length: int | None = ...,
|
||||
linesep: str = ...,
|
||||
cte_type: str = ...,
|
||||
raise_on_defect: bool = ...,
|
||||
mangle_from_: bool = ...,
|
||||
message_factory: Callable[[Policy], Message] | None = ...,
|
||||
# Added in Python 3.8.20, 3.9.20, 3.10.15, 3.11.10, 3.12.5
|
||||
verify_generated_headers: bool = ...,
|
||||
) -> Self: ...
|
||||
def __add__(self, other: Policy) -> Self: ...
|
||||
|
||||
class Policy(_PolicyBase, metaclass=ABCMeta):
|
||||
max_line_length: int | None
|
||||
@@ -17,16 +39,9 @@ class Policy(_PolicyBase, metaclass=ABCMeta):
|
||||
raise_on_defect: bool
|
||||
mangle_from_: bool
|
||||
message_factory: Callable[[Policy], Message] | None
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
max_line_length: int | None = 78,
|
||||
linesep: str = "\n",
|
||||
cte_type: str = "8bit",
|
||||
raise_on_defect: bool = False,
|
||||
mangle_from_: bool = False,
|
||||
message_factory: Callable[[Policy], Message] | None = None,
|
||||
) -> None: ...
|
||||
# Added in Python 3.8.20, 3.9.20, 3.10.15, 3.11.10, 3.12.5
|
||||
verify_generated_headers: bool
|
||||
|
||||
def handle_defect(self, obj: Message, defect: MessageDefect) -> None: ...
|
||||
def register_defect(self, obj: Message, defect: MessageDefect) -> None: ...
|
||||
def header_max_count(self, name: str) -> int | None: ...
|
||||
|
||||
@@ -7,6 +7,9 @@ class BoundaryError(MessageParseError): ...
|
||||
class MultipartConversionError(MessageError, TypeError): ...
|
||||
class CharsetError(MessageError): ...
|
||||
|
||||
# Added in Python 3.8.20, 3.9.20, 3.10.15, 3.11.10, 3.12.5
|
||||
class HeaderWriteError(MessageError): ...
|
||||
|
||||
class MessageDefect(ValueError):
|
||||
def __init__(self, line: str | None = None) -> None: ...
|
||||
|
||||
|
||||
@@ -30,20 +30,12 @@ _PDTZ: TypeAlias = tuple[int, int, int, int, int, int, int, int, int, int | None
|
||||
def quote(str: str) -> str: ...
|
||||
def unquote(str: str) -> str: ...
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
def parseaddr(addr: str | list[str], *, strict: bool = True) -> tuple[str, str]: ...
|
||||
|
||||
else:
|
||||
def parseaddr(addr: str) -> tuple[str, str]: ...
|
||||
|
||||
# `strict` parameter added in Python 3.8.20, 3.9.20, 3.10.15, 3.11.10, 3.12.5
|
||||
def parseaddr(addr: str | list[str], *, strict: bool = True) -> tuple[str, str]: ...
|
||||
def formataddr(pair: tuple[str | None, str], charset: str | Charset = "utf-8") -> str: ...
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
def getaddresses(fieldvalues: Iterable[str], *, strict: bool = True) -> list[tuple[str, str]]: ...
|
||||
|
||||
else:
|
||||
def getaddresses(fieldvalues: Iterable[str]) -> list[tuple[str, str]]: ...
|
||||
|
||||
# `strict` parameter added in Python 3.8.20, 3.9.20, 3.10.15, 3.11.10, 3.12.5
|
||||
def getaddresses(fieldvalues: Iterable[str], *, strict: bool = True) -> list[tuple[str, str]]: ...
|
||||
@overload
|
||||
def parsedate(data: None) -> None: ...
|
||||
@overload
|
||||
|
||||
@@ -84,7 +84,6 @@ class RawIOBase(IOBase):
|
||||
def read(self, size: int = -1, /) -> bytes | None: ...
|
||||
|
||||
class BufferedIOBase(IOBase):
|
||||
raw: RawIOBase # This is not part of the BufferedIOBase API and may not exist on some implementations.
|
||||
def detach(self) -> RawIOBase: ...
|
||||
def readinto(self, buffer: WriteableBuffer, /) -> int: ...
|
||||
def write(self, buffer: ReadableBuffer, /) -> int: ...
|
||||
@@ -119,11 +118,13 @@ class BytesIO(BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible d
|
||||
def read1(self, size: int | None = -1, /) -> bytes: ...
|
||||
|
||||
class BufferedReader(BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of methods in the base classes
|
||||
raw: RawIOBase
|
||||
def __enter__(self) -> Self: ...
|
||||
def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ...
|
||||
def peek(self, size: int = 0, /) -> bytes: ...
|
||||
|
||||
class BufferedWriter(BufferedIOBase, BinaryIO): # type: ignore[misc] # incompatible definitions of writelines in the base classes
|
||||
raw: RawIOBase
|
||||
def __enter__(self) -> Self: ...
|
||||
def __init__(self, raw: RawIOBase, buffer_size: int = ...) -> None: ...
|
||||
def write(self, buffer: ReadableBuffer, /) -> int: ...
|
||||
|
||||
@@ -2582,6 +2582,11 @@ else:
|
||||
def list2cmdline(seq: Iterable[StrOrBytesPath]) -> str: ... # undocumented
|
||||
|
||||
if sys.platform == "win32":
|
||||
if sys.version_info >= (3, 13):
|
||||
from _winapi import STARTF_FORCEOFFFEEDBACK, STARTF_FORCEONFEEDBACK
|
||||
|
||||
__all__ += ["STARTF_FORCEOFFFEEDBACK", "STARTF_FORCEONFEEDBACK"]
|
||||
|
||||
class STARTUPINFO:
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -253,11 +253,11 @@ class _TemporaryFileWrapper(IO[AnyStr]):
|
||||
def truncate(self, size: int | None = ...) -> int: ...
|
||||
def writable(self) -> bool: ...
|
||||
@overload
|
||||
def write(self: _TemporaryFileWrapper[str], s: str) -> int: ...
|
||||
def write(self: _TemporaryFileWrapper[str], s: str, /) -> int: ...
|
||||
@overload
|
||||
def write(self: _TemporaryFileWrapper[bytes], s: ReadableBuffer) -> int: ...
|
||||
def write(self: _TemporaryFileWrapper[bytes], s: ReadableBuffer, /) -> int: ...
|
||||
@overload
|
||||
def write(self, s: AnyStr) -> int: ...
|
||||
def write(self, s: AnyStr, /) -> int: ...
|
||||
@overload
|
||||
def writelines(self: _TemporaryFileWrapper[str], lines: Iterable[str]) -> None: ...
|
||||
@overload
|
||||
|
||||
@@ -66,4 +66,4 @@ codspeed = ["codspeed-criterion-compat"]
|
||||
mimalloc = { workspace = true }
|
||||
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dev-dependencies]
|
||||
tikv-jemallocator = { workspace = true, features = ["unprefixed_malloc_on_supported_platforms"] }
|
||||
tikv-jemallocator = { workspace = true }
|
||||
|
||||
@@ -42,9 +42,9 @@ static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
||||
)
|
||||
))]
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[export_name = "malloc_conf"]
|
||||
#[export_name = "_rjem_malloc_conf"]
|
||||
#[allow(unsafe_code)]
|
||||
pub static malloc_conf: &[u8] = b"dirty_decay_ms:-1,muzzy_decay_ms:-1\0";
|
||||
pub static _rjem_malloc_conf: &[u8] = b"dirty_decay_ms:-1,muzzy_decay_ms:-1\0";
|
||||
|
||||
fn create_test_cases() -> Result<Vec<TestCase>, TestFileDownloadError> {
|
||||
Ok(vec![
|
||||
|
||||
@@ -23,7 +23,6 @@ const TOMLLIB_312_URL: &str = "https://raw.githubusercontent.com/python/cpython/
|
||||
|
||||
// The failed import from 'collections.abc' is due to lack of support for 'import *'.
|
||||
static EXPECTED_DIAGNOSTICS: &[&str] = &[
|
||||
"/src/tomllib/_parser.py:5:24: Module '__future__' has no member 'annotations'",
|
||||
"/src/tomllib/_parser.py:7:29: Module 'collections.abc' has no member 'Iterable'",
|
||||
"Line 69 is too long (89 characters)",
|
||||
"Use double quotes for strings",
|
||||
|
||||
52
crates/ruff_db/src/display.rs
Normal file
52
crates/ruff_db/src/display.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
pub trait FormatterJoinExtension<'b> {
|
||||
fn join<'a>(&'a mut self, separator: &'static str) -> Join<'a, 'b>;
|
||||
}
|
||||
|
||||
impl<'b> FormatterJoinExtension<'b> for Formatter<'b> {
|
||||
fn join<'a>(&'a mut self, separator: &'static str) -> Join<'a, 'b> {
|
||||
Join {
|
||||
fmt: self,
|
||||
separator,
|
||||
result: fmt::Result::Ok(()),
|
||||
seen_first: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Join<'a, 'b> {
|
||||
fmt: &'a mut Formatter<'b>,
|
||||
separator: &'static str,
|
||||
result: fmt::Result,
|
||||
seen_first: bool,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Join<'a, 'b> {
|
||||
pub fn entry(&mut self, item: &dyn Display) -> &mut Self {
|
||||
if self.seen_first {
|
||||
self.result = self
|
||||
.result
|
||||
.and_then(|()| self.fmt.write_str(self.separator));
|
||||
} else {
|
||||
self.seen_first = true;
|
||||
}
|
||||
self.result = self.result.and_then(|()| item.fmt(self.fmt));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn entries<I, F>(&mut self, items: I) -> &mut Self
|
||||
where
|
||||
I: IntoIterator<Item = F>,
|
||||
F: Display,
|
||||
{
|
||||
for item in items {
|
||||
self.entry(&item);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn finish(&mut self) -> fmt::Result {
|
||||
self.result
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use crate::files::Files;
|
||||
use crate::system::System;
|
||||
use crate::vendored::VendoredFileSystem;
|
||||
|
||||
pub mod display;
|
||||
pub mod file_revision;
|
||||
pub mod files;
|
||||
pub mod parsed;
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
use regex::{Captures, Regex};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
@@ -33,7 +34,26 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
|
||||
let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap();
|
||||
if linter.url().is_some() {
|
||||
output.push_str(&format!("Derived from the **{}** linter.", linter.name()));
|
||||
let common_prefix: String = match linter.common_prefix() {
|
||||
"" => linter
|
||||
.upstream_categories()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|c| c.prefix)
|
||||
.join("-"),
|
||||
prefix => prefix.to_string(),
|
||||
};
|
||||
let anchor = format!(
|
||||
"{}-{}",
|
||||
linter.name().to_lowercase(),
|
||||
common_prefix.to_lowercase()
|
||||
);
|
||||
|
||||
output.push_str(&format!(
|
||||
"Derived from the **[{}](../rules.md#{})** linter.",
|
||||
linter.name(),
|
||||
anchor
|
||||
));
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"target_version": "py310"}
|
||||
10
crates/ruff_python_formatter/resources/test/fixtures/black/cases/backslash_before_indent.py
vendored
Normal file
10
crates/ruff_python_formatter/resources/test/fixtures/black/cases/backslash_before_indent.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
class Plotter:
|
||||
\
|
||||
pass
|
||||
|
||||
class AnotherCase:
|
||||
\
|
||||
"""Some
|
||||
\
|
||||
Docstring
|
||||
"""
|
||||
@@ -0,0 +1,10 @@
|
||||
class Plotter:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AnotherCase:
|
||||
"""Some
|
||||
\
|
||||
Docstring
|
||||
"""
|
||||
@@ -2,5 +2,7 @@ def bob(): # pylint: disable=W9016
|
||||
pass
|
||||
|
||||
|
||||
def bobtwo(): # some comment here
|
||||
def bobtwo():
|
||||
|
||||
# some comment here
|
||||
pass
|
||||
|
||||
@@ -67,3 +67,63 @@ async def async_function(self):
|
||||
@decorated
|
||||
async def async_function(self):
|
||||
...
|
||||
|
||||
class ClassA:
|
||||
def f(self):
|
||||
|
||||
...
|
||||
|
||||
|
||||
class ClassB:
|
||||
def f(self):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
...
|
||||
|
||||
|
||||
class ClassC:
|
||||
def f(self):
|
||||
|
||||
...
|
||||
# Comment
|
||||
|
||||
|
||||
class ClassD:
|
||||
def f(self):# Comment 1
|
||||
|
||||
...# Comment 2
|
||||
# Comment 3
|
||||
|
||||
|
||||
class ClassE:
|
||||
def f(self):
|
||||
|
||||
...
|
||||
def f2(self):
|
||||
print(10)
|
||||
|
||||
|
||||
class ClassF:
|
||||
def f(self):
|
||||
|
||||
...# Comment 2
|
||||
|
||||
|
||||
class ClassG:
|
||||
def f(self):#Comment 1
|
||||
|
||||
...# Comment 2
|
||||
|
||||
|
||||
class ClassH:
|
||||
def f(self):
|
||||
#Comment
|
||||
|
||||
...
|
||||
|
||||
@@ -70,3 +70,47 @@ async def async_function(self): ...
|
||||
|
||||
@decorated
|
||||
async def async_function(self): ...
|
||||
|
||||
|
||||
class ClassA:
|
||||
def f(self): ...
|
||||
|
||||
|
||||
class ClassB:
|
||||
def f(self): ...
|
||||
|
||||
|
||||
class ClassC:
|
||||
def f(self):
|
||||
|
||||
...
|
||||
# Comment
|
||||
|
||||
|
||||
class ClassD:
|
||||
def f(self): # Comment 1
|
||||
|
||||
... # Comment 2
|
||||
# Comment 3
|
||||
|
||||
|
||||
class ClassE:
|
||||
def f(self): ...
|
||||
def f2(self):
|
||||
print(10)
|
||||
|
||||
|
||||
class ClassF:
|
||||
def f(self): ... # Comment 2
|
||||
|
||||
|
||||
class ClassG:
|
||||
def f(self): # Comment 1
|
||||
... # Comment 2
|
||||
|
||||
|
||||
class ClassH:
|
||||
def f(self):
|
||||
# Comment
|
||||
|
||||
...
|
||||
|
||||
13
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtonoff6.py
vendored
Normal file
13
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtonoff6.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Regression test for https://github.com/psf/black/issues/2478.
|
||||
def foo():
|
||||
arr = (
|
||||
(3833567325051000, 5, 1, 2, 4229.25, 6, 0),
|
||||
# fmt: off
|
||||
)
|
||||
|
||||
|
||||
# Regression test for https://github.com/psf/black/issues/3458.
|
||||
dependencies = {
|
||||
a: b,
|
||||
# fmt: off
|
||||
}
|
||||
13
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtonoff6.py.expect
vendored
Normal file
13
crates/ruff_python_formatter/resources/test/fixtures/black/cases/fmtonoff6.py.expect
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Regression test for https://github.com/psf/black/issues/2478.
|
||||
def foo():
|
||||
arr = (
|
||||
(3833567325051000, 5, 1, 2, 4229.25, 6, 0),
|
||||
# fmt: off
|
||||
)
|
||||
|
||||
|
||||
# Regression test for https://github.com/psf/black/issues/3458.
|
||||
dependencies = {
|
||||
a: b,
|
||||
# fmt: off
|
||||
}
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
#
|
||||
|
||||
|
||||
#
|
||||
pass
|
||||
|
||||
|
||||
@@ -59,3 +59,61 @@ some_module.some_function(
|
||||
some_module.some_function(
|
||||
argument1, (one, two,), argument4, argument5, argument6
|
||||
)
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised return type
|
||||
int
|
||||
):
|
||||
...
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised return type
|
||||
# more
|
||||
int
|
||||
# another
|
||||
):
|
||||
...
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised new union return type
|
||||
int | str | bytes
|
||||
):
|
||||
...
|
||||
|
||||
def foo() -> (
|
||||
# comment inside plain tuple
|
||||
):
|
||||
pass
|
||||
|
||||
def foo(arg: (# comment with non-return annotation
|
||||
int
|
||||
# comment with non-return annotation
|
||||
)):
|
||||
pass
|
||||
|
||||
def foo(arg: (# comment with non-return annotation
|
||||
int | range | memoryview
|
||||
# comment with non-return annotation
|
||||
)):
|
||||
pass
|
||||
|
||||
def foo(arg: (# only before
|
||||
int
|
||||
)):
|
||||
pass
|
||||
|
||||
def foo(arg: (
|
||||
int
|
||||
# only after
|
||||
)):
|
||||
pass
|
||||
|
||||
variable: ( # annotation
|
||||
because
|
||||
# why not
|
||||
)
|
||||
|
||||
variable: (
|
||||
because
|
||||
# why not
|
||||
)
|
||||
|
||||
@@ -112,3 +112,75 @@ some_module.some_function(
|
||||
argument5,
|
||||
argument6,
|
||||
)
|
||||
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised return type
|
||||
int
|
||||
): ...
|
||||
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised return type
|
||||
# more
|
||||
int
|
||||
# another
|
||||
): ...
|
||||
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised new union return type
|
||||
int
|
||||
| str
|
||||
| bytes
|
||||
): ...
|
||||
|
||||
|
||||
def foo() -> (
|
||||
# comment inside plain tuple
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo(
|
||||
arg: ( # comment with non-return annotation
|
||||
int
|
||||
# comment with non-return annotation
|
||||
),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo(
|
||||
arg: ( # comment with non-return annotation
|
||||
int
|
||||
| range
|
||||
| memoryview
|
||||
# comment with non-return annotation
|
||||
),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo(arg: int): # only before
|
||||
pass
|
||||
|
||||
|
||||
def foo(
|
||||
arg: (
|
||||
int
|
||||
# only after
|
||||
),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
variable: ( # annotation
|
||||
because
|
||||
# why not
|
||||
)
|
||||
|
||||
variable: (
|
||||
because
|
||||
# why not
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ def foo2(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parame
|
||||
def foo3(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
|
||||
def foo4(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
|
||||
|
||||
# Adding some unformatted code covering a wide range of syntaxes.
|
||||
# Adding some unformated code covering a wide range of syntaxes.
|
||||
|
||||
if True:
|
||||
# Incorrectly indented prefix comments.
|
||||
|
||||
@@ -28,7 +28,7 @@ def foo3(
|
||||
|
||||
def foo4(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
|
||||
|
||||
# Adding some unformatted code covering a wide range of syntaxes.
|
||||
# Adding some unformated code covering a wide range of syntaxes.
|
||||
|
||||
if True:
|
||||
# Incorrectly indented prefix comments.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# flags: --line-ranges=6-1000
|
||||
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
|
||||
# flag above as it's formatting specifically these lines.
|
||||
def foo1(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
|
||||
def foo2(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
|
||||
def foo3(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
|
||||
def foo4(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
|
||||
@@ -0,0 +1,27 @@
|
||||
# flags: --line-ranges=6-1000
|
||||
# NOTE: If you need to modify this file, pay special attention to the --line-ranges=
|
||||
# flag above as it's formatting specifically these lines.
|
||||
def foo1(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
|
||||
def foo2(parameter_1, parameter_2, parameter_3, parameter_4, parameter_5, parameter_6, parameter_7): pass
|
||||
def foo3(
|
||||
parameter_1,
|
||||
parameter_2,
|
||||
parameter_3,
|
||||
parameter_4,
|
||||
parameter_5,
|
||||
parameter_6,
|
||||
parameter_7,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo4(
|
||||
parameter_1,
|
||||
parameter_2,
|
||||
parameter_3,
|
||||
parameter_4,
|
||||
parameter_5,
|
||||
parameter_6,
|
||||
parameter_7,
|
||||
):
|
||||
pass
|
||||
@@ -82,7 +82,7 @@ match (0, 1, 2):
|
||||
match x:
|
||||
case [0]:
|
||||
y = 0
|
||||
case [1, 0] if (x := x[:0]):
|
||||
case [1, 0] if x := x[:0]:
|
||||
y = 1
|
||||
case [1, 0]:
|
||||
y = 2
|
||||
|
||||
@@ -82,7 +82,7 @@ match (0, 1, 2):
|
||||
match x:
|
||||
case [0]:
|
||||
y = 0
|
||||
case [1, 0] if (x := x[:0]):
|
||||
case [1, 0] if x := x[:0]:
|
||||
y = 1
|
||||
case [1, 0]:
|
||||
y = 2
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"preview": "enabled", "target_version": "py310"}
|
||||
@@ -0,0 +1,32 @@
|
||||
match match:
|
||||
case "test" if case != "not very loooooooooooooog condition": # comment
|
||||
pass
|
||||
|
||||
match smth:
|
||||
case "test" if "any long condition" != "another long condition" and "this is a long condition":
|
||||
pass
|
||||
case test if "any long condition" != "another long condition" and "this is a looooong condition":
|
||||
pass
|
||||
case test if "any long condition" != "another long condition" and "this is a looooong condition": # some additional comments
|
||||
pass
|
||||
case test if (True): # some comment
|
||||
pass
|
||||
case test if (False
|
||||
): # some comment
|
||||
pass
|
||||
case test if (True # some comment
|
||||
):
|
||||
pass # some comment
|
||||
case cases if (True # some comment
|
||||
): # some other comment
|
||||
pass # some comment
|
||||
case match if (True # some comment
|
||||
):
|
||||
pass # some comment
|
||||
|
||||
# case black_test_patma_052 (originally in the pattern_matching_complex test case)
|
||||
match x:
|
||||
case [1, 0] if x := x[:0]:
|
||||
y = 1
|
||||
case [1, 0] if (x := x[:0]):
|
||||
y = 1
|
||||
@@ -0,0 +1,36 @@
|
||||
match match:
|
||||
case "test" if case != "not very loooooooooooooog condition": # comment
|
||||
pass
|
||||
|
||||
match smth:
|
||||
case "test" if (
|
||||
"any long condition" != "another long condition" and "this is a long condition"
|
||||
):
|
||||
pass
|
||||
case test if (
|
||||
"any long condition" != "another long condition"
|
||||
and "this is a looooong condition"
|
||||
):
|
||||
pass
|
||||
case test if (
|
||||
"any long condition" != "another long condition"
|
||||
and "this is a looooong condition"
|
||||
): # some additional comments
|
||||
pass
|
||||
case test if True: # some comment
|
||||
pass
|
||||
case test if False: # some comment
|
||||
pass
|
||||
case test if True: # some comment
|
||||
pass # some comment
|
||||
case cases if True: # some comment # some other comment
|
||||
pass # some comment
|
||||
case match if True: # some comment
|
||||
pass # some comment
|
||||
|
||||
# case black_test_patma_052 (originally in the pattern_matching_complex test case)
|
||||
match x:
|
||||
case [1, 0] if x := x[:0]:
|
||||
y = 1
|
||||
case [1, 0] if x := x[:0]:
|
||||
y = 1
|
||||
@@ -19,7 +19,7 @@ z: (Short
|
||||
z: (int) = 2.3
|
||||
z: ((int)) = foo()
|
||||
|
||||
# In case I go for not enforcing parentheses, this might get improved at the same time
|
||||
# In case I go for not enforcing parantheses, this might get improved at the same time
|
||||
x = (
|
||||
z
|
||||
== 9999999999999999999999999999999999999999
|
||||
|
||||
@@ -28,7 +28,7 @@ z: Short | Short2 | Short3 | Short4 = 8
|
||||
z: int = 2.3
|
||||
z: int = foo()
|
||||
|
||||
# In case I go for not enforcing parentheses, this might get improved at the same time
|
||||
# In case I go for not enforcing parantheses, this might get improved at the same time
|
||||
x = (
|
||||
z
|
||||
== 9999999999999999999999999999999999999999
|
||||
|
||||
1
crates/ruff_python_formatter/resources/test/fixtures/black/cases/pep_701.options.json
vendored
Normal file
1
crates/ruff_python_formatter/resources/test/fixtures/black/cases/pep_701.options.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"target_version": "py312"}
|
||||
136
crates/ruff_python_formatter/resources/test/fixtures/black/cases/pep_701.py
vendored
Normal file
136
crates/ruff_python_formatter/resources/test/fixtures/black/cases/pep_701.py
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
x = f"foo"
|
||||
x = f'foo'
|
||||
x = f"""foo"""
|
||||
x = f'''foo'''
|
||||
x = f"foo {{ bar {{ baz"
|
||||
x = f"foo {{ {2 + 2}bar {{ baz"
|
||||
x = f'foo {{ {2 + 2}bar {{ baz'
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
x = f'''foo {{ {2 + 2}bar {{ baz'''
|
||||
|
||||
# edge case: FSTRING_MIDDLE containing only whitespace should not be stripped
|
||||
x = f"{a} {b}"
|
||||
|
||||
x = f"foo {
|
||||
2 + 2
|
||||
} bar baz"
|
||||
|
||||
x = f"foo {{ {"a {2 + 2} b"}bar {{ baz"
|
||||
x = f"foo {{ {f'a {2 + 2} b'}bar {{ baz"
|
||||
x = f"foo {{ {f"a {2 + 2} b"}bar {{ baz"
|
||||
|
||||
x = f"foo {{ {f'a {f"a {2 + 2} b"} b'}bar {{ baz"
|
||||
x = f"foo {{ {f"a {f"a {2 + 2} b"} b"}bar {{ baz"
|
||||
|
||||
x = """foo {{ {2 + 2}bar
|
||||
baz"""
|
||||
|
||||
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
|
||||
x = f"""foo {{ {
|
||||
2 + 2
|
||||
}bar {{ baz"""
|
||||
|
||||
|
||||
x = f"""foo {{ {
|
||||
2 + 2
|
||||
}bar
|
||||
baz"""
|
||||
|
||||
x = f"""foo {{ a
|
||||
foo {2 + 2}bar {{ baz
|
||||
|
||||
x = f"foo {{ {
|
||||
2 + 2 # comment
|
||||
}bar"
|
||||
|
||||
{{ baz
|
||||
|
||||
}} buzz
|
||||
|
||||
{print("abc" + "def"
|
||||
)}
|
||||
abc"""
|
||||
|
||||
# edge case: end triple quotes at index zero
|
||||
f"""foo {2+2} bar
|
||||
"""
|
||||
|
||||
f' \' {f"'"} \' '
|
||||
f" \" {f'"'} \" "
|
||||
|
||||
x = f"a{2+2:=^72}b"
|
||||
x = f"a{2+2:x}b"
|
||||
|
||||
rf'foo'
|
||||
rf'{foo}'
|
||||
|
||||
f"{x:{y}d}"
|
||||
|
||||
x = f"a{2+2:=^{x}}b"
|
||||
x = f"a{2+2:=^{foo(x+y**2):something else}}b"
|
||||
x = f"a{2+2:=^{foo(x+y**2):something else}one more}b"
|
||||
f'{(abc:=10)}'
|
||||
|
||||
f"This is a really long string, but just make sure that you reflow fstrings {
|
||||
2+2:d
|
||||
}"
|
||||
f"This is a really long string, but just make sure that you reflow fstrings correctly {2+2:d}"
|
||||
|
||||
f"{2+2=}"
|
||||
f"{2+2 = }"
|
||||
f"{ 2 + 2 = }"
|
||||
|
||||
f"""foo {
|
||||
datetime.datetime.now():%Y
|
||||
%m
|
||||
%d
|
||||
}"""
|
||||
|
||||
f"{
|
||||
X
|
||||
!r
|
||||
}"
|
||||
|
||||
raise ValueError(
|
||||
"xxxxxxxxxxxIncorrect --line-ranges format, expect START-END, found"
|
||||
f" {lines_str!r}"
|
||||
)
|
||||
|
||||
f"`escape` only permitted in {{'html', 'latex', 'latex-math'}}, \
|
||||
got {escape}"
|
||||
|
||||
x = f'\N{GREEK CAPITAL LETTER DELTA} \N{SNOWMAN} {x}'
|
||||
fr'\{{\}}'
|
||||
|
||||
f"""
|
||||
WITH {f'''
|
||||
{1}_cte AS ()'''}
|
||||
"""
|
||||
|
||||
value: str = f'''foo
|
||||
'''
|
||||
|
||||
log(
|
||||
f"Received operation {server_operation.name} from "
|
||||
f"{self.writer._transport.get_extra_info('peername')}", # type: ignore[attr-defined]
|
||||
level=0,
|
||||
)
|
||||
|
||||
f"{1:{f'{2}'}}"
|
||||
f'{1:{f'{2}'}}'
|
||||
f'{1:{2}d}'
|
||||
|
||||
f'{{\\"kind\\":\\"ConfigMap\\",\\"metadata\\":{{\\"annotations\\":{{}},\\"name\\":\\"cluster-info\\",\\"namespace\\":\\"amazon-cloudwatch\\"}}}}'
|
||||
|
||||
f"""{'''
|
||||
'''}"""
|
||||
|
||||
f"{'\''}"
|
||||
f"{f'\''}"
|
||||
|
||||
f'{1}\{{'
|
||||
f'{2} foo \{{[\}}'
|
||||
f'\{3}'
|
||||
rf"\{"a"}"
|
||||
136
crates/ruff_python_formatter/resources/test/fixtures/black/cases/pep_701.py.expect
vendored
Normal file
136
crates/ruff_python_formatter/resources/test/fixtures/black/cases/pep_701.py.expect
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
x = f"foo"
|
||||
x = f"foo"
|
||||
x = f"""foo"""
|
||||
x = f"""foo"""
|
||||
x = f"foo {{ bar {{ baz"
|
||||
x = f"foo {{ {2 + 2}bar {{ baz"
|
||||
x = f"foo {{ {2 + 2}bar {{ baz"
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
|
||||
# edge case: FSTRING_MIDDLE containing only whitespace should not be stripped
|
||||
x = f"{a} {b}"
|
||||
|
||||
x = f"foo {
|
||||
2 + 2
|
||||
} bar baz"
|
||||
|
||||
x = f"foo {{ {"a {2 + 2} b"}bar {{ baz"
|
||||
x = f"foo {{ {f'a {2 + 2} b'}bar {{ baz"
|
||||
x = f"foo {{ {f"a {2 + 2} b"}bar {{ baz"
|
||||
|
||||
x = f"foo {{ {f'a {f"a {2 + 2} b"} b'}bar {{ baz"
|
||||
x = f"foo {{ {f"a {f"a {2 + 2} b"} b"}bar {{ baz"
|
||||
|
||||
x = """foo {{ {2 + 2}bar
|
||||
baz"""
|
||||
|
||||
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
|
||||
x = f"""foo {{ {
|
||||
2 + 2
|
||||
}bar {{ baz"""
|
||||
|
||||
|
||||
x = f"""foo {{ {
|
||||
2 + 2
|
||||
}bar
|
||||
baz"""
|
||||
|
||||
x = f"""foo {{ a
|
||||
foo {2 + 2}bar {{ baz
|
||||
|
||||
x = f"foo {{ {
|
||||
2 + 2 # comment
|
||||
}bar"
|
||||
|
||||
{{ baz
|
||||
|
||||
}} buzz
|
||||
|
||||
{print("abc" + "def"
|
||||
)}
|
||||
abc"""
|
||||
|
||||
# edge case: end triple quotes at index zero
|
||||
f"""foo {2+2} bar
|
||||
"""
|
||||
|
||||
f' \' {f"'"} \' '
|
||||
f" \" {f'"'} \" "
|
||||
|
||||
x = f"a{2+2:=^72}b"
|
||||
x = f"a{2+2:x}b"
|
||||
|
||||
rf"foo"
|
||||
rf"{foo}"
|
||||
|
||||
f"{x:{y}d}"
|
||||
|
||||
x = f"a{2+2:=^{x}}b"
|
||||
x = f"a{2+2:=^{foo(x+y**2):something else}}b"
|
||||
x = f"a{2+2:=^{foo(x+y**2):something else}one more}b"
|
||||
f"{(abc:=10)}"
|
||||
|
||||
f"This is a really long string, but just make sure that you reflow fstrings {
|
||||
2+2:d
|
||||
}"
|
||||
f"This is a really long string, but just make sure that you reflow fstrings correctly {2+2:d}"
|
||||
|
||||
f"{2+2=}"
|
||||
f"{2+2 = }"
|
||||
f"{ 2 + 2 = }"
|
||||
|
||||
f"""foo {
|
||||
datetime.datetime.now():%Y
|
||||
%m
|
||||
%d
|
||||
}"""
|
||||
|
||||
f"{
|
||||
X
|
||||
!r
|
||||
}"
|
||||
|
||||
raise ValueError(
|
||||
"xxxxxxxxxxxIncorrect --line-ranges format, expect START-END, found"
|
||||
f" {lines_str!r}"
|
||||
)
|
||||
|
||||
f"`escape` only permitted in {{'html', 'latex', 'latex-math'}}, \
|
||||
got {escape}"
|
||||
|
||||
x = f"\N{GREEK CAPITAL LETTER DELTA} \N{SNOWMAN} {x}"
|
||||
rf"\{{\}}"
|
||||
|
||||
f"""
|
||||
WITH {f'''
|
||||
{1}_cte AS ()'''}
|
||||
"""
|
||||
|
||||
value: str = f"""foo
|
||||
"""
|
||||
|
||||
log(
|
||||
f"Received operation {server_operation.name} from "
|
||||
f"{self.writer._transport.get_extra_info('peername')}", # type: ignore[attr-defined]
|
||||
level=0,
|
||||
)
|
||||
|
||||
f"{1:{f'{2}'}}"
|
||||
f"{1:{f'{2}'}}"
|
||||
f"{1:{2}d}"
|
||||
|
||||
f'{{\\"kind\\":\\"ConfigMap\\",\\"metadata\\":{{\\"annotations\\":{{}},\\"name\\":\\"cluster-info\\",\\"namespace\\":\\"amazon-cloudwatch\\"}}}}'
|
||||
|
||||
f"""{'''
|
||||
'''}"""
|
||||
|
||||
f"{'\''}"
|
||||
f"{f'\''}"
|
||||
|
||||
f"{1}\{{"
|
||||
f"{2} foo \{{[\}}"
|
||||
f"\{3}"
|
||||
rf"\{"a"}"
|
||||
@@ -173,3 +173,10 @@ this_will_also_become_one_line = ( # comment
|
||||
"b"
|
||||
"c"
|
||||
)
|
||||
|
||||
assert some_var == expected_result, """
|
||||
test
|
||||
"""
|
||||
assert some_var == expected_result, f"""
|
||||
expected: {expected_result}
|
||||
actual: {some_var}"""
|
||||
|
||||
@@ -207,3 +207,10 @@ this_will_stay_on_three_lines = (
|
||||
)
|
||||
|
||||
this_will_also_become_one_line = "abc" # comment
|
||||
|
||||
assert some_var == expected_result, """
|
||||
test
|
||||
"""
|
||||
assert some_var == expected_result, f"""
|
||||
expected: {expected_result}
|
||||
actual: {some_var}"""
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"preview": "enabled", "target_version": "py311"}
|
||||
@@ -0,0 +1,5 @@
|
||||
def fn(*args: *tuple[*A, B]) -> None:
|
||||
pass
|
||||
|
||||
|
||||
fn.__annotations__
|
||||
@@ -0,0 +1,5 @@
|
||||
def fn(*args: *tuple[*A, B]) -> None:
|
||||
pass
|
||||
|
||||
|
||||
fn.__annotations__
|
||||
23
crates/ruff_python_formatter/resources/test/fixtures/black/cases/split_delimiter_comments.py
vendored
Normal file
23
crates/ruff_python_formatter/resources/test/fixtures/black/cases/split_delimiter_comments.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
a = (
|
||||
1 + # type: ignore
|
||||
2 # type: ignore
|
||||
)
|
||||
a = (
|
||||
1 # type: ignore
|
||||
+ 2 # type: ignore
|
||||
)
|
||||
bad_split3 = (
|
||||
"What if we have inline comments on " # First Comment
|
||||
"each line of a bad split? In that " # Second Comment
|
||||
"case, we should just leave it alone." # Third Comment
|
||||
)
|
||||
parametrize(
|
||||
(
|
||||
{},
|
||||
{},
|
||||
),
|
||||
( # foobar
|
||||
{},
|
||||
{},
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
a = (
|
||||
1 # type: ignore
|
||||
+ 2 # type: ignore
|
||||
)
|
||||
a = (
|
||||
1 # type: ignore
|
||||
+ 2 # type: ignore
|
||||
)
|
||||
bad_split3 = (
|
||||
"What if we have inline comments on " # First Comment
|
||||
"each line of a bad split? In that " # Second Comment
|
||||
"case, we should just leave it alone." # Third Comment
|
||||
)
|
||||
parametrize(
|
||||
(
|
||||
{},
|
||||
{},
|
||||
),
|
||||
( # foobar
|
||||
{},
|
||||
{},
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
{"target_version": "py313"}
|
||||
19
crates/ruff_python_formatter/resources/test/fixtures/black/cases/type_param_defaults.py
vendored
Normal file
19
crates/ruff_python_formatter/resources/test/fixtures/black/cases/type_param_defaults.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
type A[T=int] = float
|
||||
type B[*P=int] = float
|
||||
type C[*Ts=int] = float
|
||||
type D[*Ts=*int] = float
|
||||
type D[something_that_is_very_very_very_long=something_that_is_very_very_very_long] = float
|
||||
type D[*something_that_is_very_very_very_long=*something_that_is_very_very_very_long] = float
|
||||
type something_that_is_long[something_that_is_long=something_that_is_long] = something_that_is_long
|
||||
|
||||
def simple[T=something_that_is_long](short1: int, short2: str, short3: bytes) -> float:
|
||||
pass
|
||||
|
||||
def longer[something_that_is_long=something_that_is_long](something_that_is_long: something_that_is_long) -> something_that_is_long:
|
||||
pass
|
||||
|
||||
def trailing_comma1[T=int,](a: str):
|
||||
pass
|
||||
|
||||
def trailing_comma2[T=int](a: str,):
|
||||
pass
|
||||
37
crates/ruff_python_formatter/resources/test/fixtures/black/cases/type_param_defaults.py.expect
vendored
Normal file
37
crates/ruff_python_formatter/resources/test/fixtures/black/cases/type_param_defaults.py.expect
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
type A[T = int] = float
|
||||
type B[*P = int] = float
|
||||
type C[*Ts = int] = float
|
||||
type D[*Ts = *int] = float
|
||||
type D[
|
||||
something_that_is_very_very_very_long = something_that_is_very_very_very_long
|
||||
] = float
|
||||
type D[
|
||||
*something_that_is_very_very_very_long = *something_that_is_very_very_very_long
|
||||
] = float
|
||||
type something_that_is_long[
|
||||
something_that_is_long = something_that_is_long
|
||||
] = something_that_is_long
|
||||
|
||||
|
||||
def simple[
|
||||
T = something_that_is_long
|
||||
](short1: int, short2: str, short3: bytes) -> float:
|
||||
pass
|
||||
|
||||
|
||||
def longer[
|
||||
something_that_is_long = something_that_is_long
|
||||
](something_that_is_long: something_that_is_long) -> something_that_is_long:
|
||||
pass
|
||||
|
||||
|
||||
def trailing_comma1[
|
||||
T = int,
|
||||
](a: str):
|
||||
pass
|
||||
|
||||
|
||||
def trailing_comma2[
|
||||
T = int
|
||||
](a: str,):
|
||||
pass
|
||||
@@ -104,6 +104,9 @@ IGNORE_LIST = [
|
||||
|
||||
# Uses a different output format
|
||||
"decorators.py",
|
||||
|
||||
# Tests line ranges that fall outside the source range. This is a CLI test case and not a formatting test case.
|
||||
"line_ranges_outside_source.py",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/backslash_before_indent.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```python
|
||||
class Plotter:
|
||||
\
|
||||
pass
|
||||
|
||||
class AnotherCase:
|
||||
\
|
||||
"""Some
|
||||
\
|
||||
Docstring
|
||||
"""
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -1,5 +1,4 @@
|
||||
class Plotter:
|
||||
-
|
||||
pass
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```python
|
||||
class Plotter:
|
||||
pass
|
||||
|
||||
|
||||
class AnotherCase:
|
||||
"""Some
|
||||
\
|
||||
Docstring
|
||||
"""
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```python
|
||||
class Plotter:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class AnotherCase:
|
||||
"""Some
|
||||
\
|
||||
Docstring
|
||||
"""
|
||||
```
|
||||
@@ -21,16 +21,16 @@ def bobtwo(): \
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -1,6 +1,8 @@
|
||||
@@ -1,8 +1,8 @@
|
||||
-def bob(): # pylint: disable=W9016
|
||||
+def bob():
|
||||
+ # pylint: disable=W9016
|
||||
pass
|
||||
|
||||
|
||||
-def bobtwo(): # some comment here
|
||||
+def bobtwo():
|
||||
+ # some comment here
|
||||
def bobtwo():
|
||||
-
|
||||
# some comment here
|
||||
pass
|
||||
```
|
||||
|
||||
@@ -54,8 +54,8 @@ def bob(): # pylint: disable=W9016
|
||||
pass
|
||||
|
||||
|
||||
def bobtwo(): # some comment here
|
||||
def bobtwo():
|
||||
|
||||
# some comment here
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/dummy_implementations.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```python
|
||||
from typing import NoReturn, Protocol, Union, overload
|
||||
|
||||
class Empty:
|
||||
...
|
||||
|
||||
def dummy(a): ...
|
||||
async def other(b): ...
|
||||
|
||||
|
||||
@overload
|
||||
def a(arg: int) -> int: ...
|
||||
@overload
|
||||
def a(arg: str) -> str: ...
|
||||
@overload
|
||||
def a(arg: object) -> NoReturn: ...
|
||||
def a(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
return arg
|
||||
|
||||
class Proto(Protocol):
|
||||
def foo(self, a: int) -> int:
|
||||
...
|
||||
|
||||
def bar(self, b: str) -> str: ...
|
||||
def baz(self, c: bytes) -> str:
|
||||
...
|
||||
|
||||
|
||||
def dummy_two():
|
||||
...
|
||||
@dummy
|
||||
def dummy_three():
|
||||
...
|
||||
|
||||
def dummy_four():
|
||||
...
|
||||
|
||||
@overload
|
||||
def b(arg: int) -> int: ...
|
||||
|
||||
@overload
|
||||
def b(arg: str) -> str: ...
|
||||
@overload
|
||||
def b(arg: object) -> NoReturn: ...
|
||||
|
||||
def b(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
return arg
|
||||
|
||||
def has_comment():
|
||||
... # still a dummy
|
||||
|
||||
if some_condition:
|
||||
...
|
||||
|
||||
if already_dummy: ...
|
||||
|
||||
class AsyncCls:
|
||||
async def async_method(self):
|
||||
...
|
||||
|
||||
async def async_function(self):
|
||||
...
|
||||
|
||||
@decorated
|
||||
async def async_function(self):
|
||||
...
|
||||
|
||||
class ClassA:
|
||||
def f(self):
|
||||
|
||||
...
|
||||
|
||||
|
||||
class ClassB:
|
||||
def f(self):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
...
|
||||
|
||||
|
||||
class ClassC:
|
||||
def f(self):
|
||||
|
||||
...
|
||||
# Comment
|
||||
|
||||
|
||||
class ClassD:
|
||||
def f(self):# Comment 1
|
||||
|
||||
...# Comment 2
|
||||
# Comment 3
|
||||
|
||||
|
||||
class ClassE:
|
||||
def f(self):
|
||||
|
||||
...
|
||||
def f2(self):
|
||||
print(10)
|
||||
|
||||
|
||||
class ClassF:
|
||||
def f(self):
|
||||
|
||||
...# Comment 2
|
||||
|
||||
|
||||
class ClassG:
|
||||
def f(self):#Comment 1
|
||||
|
||||
...# Comment 2
|
||||
|
||||
|
||||
class ClassH:
|
||||
def f(self):
|
||||
#Comment
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -82,14 +82,12 @@
|
||||
|
||||
class ClassC:
|
||||
def f(self):
|
||||
-
|
||||
...
|
||||
# Comment
|
||||
|
||||
|
||||
class ClassD:
|
||||
def f(self): # Comment 1
|
||||
-
|
||||
... # Comment 2
|
||||
# Comment 3
|
||||
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```python
|
||||
from typing import NoReturn, Protocol, Union, overload
|
||||
|
||||
|
||||
class Empty: ...
|
||||
|
||||
|
||||
def dummy(a): ...
|
||||
async def other(b): ...
|
||||
|
||||
|
||||
@overload
|
||||
def a(arg: int) -> int: ...
|
||||
@overload
|
||||
def a(arg: str) -> str: ...
|
||||
@overload
|
||||
def a(arg: object) -> NoReturn: ...
|
||||
def a(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
return arg
|
||||
|
||||
|
||||
class Proto(Protocol):
|
||||
def foo(self, a: int) -> int: ...
|
||||
|
||||
def bar(self, b: str) -> str: ...
|
||||
def baz(self, c: bytes) -> str: ...
|
||||
|
||||
|
||||
def dummy_two(): ...
|
||||
@dummy
|
||||
def dummy_three(): ...
|
||||
|
||||
|
||||
def dummy_four(): ...
|
||||
|
||||
|
||||
@overload
|
||||
def b(arg: int) -> int: ...
|
||||
|
||||
|
||||
@overload
|
||||
def b(arg: str) -> str: ...
|
||||
@overload
|
||||
def b(arg: object) -> NoReturn: ...
|
||||
|
||||
|
||||
def b(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
return arg
|
||||
|
||||
|
||||
def has_comment(): ... # still a dummy
|
||||
|
||||
|
||||
if some_condition:
|
||||
...
|
||||
|
||||
if already_dummy:
|
||||
...
|
||||
|
||||
|
||||
class AsyncCls:
|
||||
async def async_method(self): ...
|
||||
|
||||
|
||||
async def async_function(self): ...
|
||||
|
||||
|
||||
@decorated
|
||||
async def async_function(self): ...
|
||||
|
||||
|
||||
class ClassA:
|
||||
def f(self): ...
|
||||
|
||||
|
||||
class ClassB:
|
||||
def f(self): ...
|
||||
|
||||
|
||||
class ClassC:
|
||||
def f(self):
|
||||
...
|
||||
# Comment
|
||||
|
||||
|
||||
class ClassD:
|
||||
def f(self): # Comment 1
|
||||
... # Comment 2
|
||||
# Comment 3
|
||||
|
||||
|
||||
class ClassE:
|
||||
def f(self): ...
|
||||
def f2(self):
|
||||
print(10)
|
||||
|
||||
|
||||
class ClassF:
|
||||
def f(self): ... # Comment 2
|
||||
|
||||
|
||||
class ClassG:
|
||||
def f(self): # Comment 1
|
||||
... # Comment 2
|
||||
|
||||
|
||||
class ClassH:
|
||||
def f(self):
|
||||
# Comment
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```python
|
||||
from typing import NoReturn, Protocol, Union, overload
|
||||
|
||||
|
||||
class Empty: ...
|
||||
|
||||
|
||||
def dummy(a): ...
|
||||
async def other(b): ...
|
||||
|
||||
|
||||
@overload
|
||||
def a(arg: int) -> int: ...
|
||||
@overload
|
||||
def a(arg: str) -> str: ...
|
||||
@overload
|
||||
def a(arg: object) -> NoReturn: ...
|
||||
def a(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
return arg
|
||||
|
||||
|
||||
class Proto(Protocol):
|
||||
def foo(self, a: int) -> int: ...
|
||||
|
||||
def bar(self, b: str) -> str: ...
|
||||
def baz(self, c: bytes) -> str: ...
|
||||
|
||||
|
||||
def dummy_two(): ...
|
||||
@dummy
|
||||
def dummy_three(): ...
|
||||
|
||||
|
||||
def dummy_four(): ...
|
||||
|
||||
|
||||
@overload
|
||||
def b(arg: int) -> int: ...
|
||||
|
||||
|
||||
@overload
|
||||
def b(arg: str) -> str: ...
|
||||
@overload
|
||||
def b(arg: object) -> NoReturn: ...
|
||||
|
||||
|
||||
def b(arg: Union[int, str, object]) -> Union[int, str]:
|
||||
if not isinstance(arg, (int, str)):
|
||||
raise TypeError
|
||||
return arg
|
||||
|
||||
|
||||
def has_comment(): ... # still a dummy
|
||||
|
||||
|
||||
if some_condition:
|
||||
...
|
||||
|
||||
if already_dummy:
|
||||
...
|
||||
|
||||
|
||||
class AsyncCls:
|
||||
async def async_method(self): ...
|
||||
|
||||
|
||||
async def async_function(self): ...
|
||||
|
||||
|
||||
@decorated
|
||||
async def async_function(self): ...
|
||||
|
||||
|
||||
class ClassA:
|
||||
def f(self): ...
|
||||
|
||||
|
||||
class ClassB:
|
||||
def f(self): ...
|
||||
|
||||
|
||||
class ClassC:
|
||||
def f(self):
|
||||
|
||||
...
|
||||
# Comment
|
||||
|
||||
|
||||
class ClassD:
|
||||
def f(self): # Comment 1
|
||||
|
||||
... # Comment 2
|
||||
# Comment 3
|
||||
|
||||
|
||||
class ClassE:
|
||||
def f(self): ...
|
||||
def f2(self):
|
||||
print(10)
|
||||
|
||||
|
||||
class ClassF:
|
||||
def f(self): ... # Comment 2
|
||||
|
||||
|
||||
class ClassG:
|
||||
def f(self): # Comment 1
|
||||
... # Comment 2
|
||||
|
||||
|
||||
class ClassH:
|
||||
def f(self):
|
||||
# Comment
|
||||
|
||||
...
|
||||
```
|
||||
@@ -128,7 +128,7 @@ a = [
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -5,62 +5,62 @@
|
||||
@@ -5,63 +5,62 @@
|
||||
|
||||
# Comment and statement processing is different enough that we'll test variations of both
|
||||
# contexts here
|
||||
@@ -167,8 +167,9 @@ a = [
|
||||
+
|
||||
#
|
||||
-
|
||||
+
|
||||
#
|
||||
-#
|
||||
|
||||
+#
|
||||
|
||||
#
|
||||
pass
|
||||
@@ -211,7 +212,7 @@ a = [
|
||||
pass
|
||||
|
||||
|
||||
@@ -68,25 +68,23 @@
|
||||
@@ -69,25 +68,23 @@
|
||||
def foo():
|
||||
pass
|
||||
|
||||
@@ -385,6 +386,7 @@ a = []
|
||||
|
||||
#
|
||||
|
||||
|
||||
#
|
||||
pass
|
||||
|
||||
|
||||
@@ -0,0 +1,569 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/function_trailing_comma.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```python
|
||||
def f(a,):
|
||||
d = {'key': 'value',}
|
||||
tup = (1,)
|
||||
|
||||
def f2(a,b,):
|
||||
d = {'key': 'value', 'key2': 'value2',}
|
||||
tup = (1,2,)
|
||||
|
||||
def f(a:int=1,):
|
||||
call(arg={'explode': 'this',})
|
||||
call2(arg=[1,2,3],)
|
||||
x = {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
}["a"]
|
||||
if a == {"a": 1,"b": 2,"c": 3,"d": 4,"e": 5,"f": 6,"g": 7,"h": 8,}["a"]:
|
||||
pass
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
]:
|
||||
json = {"k": {"k2": {"k3": [1,]}}}
|
||||
|
||||
|
||||
|
||||
# The type annotation shouldn't get a trailing comma since that would change its type.
|
||||
# Relevant bug report: https://github.com/psf/black/issues/2381.
|
||||
def some_function_with_a_really_long_name() -> (
|
||||
returning_a_deeply_nested_import_of_a_type_i_suppose
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def some_method_with_a_really_long_name(very_long_parameter_so_yeah: str, another_long_parameter: int) -> (
|
||||
another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def func() -> (
|
||||
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(this_shouldn_t_get_a_trailing_comma_too)
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def func() -> ((also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
|
||||
this_shouldn_t_get_a_trailing_comma_too
|
||||
))
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Make sure inner one-element tuple won't explode
|
||||
some_module.some_function(
|
||||
argument1, (one_element_tuple,), argument4, argument5, argument6
|
||||
)
|
||||
|
||||
# Inner trailing comma causes outer to explode
|
||||
some_module.some_function(
|
||||
argument1, (one, two,), argument4, argument5, argument6
|
||||
)
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised return type
|
||||
int
|
||||
):
|
||||
...
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised return type
|
||||
# more
|
||||
int
|
||||
# another
|
||||
):
|
||||
...
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised new union return type
|
||||
int | str | bytes
|
||||
):
|
||||
...
|
||||
|
||||
def foo() -> (
|
||||
# comment inside plain tuple
|
||||
):
|
||||
pass
|
||||
|
||||
def foo(arg: (# comment with non-return annotation
|
||||
int
|
||||
# comment with non-return annotation
|
||||
)):
|
||||
pass
|
||||
|
||||
def foo(arg: (# comment with non-return annotation
|
||||
int | range | memoryview
|
||||
# comment with non-return annotation
|
||||
)):
|
||||
pass
|
||||
|
||||
def foo(arg: (# only before
|
||||
int
|
||||
)):
|
||||
pass
|
||||
|
||||
def foo(arg: (
|
||||
int
|
||||
# only after
|
||||
)):
|
||||
pass
|
||||
|
||||
variable: ( # annotation
|
||||
because
|
||||
# why not
|
||||
)
|
||||
|
||||
variable: (
|
||||
because
|
||||
# why not
|
||||
)
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -130,9 +130,7 @@
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised new union return type
|
||||
- int
|
||||
- | str
|
||||
- | bytes
|
||||
+ int | str | bytes
|
||||
): ...
|
||||
|
||||
|
||||
@@ -143,34 +141,31 @@
|
||||
|
||||
|
||||
def foo(
|
||||
- arg: ( # comment with non-return annotation
|
||||
- int
|
||||
- # comment with non-return annotation
|
||||
- ),
|
||||
+ # comment with non-return annotation
|
||||
+ # comment with non-return annotation
|
||||
+ arg: (int),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo(
|
||||
- arg: ( # comment with non-return annotation
|
||||
- int
|
||||
- | range
|
||||
- | memoryview
|
||||
- # comment with non-return annotation
|
||||
- ),
|
||||
+ # comment with non-return annotation
|
||||
+ # comment with non-return annotation
|
||||
+ arg: (int | range | memoryview),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
-def foo(arg: int): # only before
|
||||
+def foo(
|
||||
+ # only before
|
||||
+ arg: (int),
|
||||
+):
|
||||
pass
|
||||
|
||||
|
||||
def foo(
|
||||
- arg: (
|
||||
- int
|
||||
- # only after
|
||||
- ),
|
||||
+ # only after
|
||||
+ arg: (int),
|
||||
):
|
||||
pass
|
||||
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```python
|
||||
def f(
|
||||
a,
|
||||
):
|
||||
d = {
|
||||
"key": "value",
|
||||
}
|
||||
tup = (1,)
|
||||
|
||||
|
||||
def f2(
|
||||
a,
|
||||
b,
|
||||
):
|
||||
d = {
|
||||
"key": "value",
|
||||
"key2": "value2",
|
||||
}
|
||||
tup = (
|
||||
1,
|
||||
2,
|
||||
)
|
||||
|
||||
|
||||
def f(
|
||||
a: int = 1,
|
||||
):
|
||||
call(
|
||||
arg={
|
||||
"explode": "this",
|
||||
}
|
||||
)
|
||||
call2(
|
||||
arg=[1, 2, 3],
|
||||
)
|
||||
x = {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
}["a"]
|
||||
if (
|
||||
a
|
||||
== {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": 3,
|
||||
"d": 4,
|
||||
"e": 5,
|
||||
"f": 6,
|
||||
"g": 7,
|
||||
"h": 8,
|
||||
}["a"]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]
|
||||
):
|
||||
json = {
|
||||
"k": {
|
||||
"k2": {
|
||||
"k3": [
|
||||
1,
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# The type annotation shouldn't get a trailing comma since that would change its type.
|
||||
# Relevant bug report: https://github.com/psf/black/issues/2381.
|
||||
def some_function_with_a_really_long_name() -> (
|
||||
returning_a_deeply_nested_import_of_a_type_i_suppose
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def some_method_with_a_really_long_name(
|
||||
very_long_parameter_so_yeah: str, another_long_parameter: int
|
||||
) -> another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not:
|
||||
pass
|
||||
|
||||
|
||||
def func() -> (
|
||||
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
|
||||
this_shouldn_t_get_a_trailing_comma_too
|
||||
)
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def func() -> (
|
||||
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
|
||||
this_shouldn_t_get_a_trailing_comma_too
|
||||
)
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Make sure inner one-element tuple won't explode
|
||||
some_module.some_function(
|
||||
argument1, (one_element_tuple,), argument4, argument5, argument6
|
||||
)
|
||||
|
||||
# Inner trailing comma causes outer to explode
|
||||
some_module.some_function(
|
||||
argument1,
|
||||
(
|
||||
one,
|
||||
two,
|
||||
),
|
||||
argument4,
|
||||
argument5,
|
||||
argument6,
|
||||
)
|
||||
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised return type
|
||||
int
|
||||
): ...
|
||||
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised return type
|
||||
# more
|
||||
int
|
||||
# another
|
||||
): ...
|
||||
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised new union return type
|
||||
int | str | bytes
|
||||
): ...
|
||||
|
||||
|
||||
def foo() -> (
|
||||
# comment inside plain tuple
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo(
|
||||
# comment with non-return annotation
|
||||
# comment with non-return annotation
|
||||
arg: (int),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo(
|
||||
# comment with non-return annotation
|
||||
# comment with non-return annotation
|
||||
arg: (int | range | memoryview),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo(
|
||||
# only before
|
||||
arg: (int),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo(
|
||||
# only after
|
||||
arg: (int),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
variable: ( # annotation
|
||||
because
|
||||
# why not
|
||||
)
|
||||
|
||||
variable: (
|
||||
because
|
||||
# why not
|
||||
)
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```python
|
||||
def f(
|
||||
a,
|
||||
):
|
||||
d = {
|
||||
"key": "value",
|
||||
}
|
||||
tup = (1,)
|
||||
|
||||
|
||||
def f2(
|
||||
a,
|
||||
b,
|
||||
):
|
||||
d = {
|
||||
"key": "value",
|
||||
"key2": "value2",
|
||||
}
|
||||
tup = (
|
||||
1,
|
||||
2,
|
||||
)
|
||||
|
||||
|
||||
def f(
|
||||
a: int = 1,
|
||||
):
|
||||
call(
|
||||
arg={
|
||||
"explode": "this",
|
||||
}
|
||||
)
|
||||
call2(
|
||||
arg=[1, 2, 3],
|
||||
)
|
||||
x = {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
}["a"]
|
||||
if (
|
||||
a
|
||||
== {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": 3,
|
||||
"d": 4,
|
||||
"e": 5,
|
||||
"f": 6,
|
||||
"g": 7,
|
||||
"h": 8,
|
||||
}["a"]
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]
|
||||
):
|
||||
json = {
|
||||
"k": {
|
||||
"k2": {
|
||||
"k3": [
|
||||
1,
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# The type annotation shouldn't get a trailing comma since that would change its type.
|
||||
# Relevant bug report: https://github.com/psf/black/issues/2381.
|
||||
def some_function_with_a_really_long_name() -> (
|
||||
returning_a_deeply_nested_import_of_a_type_i_suppose
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def some_method_with_a_really_long_name(
|
||||
very_long_parameter_so_yeah: str, another_long_parameter: int
|
||||
) -> another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not:
|
||||
pass
|
||||
|
||||
|
||||
def func() -> (
|
||||
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
|
||||
this_shouldn_t_get_a_trailing_comma_too
|
||||
)
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def func() -> (
|
||||
also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(
|
||||
this_shouldn_t_get_a_trailing_comma_too
|
||||
)
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
# Make sure inner one-element tuple won't explode
|
||||
some_module.some_function(
|
||||
argument1, (one_element_tuple,), argument4, argument5, argument6
|
||||
)
|
||||
|
||||
# Inner trailing comma causes outer to explode
|
||||
some_module.some_function(
|
||||
argument1,
|
||||
(
|
||||
one,
|
||||
two,
|
||||
),
|
||||
argument4,
|
||||
argument5,
|
||||
argument6,
|
||||
)
|
||||
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised return type
|
||||
int
|
||||
): ...
|
||||
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised return type
|
||||
# more
|
||||
int
|
||||
# another
|
||||
): ...
|
||||
|
||||
|
||||
def foo() -> (
|
||||
# comment inside parenthesised new union return type
|
||||
int
|
||||
| str
|
||||
| bytes
|
||||
): ...
|
||||
|
||||
|
||||
def foo() -> (
|
||||
# comment inside plain tuple
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo(
|
||||
arg: ( # comment with non-return annotation
|
||||
int
|
||||
# comment with non-return annotation
|
||||
),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo(
|
||||
arg: ( # comment with non-return annotation
|
||||
int
|
||||
| range
|
||||
| memoryview
|
||||
# comment with non-return annotation
|
||||
),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo(arg: int): # only before
|
||||
pass
|
||||
|
||||
|
||||
def foo(
|
||||
arg: (
|
||||
int
|
||||
# only after
|
||||
),
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
variable: ( # annotation
|
||||
because
|
||||
# why not
|
||||
)
|
||||
|
||||
variable: (
|
||||
because
|
||||
# why not
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,185 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/pattern_matching_with_if_stmt.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```python
|
||||
match match:
|
||||
case "test" if case != "not very loooooooooooooog condition": # comment
|
||||
pass
|
||||
|
||||
match smth:
|
||||
case "test" if "any long condition" != "another long condition" and "this is a long condition":
|
||||
pass
|
||||
case test if "any long condition" != "another long condition" and "this is a looooong condition":
|
||||
pass
|
||||
case test if "any long condition" != "another long condition" and "this is a looooong condition": # some additional comments
|
||||
pass
|
||||
case test if (True): # some comment
|
||||
pass
|
||||
case test if (False
|
||||
): # some comment
|
||||
pass
|
||||
case test if (True # some comment
|
||||
):
|
||||
pass # some comment
|
||||
case cases if (True # some comment
|
||||
): # some other comment
|
||||
pass # some comment
|
||||
case match if (True # some comment
|
||||
):
|
||||
pass # some comment
|
||||
|
||||
# case black_test_patma_052 (originally in the pattern_matching_complex test case)
|
||||
match x:
|
||||
case [1, 0] if x := x[:0]:
|
||||
y = 1
|
||||
case [1, 0] if (x := x[:0]):
|
||||
y = 1
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -3,34 +3,36 @@
|
||||
pass
|
||||
|
||||
match smth:
|
||||
- case "test" if (
|
||||
- "any long condition" != "another long condition" and "this is a long condition"
|
||||
- ):
|
||||
+ case "test" if "any long condition" != "another long condition" and "this is a long condition":
|
||||
pass
|
||||
- case test if (
|
||||
- "any long condition" != "another long condition"
|
||||
- and "this is a looooong condition"
|
||||
- ):
|
||||
+ case (
|
||||
+ test
|
||||
+ ) if "any long condition" != "another long condition" and "this is a looooong condition":
|
||||
pass
|
||||
- case test if (
|
||||
- "any long condition" != "another long condition"
|
||||
- and "this is a looooong condition"
|
||||
- ): # some additional comments
|
||||
+ case (
|
||||
+ test
|
||||
+ ) if "any long condition" != "another long condition" and "this is a looooong condition": # some additional comments
|
||||
pass
|
||||
- case test if True: # some comment
|
||||
+ case test if (True): # some comment
|
||||
pass
|
||||
- case test if False: # some comment
|
||||
+ case test if (False): # some comment
|
||||
pass
|
||||
- case test if True: # some comment
|
||||
+ case test if (
|
||||
+ True # some comment
|
||||
+ ):
|
||||
pass # some comment
|
||||
- case cases if True: # some comment # some other comment
|
||||
+ case cases if (
|
||||
+ True # some comment
|
||||
+ ): # some other comment
|
||||
pass # some comment
|
||||
- case match if True: # some comment
|
||||
+ case match if (
|
||||
+ True # some comment
|
||||
+ ):
|
||||
pass # some comment
|
||||
|
||||
# case black_test_patma_052 (originally in the pattern_matching_complex test case)
|
||||
match x:
|
||||
case [1, 0] if x := x[:0]:
|
||||
y = 1
|
||||
- case [1, 0] if x := x[:0]:
|
||||
+ case [1, 0] if (x := x[:0]):
|
||||
y = 1
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```python
|
||||
match match:
|
||||
case "test" if case != "not very loooooooooooooog condition": # comment
|
||||
pass
|
||||
|
||||
match smth:
|
||||
case "test" if "any long condition" != "another long condition" and "this is a long condition":
|
||||
pass
|
||||
case (
|
||||
test
|
||||
) if "any long condition" != "another long condition" and "this is a looooong condition":
|
||||
pass
|
||||
case (
|
||||
test
|
||||
) if "any long condition" != "another long condition" and "this is a looooong condition": # some additional comments
|
||||
pass
|
||||
case test if (True): # some comment
|
||||
pass
|
||||
case test if (False): # some comment
|
||||
pass
|
||||
case test if (
|
||||
True # some comment
|
||||
):
|
||||
pass # some comment
|
||||
case cases if (
|
||||
True # some comment
|
||||
): # some other comment
|
||||
pass # some comment
|
||||
case match if (
|
||||
True # some comment
|
||||
):
|
||||
pass # some comment
|
||||
|
||||
# case black_test_patma_052 (originally in the pattern_matching_complex test case)
|
||||
match x:
|
||||
case [1, 0] if x := x[:0]:
|
||||
y = 1
|
||||
case [1, 0] if (x := x[:0]):
|
||||
y = 1
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```python
|
||||
match match:
|
||||
case "test" if case != "not very loooooooooooooog condition": # comment
|
||||
pass
|
||||
|
||||
match smth:
|
||||
case "test" if (
|
||||
"any long condition" != "another long condition" and "this is a long condition"
|
||||
):
|
||||
pass
|
||||
case test if (
|
||||
"any long condition" != "another long condition"
|
||||
and "this is a looooong condition"
|
||||
):
|
||||
pass
|
||||
case test if (
|
||||
"any long condition" != "another long condition"
|
||||
and "this is a looooong condition"
|
||||
): # some additional comments
|
||||
pass
|
||||
case test if True: # some comment
|
||||
pass
|
||||
case test if False: # some comment
|
||||
pass
|
||||
case test if True: # some comment
|
||||
pass # some comment
|
||||
case cases if True: # some comment # some other comment
|
||||
pass # some comment
|
||||
case match if True: # some comment
|
||||
pass # some comment
|
||||
|
||||
# case black_test_patma_052 (originally in the pattern_matching_complex test case)
|
||||
match x:
|
||||
case [1, 0] if x := x[:0]:
|
||||
y = 1
|
||||
case [1, 0] if x := x[:0]:
|
||||
y = 1
|
||||
```
|
||||
@@ -26,7 +26,7 @@ z: (Short
|
||||
z: (int) = 2.3
|
||||
z: ((int)) = foo()
|
||||
|
||||
# In case I go for not enforcing parentheses, this might get improved at the same time
|
||||
# In case I go for not enforcing parantheses, this might get improved at the same time
|
||||
x = (
|
||||
z
|
||||
== 9999999999999999999999999999999999999999
|
||||
@@ -165,7 +165,7 @@ z: Short | Short2 | Short3 | Short4 = 8
|
||||
z: int = 2.3
|
||||
z: int = foo()
|
||||
|
||||
# In case I go for not enforcing parentheses, this might get improved at the same time
|
||||
# In case I go for not enforcing parantheses, this might get improved at the same time
|
||||
x = (
|
||||
z
|
||||
== 9999999999999999999999999999999999999999
|
||||
@@ -269,7 +269,7 @@ z: Short | Short2 | Short3 | Short4 = 8
|
||||
z: int = 2.3
|
||||
z: int = foo()
|
||||
|
||||
# In case I go for not enforcing parentheses, this might get improved at the same time
|
||||
# In case I go for not enforcing parantheses, this might get improved at the same time
|
||||
x = (
|
||||
z
|
||||
== 9999999999999999999999999999999999999999
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/pep_701.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```python
|
||||
x = f"foo"
|
||||
x = f'foo'
|
||||
x = f"""foo"""
|
||||
x = f'''foo'''
|
||||
x = f"foo {{ bar {{ baz"
|
||||
x = f"foo {{ {2 + 2}bar {{ baz"
|
||||
x = f'foo {{ {2 + 2}bar {{ baz'
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
x = f'''foo {{ {2 + 2}bar {{ baz'''
|
||||
|
||||
# edge case: FSTRING_MIDDLE containing only whitespace should not be stripped
|
||||
x = f"{a} {b}"
|
||||
|
||||
x = f"foo {
|
||||
2 + 2
|
||||
} bar baz"
|
||||
|
||||
x = f"foo {{ {"a {2 + 2} b"}bar {{ baz"
|
||||
x = f"foo {{ {f'a {2 + 2} b'}bar {{ baz"
|
||||
x = f"foo {{ {f"a {2 + 2} b"}bar {{ baz"
|
||||
|
||||
x = f"foo {{ {f'a {f"a {2 + 2} b"} b'}bar {{ baz"
|
||||
x = f"foo {{ {f"a {f"a {2 + 2} b"} b"}bar {{ baz"
|
||||
|
||||
x = """foo {{ {2 + 2}bar
|
||||
baz"""
|
||||
|
||||
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
|
||||
x = f"""foo {{ {
|
||||
2 + 2
|
||||
}bar {{ baz"""
|
||||
|
||||
|
||||
x = f"""foo {{ {
|
||||
2 + 2
|
||||
}bar
|
||||
baz"""
|
||||
|
||||
x = f"""foo {{ a
|
||||
foo {2 + 2}bar {{ baz
|
||||
|
||||
x = f"foo {{ {
|
||||
2 + 2 # comment
|
||||
}bar"
|
||||
|
||||
{{ baz
|
||||
|
||||
}} buzz
|
||||
|
||||
{print("abc" + "def"
|
||||
)}
|
||||
abc"""
|
||||
|
||||
# edge case: end triple quotes at index zero
|
||||
f"""foo {2+2} bar
|
||||
"""
|
||||
|
||||
f' \' {f"'"} \' '
|
||||
f" \" {f'"'} \" "
|
||||
|
||||
x = f"a{2+2:=^72}b"
|
||||
x = f"a{2+2:x}b"
|
||||
|
||||
rf'foo'
|
||||
rf'{foo}'
|
||||
|
||||
f"{x:{y}d}"
|
||||
|
||||
x = f"a{2+2:=^{x}}b"
|
||||
x = f"a{2+2:=^{foo(x+y**2):something else}}b"
|
||||
x = f"a{2+2:=^{foo(x+y**2):something else}one more}b"
|
||||
f'{(abc:=10)}'
|
||||
|
||||
f"This is a really long string, but just make sure that you reflow fstrings {
|
||||
2+2:d
|
||||
}"
|
||||
f"This is a really long string, but just make sure that you reflow fstrings correctly {2+2:d}"
|
||||
|
||||
f"{2+2=}"
|
||||
f"{2+2 = }"
|
||||
f"{ 2 + 2 = }"
|
||||
|
||||
f"""foo {
|
||||
datetime.datetime.now():%Y
|
||||
%m
|
||||
%d
|
||||
}"""
|
||||
|
||||
f"{
|
||||
X
|
||||
!r
|
||||
}"
|
||||
|
||||
raise ValueError(
|
||||
"xxxxxxxxxxxIncorrect --line-ranges format, expect START-END, found"
|
||||
f" {lines_str!r}"
|
||||
)
|
||||
|
||||
f"`escape` only permitted in {{'html', 'latex', 'latex-math'}}, \
|
||||
got {escape}"
|
||||
|
||||
x = f'\N{GREEK CAPITAL LETTER DELTA} \N{SNOWMAN} {x}'
|
||||
fr'\{{\}}'
|
||||
|
||||
f"""
|
||||
WITH {f'''
|
||||
{1}_cte AS ()'''}
|
||||
"""
|
||||
|
||||
value: str = f'''foo
|
||||
'''
|
||||
|
||||
log(
|
||||
f"Received operation {server_operation.name} from "
|
||||
f"{self.writer._transport.get_extra_info('peername')}", # type: ignore[attr-defined]
|
||||
level=0,
|
||||
)
|
||||
|
||||
f"{1:{f'{2}'}}"
|
||||
f'{1:{f'{2}'}}'
|
||||
f'{1:{2}d}'
|
||||
|
||||
f'{{\\"kind\\":\\"ConfigMap\\",\\"metadata\\":{{\\"annotations\\":{{}},\\"name\\":\\"cluster-info\\",\\"namespace\\":\\"amazon-cloudwatch\\"}}}}'
|
||||
|
||||
f"""{'''
|
||||
'''}"""
|
||||
|
||||
f"{'\''}"
|
||||
f"{f'\''}"
|
||||
|
||||
f'{1}\{{'
|
||||
f'{2} foo \{{[\}}'
|
||||
f'\{3}'
|
||||
rf"\{"a"}"
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -119,7 +119,7 @@
|
||||
)
|
||||
|
||||
f"{1:{f'{2}'}}"
|
||||
-f"{1:{f'{2}'}}"
|
||||
+f'{1:{f'{2}'}}'
|
||||
f"{1:{2}d}"
|
||||
|
||||
f'{{\\"kind\\":\\"ConfigMap\\",\\"metadata\\":{{\\"annotations\\":{{}},\\"name\\":\\"cluster-info\\",\\"namespace\\":\\"amazon-cloudwatch\\"}}}}'
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```python
|
||||
x = f"foo"
|
||||
x = f"foo"
|
||||
x = f"""foo"""
|
||||
x = f"""foo"""
|
||||
x = f"foo {{ bar {{ baz"
|
||||
x = f"foo {{ {2 + 2}bar {{ baz"
|
||||
x = f"foo {{ {2 + 2}bar {{ baz"
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
|
||||
# edge case: FSTRING_MIDDLE containing only whitespace should not be stripped
|
||||
x = f"{a} {b}"
|
||||
|
||||
x = f"foo {
|
||||
2 + 2
|
||||
} bar baz"
|
||||
|
||||
x = f"foo {{ {"a {2 + 2} b"}bar {{ baz"
|
||||
x = f"foo {{ {f'a {2 + 2} b'}bar {{ baz"
|
||||
x = f"foo {{ {f"a {2 + 2} b"}bar {{ baz"
|
||||
|
||||
x = f"foo {{ {f'a {f"a {2 + 2} b"} b'}bar {{ baz"
|
||||
x = f"foo {{ {f"a {f"a {2 + 2} b"} b"}bar {{ baz"
|
||||
|
||||
x = """foo {{ {2 + 2}bar
|
||||
baz"""
|
||||
|
||||
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
|
||||
x = f"""foo {{ {
|
||||
2 + 2
|
||||
}bar {{ baz"""
|
||||
|
||||
|
||||
x = f"""foo {{ {
|
||||
2 + 2
|
||||
}bar
|
||||
baz"""
|
||||
|
||||
x = f"""foo {{ a
|
||||
foo {2 + 2}bar {{ baz
|
||||
|
||||
x = f"foo {{ {
|
||||
2 + 2 # comment
|
||||
}bar"
|
||||
|
||||
{{ baz
|
||||
|
||||
}} buzz
|
||||
|
||||
{print("abc" + "def"
|
||||
)}
|
||||
abc"""
|
||||
|
||||
# edge case: end triple quotes at index zero
|
||||
f"""foo {2+2} bar
|
||||
"""
|
||||
|
||||
f' \' {f"'"} \' '
|
||||
f" \" {f'"'} \" "
|
||||
|
||||
x = f"a{2+2:=^72}b"
|
||||
x = f"a{2+2:x}b"
|
||||
|
||||
rf"foo"
|
||||
rf"{foo}"
|
||||
|
||||
f"{x:{y}d}"
|
||||
|
||||
x = f"a{2+2:=^{x}}b"
|
||||
x = f"a{2+2:=^{foo(x+y**2):something else}}b"
|
||||
x = f"a{2+2:=^{foo(x+y**2):something else}one more}b"
|
||||
f"{(abc:=10)}"
|
||||
|
||||
f"This is a really long string, but just make sure that you reflow fstrings {
|
||||
2+2:d
|
||||
}"
|
||||
f"This is a really long string, but just make sure that you reflow fstrings correctly {2+2:d}"
|
||||
|
||||
f"{2+2=}"
|
||||
f"{2+2 = }"
|
||||
f"{ 2 + 2 = }"
|
||||
|
||||
f"""foo {
|
||||
datetime.datetime.now():%Y
|
||||
%m
|
||||
%d
|
||||
}"""
|
||||
|
||||
f"{
|
||||
X
|
||||
!r
|
||||
}"
|
||||
|
||||
raise ValueError(
|
||||
"xxxxxxxxxxxIncorrect --line-ranges format, expect START-END, found"
|
||||
f" {lines_str!r}"
|
||||
)
|
||||
|
||||
f"`escape` only permitted in {{'html', 'latex', 'latex-math'}}, \
|
||||
got {escape}"
|
||||
|
||||
x = f"\N{GREEK CAPITAL LETTER DELTA} \N{SNOWMAN} {x}"
|
||||
rf"\{{\}}"
|
||||
|
||||
f"""
|
||||
WITH {f'''
|
||||
{1}_cte AS ()'''}
|
||||
"""
|
||||
|
||||
value: str = f"""foo
|
||||
"""
|
||||
|
||||
log(
|
||||
f"Received operation {server_operation.name} from "
|
||||
f"{self.writer._transport.get_extra_info('peername')}", # type: ignore[attr-defined]
|
||||
level=0,
|
||||
)
|
||||
|
||||
f"{1:{f'{2}'}}"
|
||||
f'{1:{f'{2}'}}'
|
||||
f"{1:{2}d}"
|
||||
|
||||
f'{{\\"kind\\":\\"ConfigMap\\",\\"metadata\\":{{\\"annotations\\":{{}},\\"name\\":\\"cluster-info\\",\\"namespace\\":\\"amazon-cloudwatch\\"}}}}'
|
||||
|
||||
f"""{'''
|
||||
'''}"""
|
||||
|
||||
f"{'\''}"
|
||||
f"{f'\''}"
|
||||
|
||||
f"{1}\{{"
|
||||
f"{2} foo \{{[\}}"
|
||||
f"\{3}"
|
||||
rf"\{"a"}"
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```python
|
||||
x = f"foo"
|
||||
x = f"foo"
|
||||
x = f"""foo"""
|
||||
x = f"""foo"""
|
||||
x = f"foo {{ bar {{ baz"
|
||||
x = f"foo {{ {2 + 2}bar {{ baz"
|
||||
x = f"foo {{ {2 + 2}bar {{ baz"
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
|
||||
# edge case: FSTRING_MIDDLE containing only whitespace should not be stripped
|
||||
x = f"{a} {b}"
|
||||
|
||||
x = f"foo {
|
||||
2 + 2
|
||||
} bar baz"
|
||||
|
||||
x = f"foo {{ {"a {2 + 2} b"}bar {{ baz"
|
||||
x = f"foo {{ {f'a {2 + 2} b'}bar {{ baz"
|
||||
x = f"foo {{ {f"a {2 + 2} b"}bar {{ baz"
|
||||
|
||||
x = f"foo {{ {f'a {f"a {2 + 2} b"} b'}bar {{ baz"
|
||||
x = f"foo {{ {f"a {f"a {2 + 2} b"} b"}bar {{ baz"
|
||||
|
||||
x = """foo {{ {2 + 2}bar
|
||||
baz"""
|
||||
|
||||
|
||||
x = f"""foo {{ {2 + 2}bar {{ baz"""
|
||||
|
||||
x = f"""foo {{ {
|
||||
2 + 2
|
||||
}bar {{ baz"""
|
||||
|
||||
|
||||
x = f"""foo {{ {
|
||||
2 + 2
|
||||
}bar
|
||||
baz"""
|
||||
|
||||
x = f"""foo {{ a
|
||||
foo {2 + 2}bar {{ baz
|
||||
|
||||
x = f"foo {{ {
|
||||
2 + 2 # comment
|
||||
}bar"
|
||||
|
||||
{{ baz
|
||||
|
||||
}} buzz
|
||||
|
||||
{print("abc" + "def"
|
||||
)}
|
||||
abc"""
|
||||
|
||||
# edge case: end triple quotes at index zero
|
||||
f"""foo {2+2} bar
|
||||
"""
|
||||
|
||||
f' \' {f"'"} \' '
|
||||
f" \" {f'"'} \" "
|
||||
|
||||
x = f"a{2+2:=^72}b"
|
||||
x = f"a{2+2:x}b"
|
||||
|
||||
rf"foo"
|
||||
rf"{foo}"
|
||||
|
||||
f"{x:{y}d}"
|
||||
|
||||
x = f"a{2+2:=^{x}}b"
|
||||
x = f"a{2+2:=^{foo(x+y**2):something else}}b"
|
||||
x = f"a{2+2:=^{foo(x+y**2):something else}one more}b"
|
||||
f"{(abc:=10)}"
|
||||
|
||||
f"This is a really long string, but just make sure that you reflow fstrings {
|
||||
2+2:d
|
||||
}"
|
||||
f"This is a really long string, but just make sure that you reflow fstrings correctly {2+2:d}"
|
||||
|
||||
f"{2+2=}"
|
||||
f"{2+2 = }"
|
||||
f"{ 2 + 2 = }"
|
||||
|
||||
f"""foo {
|
||||
datetime.datetime.now():%Y
|
||||
%m
|
||||
%d
|
||||
}"""
|
||||
|
||||
f"{
|
||||
X
|
||||
!r
|
||||
}"
|
||||
|
||||
raise ValueError(
|
||||
"xxxxxxxxxxxIncorrect --line-ranges format, expect START-END, found"
|
||||
f" {lines_str!r}"
|
||||
)
|
||||
|
||||
f"`escape` only permitted in {{'html', 'latex', 'latex-math'}}, \
|
||||
got {escape}"
|
||||
|
||||
x = f"\N{GREEK CAPITAL LETTER DELTA} \N{SNOWMAN} {x}"
|
||||
rf"\{{\}}"
|
||||
|
||||
f"""
|
||||
WITH {f'''
|
||||
{1}_cte AS ()'''}
|
||||
"""
|
||||
|
||||
value: str = f"""foo
|
||||
"""
|
||||
|
||||
log(
|
||||
f"Received operation {server_operation.name} from "
|
||||
f"{self.writer._transport.get_extra_info('peername')}", # type: ignore[attr-defined]
|
||||
level=0,
|
||||
)
|
||||
|
||||
f"{1:{f'{2}'}}"
|
||||
f"{1:{f'{2}'}}"
|
||||
f"{1:{2}d}"
|
||||
|
||||
f'{{\\"kind\\":\\"ConfigMap\\",\\"metadata\\":{{\\"annotations\\":{{}},\\"name\\":\\"cluster-info\\",\\"namespace\\":\\"amazon-cloudwatch\\"}}}}'
|
||||
|
||||
f"""{'''
|
||||
'''}"""
|
||||
|
||||
f"{'\''}"
|
||||
f"{f'\''}"
|
||||
|
||||
f"{1}\{{"
|
||||
f"{2} foo \{{[\}}"
|
||||
f"\{3}"
|
||||
rf"\{"a"}"
|
||||
```
|
||||
@@ -180,6 +180,13 @@ this_will_also_become_one_line = ( # comment
|
||||
"b"
|
||||
"c"
|
||||
)
|
||||
|
||||
assert some_var == expected_result, """
|
||||
test
|
||||
"""
|
||||
assert some_var == expected_result, f"""
|
||||
expected: {expected_result}
|
||||
actual: {some_var}"""
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
@@ -368,7 +375,7 @@ this_will_also_become_one_line = ( # comment
|
||||
|
||||
this_will_stay_on_three_lines = (
|
||||
"a" # comment
|
||||
@@ -206,4 +247,6 @@
|
||||
@@ -206,7 +247,9 @@
|
||||
"c"
|
||||
)
|
||||
|
||||
@@ -376,6 +383,9 @@ this_will_also_become_one_line = ( # comment
|
||||
+this_will_also_become_one_line = ( # comment
|
||||
+ "a" "b" "c"
|
||||
+)
|
||||
|
||||
assert some_var == expected_result, """
|
||||
test
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
@@ -633,6 +643,13 @@ this_will_stay_on_three_lines = (
|
||||
this_will_also_become_one_line = ( # comment
|
||||
"a" "b" "c"
|
||||
)
|
||||
|
||||
assert some_var == expected_result, """
|
||||
test
|
||||
"""
|
||||
assert some_var == expected_result, f"""
|
||||
expected: {expected_result}
|
||||
actual: {some_var}"""
|
||||
```
|
||||
|
||||
## Black Output
|
||||
@@ -847,6 +864,11 @@ this_will_stay_on_three_lines = (
|
||||
)
|
||||
|
||||
this_will_also_become_one_line = "abc" # comment
|
||||
|
||||
assert some_var == expected_result, """
|
||||
test
|
||||
"""
|
||||
assert some_var == expected_result, f"""
|
||||
expected: {expected_result}
|
||||
actual: {some_var}"""
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/type_param_defaults.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```python
|
||||
type A[T=int] = float
|
||||
type B[*P=int] = float
|
||||
type C[*Ts=int] = float
|
||||
type D[*Ts=*int] = float
|
||||
type D[something_that_is_very_very_very_long=something_that_is_very_very_very_long] = float
|
||||
type D[*something_that_is_very_very_very_long=*something_that_is_very_very_very_long] = float
|
||||
type something_that_is_long[something_that_is_long=something_that_is_long] = something_that_is_long
|
||||
|
||||
def simple[T=something_that_is_long](short1: int, short2: str, short3: bytes) -> float:
|
||||
pass
|
||||
|
||||
def longer[something_that_is_long=something_that_is_long](something_that_is_long: something_that_is_long) -> something_that_is_long:
|
||||
pass
|
||||
|
||||
def trailing_comma1[T=int,](a: str):
|
||||
pass
|
||||
|
||||
def trailing_comma2[T=int](a: str,):
|
||||
pass
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -8,20 +8,20 @@
|
||||
type D[
|
||||
*something_that_is_very_very_very_long = *something_that_is_very_very_very_long
|
||||
] = float
|
||||
-type something_that_is_long[
|
||||
- something_that_is_long = something_that_is_long
|
||||
-] = something_that_is_long
|
||||
+type something_that_is_long[something_that_is_long = something_that_is_long] = (
|
||||
+ something_that_is_long
|
||||
+)
|
||||
|
||||
|
||||
-def simple[
|
||||
- T = something_that_is_long
|
||||
-](short1: int, short2: str, short3: bytes) -> float:
|
||||
+def simple[T = something_that_is_long](
|
||||
+ short1: int, short2: str, short3: bytes
|
||||
+) -> float:
|
||||
pass
|
||||
|
||||
|
||||
-def longer[
|
||||
- something_that_is_long = something_that_is_long
|
||||
-](something_that_is_long: something_that_is_long) -> something_that_is_long:
|
||||
+def longer[something_that_is_long = something_that_is_long](
|
||||
+ something_that_is_long: something_that_is_long,
|
||||
+) -> something_that_is_long:
|
||||
pass
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
pass
|
||||
|
||||
|
||||
-def trailing_comma2[
|
||||
- T = int
|
||||
-](a: str,):
|
||||
+def trailing_comma2[T = int](
|
||||
+ a: str,
|
||||
+):
|
||||
pass
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```python
|
||||
type A[T = int] = float
|
||||
type B[*P = int] = float
|
||||
type C[*Ts = int] = float
|
||||
type D[*Ts = *int] = float
|
||||
type D[
|
||||
something_that_is_very_very_very_long = something_that_is_very_very_very_long
|
||||
] = float
|
||||
type D[
|
||||
*something_that_is_very_very_very_long = *something_that_is_very_very_very_long
|
||||
] = float
|
||||
type something_that_is_long[something_that_is_long = something_that_is_long] = (
|
||||
something_that_is_long
|
||||
)
|
||||
|
||||
|
||||
def simple[T = something_that_is_long](
|
||||
short1: int, short2: str, short3: bytes
|
||||
) -> float:
|
||||
pass
|
||||
|
||||
|
||||
def longer[something_that_is_long = something_that_is_long](
|
||||
something_that_is_long: something_that_is_long,
|
||||
) -> something_that_is_long:
|
||||
pass
|
||||
|
||||
|
||||
def trailing_comma1[
|
||||
T = int,
|
||||
](a: str):
|
||||
pass
|
||||
|
||||
|
||||
def trailing_comma2[T = int](
|
||||
a: str,
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```python
|
||||
type A[T = int] = float
|
||||
type B[*P = int] = float
|
||||
type C[*Ts = int] = float
|
||||
type D[*Ts = *int] = float
|
||||
type D[
|
||||
something_that_is_very_very_very_long = something_that_is_very_very_very_long
|
||||
] = float
|
||||
type D[
|
||||
*something_that_is_very_very_very_long = *something_that_is_very_very_very_long
|
||||
] = float
|
||||
type something_that_is_long[
|
||||
something_that_is_long = something_that_is_long
|
||||
] = something_that_is_long
|
||||
|
||||
|
||||
def simple[
|
||||
T = something_that_is_long
|
||||
](short1: int, short2: str, short3: bytes) -> float:
|
||||
pass
|
||||
|
||||
|
||||
def longer[
|
||||
something_that_is_long = something_that_is_long
|
||||
](something_that_is_long: something_that_is_long) -> something_that_is_long:
|
||||
pass
|
||||
|
||||
|
||||
def trailing_comma1[
|
||||
T = int,
|
||||
](a: str):
|
||||
pass
|
||||
|
||||
|
||||
def trailing_comma2[
|
||||
T = int
|
||||
](a: str,):
|
||||
pass
|
||||
```
|
||||
@@ -153,7 +153,7 @@ extension for [coc.nvim](https://github.com/neoclide/coc.nvim).
|
||||
" Linter
|
||||
let g:ale_linters = { "python": ["ruff"] }
|
||||
" Formatter
|
||||
let g:ale_fixers = { "python": ["ruff-format"] }
|
||||
let g:ale_fixers = { "python": ["ruff_format"] }
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PyYAML==6.0.2
|
||||
ruff==0.6.4
|
||||
ruff==0.6.5
|
||||
mkdocs==1.6.1
|
||||
mkdocs-material @ git+ssh://git@github.com/astral-sh/mkdocs-material-insiders.git@38c0b8187325c3bab386b666daf3518ac036f2f4
|
||||
mkdocs-redirects==1.2.1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PyYAML==6.0.2
|
||||
ruff==0.6.4
|
||||
ruff==0.6.5
|
||||
mkdocs==1.6.1
|
||||
mkdocs-material==9.1.18
|
||||
mkdocs-redirects==1.2.1
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"rules": {
|
||||
// Disable some recommended rules that we don't want to enforce.
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off"
|
||||
"eqeqeq": ["error","always", { "null": "never"}]
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
|
||||
92
playground/api/package-lock.json
generated
92
playground/api/package-lock.json
generated
@@ -16,7 +16,7 @@
|
||||
"@cloudflare/workers-types": "^4.20230801.0",
|
||||
"miniflare": "^3.20230801.1",
|
||||
"typescript": "^5.1.6",
|
||||
"wrangler": "3.75.0"
|
||||
"wrangler": "3.78.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/kv-asset-handler": {
|
||||
@@ -33,9 +33,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workerd-darwin-64": {
|
||||
"version": "1.20240821.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240821.1.tgz",
|
||||
"integrity": "sha512-CDBpfZKrSy4YrIdqS84z67r3Tzal2pOhjCsIb63IuCnvVes59/ft1qhczBzk9EffeOE2iTCrA4YBT7Sbn7USew==",
|
||||
"version": "1.20240909.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240909.0.tgz",
|
||||
"integrity": "sha512-nJ8jm/6PR8DPzVb4QifNAfSdrFZXNblwIdOhLTU5FpSvFFocmzFX5WgzQagvtmcC9/ZAQyxuf7WynDNyBcoe0Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -50,9 +50,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workerd-darwin-arm64": {
|
||||
"version": "1.20240821.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240821.1.tgz",
|
||||
"integrity": "sha512-Q+9RedvNbPcEt/dKni1oN94OxbvuNAeJkgHmrLFTGF8zu21wzOhVkQeRNxcYxrMa9mfStc457NAg13OVCj2kHQ==",
|
||||
"version": "1.20240909.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240909.0.tgz",
|
||||
"integrity": "sha512-gJqKa811oSsoxy9xuoQn7bS0Hr1sY+o3EUORTcEnulG6Kz9NQ6nd8QNdp2Hrk2jmmSqwrNkn+a6PZkWzk6Q0Gw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -67,9 +67,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workerd-linux-64": {
|
||||
"version": "1.20240821.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240821.1.tgz",
|
||||
"integrity": "sha512-j6z3KsPtawrscoLuP985LbqFrmsJL6q1mvSXOXTqXGODAHIzGBipHARdOjms3UQqovzvqB2lQaQsZtLBwCZxtA==",
|
||||
"version": "1.20240909.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240909.0.tgz",
|
||||
"integrity": "sha512-sJrmtccfMg73sZljiBpe4R+lhF58TqzqhF2pQG8HRjyxkzkM1sjpZqfEFaIkNUDqd3/Ibji49fklhPCGXljKSg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -84,9 +84,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workerd-linux-arm64": {
|
||||
"version": "1.20240821.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240821.1.tgz",
|
||||
"integrity": "sha512-I9bHgZOxJQW0CV5gTdilyxzTG7ILzbTirehQWgfPx9X77E/7eIbR9sboOMgyeC69W4he0SKtpx0sYZuTJu4ERw==",
|
||||
"version": "1.20240909.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240909.0.tgz",
|
||||
"integrity": "sha512-dTbSdceyRXPOSER+18AwYRbPQG0e/Dwl2trmfMMCETkfJhNLv1fU3FFMJPjfILijKnhTZHSnHCx0+xwHdon2fg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -101,9 +101,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workerd-windows-64": {
|
||||
"version": "1.20240821.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240821.1.tgz",
|
||||
"integrity": "sha512-keC97QPArs6LWbPejQM7/Y8Jy8QqyaZow4/ZdsGo+QjlOLiZRDpAenfZx3CBUoWwEeFwQTl2FLO+8hV1SWFFYw==",
|
||||
"version": "1.20240909.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240909.0.tgz",
|
||||
"integrity": "sha512-/d4BT0kcWFa7Qc0K4K9+cwVQ1qyPNKiO42JZUijlDlco+TYTPkLO3qGEohmwbfMq+BieK7JTMSgjO81ZHpA0HQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -118,19 +118,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workers-shared": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workers-shared/-/workers-shared-0.4.1.tgz",
|
||||
"integrity": "sha512-nYh4r8JwOOjYIdH2zub++CmIKlkYFlpxI1nBHimoiHcytJXD/b7ldJ21TtfzUZMCgI78mxVlymMHA/ReaOxKlA==",
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workers-shared/-/workers-shared-0.5.3.tgz",
|
||||
"integrity": "sha512-Yk5Im7zsyKbzd7qi+DrL7ZJR9+bdZwq9BqZWS4muDIWA8MCUeSLsUC+C9u+jdwfPSi5It2AcQG4f0iwZr6jkkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"mime": "^3.0.0",
|
||||
"zod": "^3.22.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workers-types": {
|
||||
"version": "4.20240903.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240903.0.tgz",
|
||||
"integrity": "sha512-a4mqgtVsPWg3JNNlQdLRE0Z6/mHr/uXa1ANDw6Zd7in438UCbeb+j7Z954Sf93G24jExpAn9VZ8kUUml0RwZbQ==",
|
||||
"version": "4.20240909.0",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240909.0.tgz",
|
||||
"integrity": "sha512-4knwtX6efxIsIxawdmPyynU9+S8A78wntU8eUIEldStWP4gNgxGkeWcfCMXulTx8oxr3DU4aevHyld9HGV8VKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0"
|
||||
},
|
||||
@@ -1105,9 +1109,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/miniflare": {
|
||||
"version": "3.20240821.1",
|
||||
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240821.1.tgz",
|
||||
"integrity": "sha512-81qdiryDG7VXzZuoa0EwhkaIYYrn7+StRIrd/2i7SPqPUNICUBjbhFFKqTnvE1+fqIPPB6l8ShKFaFvmnZOASg==",
|
||||
"version": "3.20240909.1",
|
||||
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240909.1.tgz",
|
||||
"integrity": "sha512-tdzJFApHmqFYlpjfpqBDnsE6dHUDLHejBrNgXftLfTf/ni5NySgXKnuntCCMdRtnTpjUKmkHiusGrBCf9b1rnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1119,7 +1123,7 @@
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"stoppable": "^1.1.0",
|
||||
"undici": "^5.28.4",
|
||||
"workerd": "1.20240821.1",
|
||||
"workerd": "1.20240909.0",
|
||||
"ws": "^8.17.1",
|
||||
"youch": "^3.2.2",
|
||||
"zod": "^3.22.3"
|
||||
@@ -1484,9 +1488,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.5.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -1570,9 +1574,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/workerd": {
|
||||
"version": "1.20240821.1",
|
||||
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240821.1.tgz",
|
||||
"integrity": "sha512-y4phjCnEG96u8ZkgkkHB+gSw0i6uMNo23rBmixylWpjxDklB+LWD8dztasvsu7xGaZbLoTxQESdEw956F7VJDA==",
|
||||
"version": "1.20240909.0",
|
||||
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240909.0.tgz",
|
||||
"integrity": "sha512-NwuYh/Fgr/MK0H+Ht687sHl/f8tumwT5CWzYR0MZMHri8m3CIYu2IaY4tBFWoKE/tOU1Z5XjEXECa9zXY4+lwg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
@@ -1583,29 +1587,29 @@
|
||||
"node": ">=16"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@cloudflare/workerd-darwin-64": "1.20240821.1",
|
||||
"@cloudflare/workerd-darwin-arm64": "1.20240821.1",
|
||||
"@cloudflare/workerd-linux-64": "1.20240821.1",
|
||||
"@cloudflare/workerd-linux-arm64": "1.20240821.1",
|
||||
"@cloudflare/workerd-windows-64": "1.20240821.1"
|
||||
"@cloudflare/workerd-darwin-64": "1.20240909.0",
|
||||
"@cloudflare/workerd-darwin-arm64": "1.20240909.0",
|
||||
"@cloudflare/workerd-linux-64": "1.20240909.0",
|
||||
"@cloudflare/workerd-linux-arm64": "1.20240909.0",
|
||||
"@cloudflare/workerd-windows-64": "1.20240909.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrangler": {
|
||||
"version": "3.75.0",
|
||||
"resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.75.0.tgz",
|
||||
"integrity": "sha512-CitNuNj0O1z6qbonUXmpUbxeWpU3nx28Kc4ZT33tMdeooQssb063Ie7+ZCdfS3kPhRHSwGdtOV22xFYytHON8w==",
|
||||
"version": "3.78.2",
|
||||
"resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.78.2.tgz",
|
||||
"integrity": "sha512-PL7GchswGrNm2OvdSw5yG3ZAqNjpaQIO++p8E1TaCi63DSyssKFYeYqTvfFshsQPP2u1dox5JFXtLc6IE/m1xw==",
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@cloudflare/kv-asset-handler": "0.3.4",
|
||||
"@cloudflare/workers-shared": "0.4.1",
|
||||
"@cloudflare/workers-shared": "0.5.3",
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||
"blake3-wasm": "^2.1.5",
|
||||
"chokidar": "^3.5.3",
|
||||
"date-fns": "^3.6.0",
|
||||
"esbuild": "0.17.19",
|
||||
"miniflare": "3.20240821.1",
|
||||
"miniflare": "3.20240909.1",
|
||||
"nanoid": "^3.3.3",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"resolve": "^1.22.8",
|
||||
@@ -1613,7 +1617,7 @@
|
||||
"selfsigned": "^2.0.1",
|
||||
"source-map": "^0.6.1",
|
||||
"unenv": "npm:unenv-nightly@2.0.0-1724863496.70db6f1",
|
||||
"workerd": "1.20240821.1",
|
||||
"workerd": "1.20240909.0",
|
||||
"xxhash-wasm": "^1.0.1"
|
||||
},
|
||||
"bin": {
|
||||
@@ -1627,7 +1631,7 @@
|
||||
"fsevents": "~2.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20240821.1"
|
||||
"@cloudflare/workers-types": "^4.20240909.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@cloudflare/workers-types": {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"@cloudflare/workers-types": "^4.20230801.0",
|
||||
"miniflare": "^3.20230801.1",
|
||||
"typescript": "^5.1.6",
|
||||
"wrangler": "3.75.0"
|
||||
"wrangler": "3.78.2"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
137
playground/package-lock.json
generated
137
playground/package-lock.json
generated
@@ -1146,17 +1146,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.4.0.tgz",
|
||||
"integrity": "sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==",
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.5.0.tgz",
|
||||
"integrity": "sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.4.0",
|
||||
"@typescript-eslint/type-utils": "8.4.0",
|
||||
"@typescript-eslint/utils": "8.4.0",
|
||||
"@typescript-eslint/visitor-keys": "8.4.0",
|
||||
"@typescript-eslint/scope-manager": "8.5.0",
|
||||
"@typescript-eslint/type-utils": "8.5.0",
|
||||
"@typescript-eslint/utils": "8.5.0",
|
||||
"@typescript-eslint/visitor-keys": "8.5.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.3.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
@@ -1180,16 +1180,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.4.0.tgz",
|
||||
"integrity": "sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA==",
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.5.0.tgz",
|
||||
"integrity": "sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.4.0",
|
||||
"@typescript-eslint/types": "8.4.0",
|
||||
"@typescript-eslint/typescript-estree": "8.4.0",
|
||||
"@typescript-eslint/visitor-keys": "8.4.0",
|
||||
"@typescript-eslint/scope-manager": "8.5.0",
|
||||
"@typescript-eslint/types": "8.5.0",
|
||||
"@typescript-eslint/typescript-estree": "8.5.0",
|
||||
"@typescript-eslint/visitor-keys": "8.5.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -1209,14 +1209,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.4.0.tgz",
|
||||
"integrity": "sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==",
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz",
|
||||
"integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.4.0",
|
||||
"@typescript-eslint/visitor-keys": "8.4.0"
|
||||
"@typescript-eslint/types": "8.5.0",
|
||||
"@typescript-eslint/visitor-keys": "8.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1227,14 +1227,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.4.0.tgz",
|
||||
"integrity": "sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==",
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.5.0.tgz",
|
||||
"integrity": "sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.4.0",
|
||||
"@typescript-eslint/utils": "8.4.0",
|
||||
"@typescript-eslint/typescript-estree": "8.5.0",
|
||||
"@typescript-eslint/utils": "8.5.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
},
|
||||
@@ -1252,9 +1252,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz",
|
||||
"integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==",
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz",
|
||||
"integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1266,14 +1266,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.4.0.tgz",
|
||||
"integrity": "sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==",
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz",
|
||||
"integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.4.0",
|
||||
"@typescript-eslint/visitor-keys": "8.4.0",
|
||||
"@typescript-eslint/types": "8.5.0",
|
||||
"@typescript-eslint/visitor-keys": "8.5.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@@ -1321,16 +1321,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.4.0.tgz",
|
||||
"integrity": "sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==",
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz",
|
||||
"integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@typescript-eslint/scope-manager": "8.4.0",
|
||||
"@typescript-eslint/types": "8.4.0",
|
||||
"@typescript-eslint/typescript-estree": "8.4.0"
|
||||
"@typescript-eslint/scope-manager": "8.5.0",
|
||||
"@typescript-eslint/types": "8.5.0",
|
||||
"@typescript-eslint/typescript-estree": "8.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -1344,13 +1344,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.4.0.tgz",
|
||||
"integrity": "sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==",
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz",
|
||||
"integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.4.0",
|
||||
"@typescript-eslint/types": "8.5.0",
|
||||
"eslint-visitor-keys": "^3.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2487,9 +2487,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react": {
|
||||
"version": "7.35.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.2.tgz",
|
||||
"integrity": "sha512-Rbj2R9zwP2GYNcIak4xoAMV57hrBh3hTaR0k7hVjwCQgryE/pw5px4b13EYjduOI0hfXyZhwBxaGpOTbWSGzKQ==",
|
||||
"version": "7.36.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.36.1.tgz",
|
||||
"integrity": "sha512-/qwbqNXZoq+VP30s1d4Nc1C5GTxjJQjk4Jzs4Wq2qzxFM7dSmuG2UkIjg2USMLh3A/aVcUNrK7v0J5U1XEGGwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3983,9 +3983,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
|
||||
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
||||
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -4029,9 +4029,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.45",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz",
|
||||
"integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==",
|
||||
"version": "8.4.47",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4050,8 +4050,8 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.1",
|
||||
"source-map-js": "^1.2.0"
|
||||
"picocolors": "^1.1.0",
|
||||
"source-map-js": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
@@ -4255,9 +4255,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/react-resizable-panels": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.2.tgz",
|
||||
"integrity": "sha512-Ku2Bo7JvE8RpHhl4X1uhkdeT9auPBoxAOlGTqomDUUrBAX2mVGuHYZTcWvlnJSgx0QyHIxHECgGB5XVPUbUOkQ==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.3.tgz",
|
||||
"integrity": "sha512-Zz0sCro6aUubL+hYh67eTnn5vxAu+HUZ7+IXvGjsBCBaudDEpIyZyDGE3vcgKi2w6IN3rYH+WXO+MwpgMSOpaQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.14.0 || ^17.0.0 || ^18.0.0",
|
||||
@@ -4574,10 +4574,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -4773,9 +4774,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.10",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.10.tgz",
|
||||
"integrity": "sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==",
|
||||
"version": "3.4.11",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.11.tgz",
|
||||
"integrity": "sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4977,9 +4978,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.5.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -5052,9 +5053,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz",
|
||||
"integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==",
|
||||
"version": "5.4.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.5.tgz",
|
||||
"integrity": "sha512-pXqR0qtb2bTwLkev4SE3r4abCNioP3GkjvIDLlzziPpXtHgiJIjuKl+1GN6ESOT3wMjG3JTeARopj2SwYaHTOA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
92
playground/src/Editor/Diagnostics.tsx
Normal file
92
playground/src/Editor/Diagnostics.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { Diagnostic } from "../pkg";
|
||||
import classNames from "classnames";
|
||||
import { Theme } from "./theme";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface Props {
|
||||
diagnostics: Diagnostic[];
|
||||
theme: Theme;
|
||||
onGoTo(line: number, column: number): void;
|
||||
}
|
||||
|
||||
export default function Diagnostics({
|
||||
diagnostics: unsorted,
|
||||
theme,
|
||||
onGoTo,
|
||||
}: Props) {
|
||||
const diagnostics = useMemo(() => {
|
||||
const sorted = [...unsorted];
|
||||
sorted.sort((a, b) => {
|
||||
if (a.location.row === b.location.row) {
|
||||
return a.location.column - b.location.column;
|
||||
}
|
||||
|
||||
return a.location.row - b.location.row;
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}, [unsorted]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"flex flex-grow flex-col overflow-hidden",
|
||||
theme === "dark" ? "text-white" : null,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
"border-b border-gray-200 px-2 py-1",
|
||||
theme === "dark" ? "border-rock" : null,
|
||||
)}
|
||||
>
|
||||
Diagnostics ({diagnostics.length})
|
||||
</div>
|
||||
|
||||
<div className="flex flex-grow p-2 overflow-hidden">
|
||||
<Items diagnostics={diagnostics} onGoTo={onGoTo} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Items({
|
||||
diagnostics,
|
||||
onGoTo,
|
||||
}: {
|
||||
diagnostics: Array<Diagnostic>;
|
||||
onGoTo(line: number, column: number): void;
|
||||
}) {
|
||||
if (diagnostics.length === 0) {
|
||||
return (
|
||||
<div className={"flex flex-auto flex-col justify-center items-center"}>
|
||||
Everything is looking good!
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="space-y-0.5 flex-grow overflow-y-scroll">
|
||||
{diagnostics.map((diagnostic) => {
|
||||
return (
|
||||
<li
|
||||
key={`${diagnostic.location.row}:${diagnostic.location.column}-${diagnostic.code}`}
|
||||
>
|
||||
<button
|
||||
onClick={() =>
|
||||
onGoTo(diagnostic.location.row, diagnostic.location.column)
|
||||
}
|
||||
className="w-full text-start"
|
||||
>
|
||||
{diagnostic.message}{" "}
|
||||
<span className="text-gray-500">
|
||||
{diagnostic.code != null && `(${diagnostic.code})`} [Ln{" "}
|
||||
{diagnostic.location.row}, Col {diagnostic.location.column}]
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
import { useDeferredValue, useMemo, useState } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useDeferredValue,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Panel, PanelGroup } from "react-resizable-panels";
|
||||
import { Diagnostic, Workspace } from "../pkg";
|
||||
import { ErrorMessage } from "./ErrorMessage";
|
||||
import PrimarySideBar from "./PrimarySideBar";
|
||||
import { HorizontalResizeHandle } from "./ResizeHandle";
|
||||
import { HorizontalResizeHandle, VerticalResizeHandle } from "./ResizeHandle";
|
||||
import SecondaryPanel, {
|
||||
SecondaryPanelResult,
|
||||
SecondaryTool,
|
||||
@@ -12,6 +18,9 @@ import SecondarySideBar from "./SecondarySideBar";
|
||||
import SettingsEditor from "./SettingsEditor";
|
||||
import SourceEditor from "./SourceEditor";
|
||||
import { Theme } from "./theme";
|
||||
import Diagnostics from "./Diagnostics";
|
||||
import { editor } from "monaco-editor";
|
||||
import IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
|
||||
|
||||
type Tab = "Source" | "Settings";
|
||||
|
||||
@@ -40,6 +49,7 @@ export default function Editor({
|
||||
onSourceChanged,
|
||||
onSettingsChanged,
|
||||
}: Props) {
|
||||
const editorRef = useRef<IStandaloneCodeEditor | null>(null);
|
||||
const [tab, setTab] = useState<Tab>("Source");
|
||||
const [secondaryTool, setSecondaryTool] = useState<SecondaryTool | null>(
|
||||
() => {
|
||||
@@ -53,6 +63,7 @@ export default function Editor({
|
||||
}
|
||||
},
|
||||
);
|
||||
const [selection, setSelection] = useState<number | null>(null);
|
||||
|
||||
// Ideally this would be retrieved right from the URL... but routing without a proper
|
||||
// router is hard (there's no location changed event) and pulling in a router
|
||||
@@ -75,6 +86,83 @@ export default function Editor({
|
||||
setSecondaryTool(tool);
|
||||
};
|
||||
|
||||
const handleGoTo = useCallback((line: number, column: number) => {
|
||||
const editor = editorRef.current;
|
||||
|
||||
if (editor == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const range = {
|
||||
startLineNumber: line,
|
||||
startColumn: column,
|
||||
endLineNumber: line,
|
||||
endColumn: column,
|
||||
};
|
||||
editor.revealRange(range);
|
||||
editor.setSelection(range);
|
||||
}, []);
|
||||
|
||||
const handleSourceEditorMount = useCallback(
|
||||
(editor: IStandaloneCodeEditor) => {
|
||||
editorRef.current = editor;
|
||||
|
||||
editor.addAction({
|
||||
contextMenuGroupId: "navigation",
|
||||
contextMenuOrder: 0,
|
||||
id: "reveal-node",
|
||||
label: "Reveal node",
|
||||
precondition: "editorTextFocus",
|
||||
|
||||
run(editor: editor.ICodeEditor): void | Promise<void> {
|
||||
const position = editor.getPosition();
|
||||
if (position == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offset = editor.getModel()!.getOffsetAt(position);
|
||||
|
||||
setSelection(
|
||||
charOffsetToByteOffset(editor.getModel()!.getValue(), offset),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleSelectByteRange = useCallback(
|
||||
(startByteOffset: number, endByteOffset: number) => {
|
||||
const model = editorRef.current?.getModel();
|
||||
|
||||
if (model == null || editorRef.current == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startCharacterOffset = byteOffsetToCharOffset(
|
||||
source.pythonSource,
|
||||
startByteOffset,
|
||||
);
|
||||
const endCharacterOffset = byteOffsetToCharOffset(
|
||||
source.pythonSource,
|
||||
endByteOffset,
|
||||
);
|
||||
|
||||
const start = model.getPositionAt(startCharacterOffset);
|
||||
const end = model.getPositionAt(endCharacterOffset);
|
||||
|
||||
const range = {
|
||||
startLineNumber: start.lineNumber,
|
||||
startColumn: start.column,
|
||||
endLineNumber: end.lineNumber,
|
||||
endColumn: end.column,
|
||||
};
|
||||
editorRef.current?.revealRange(range);
|
||||
editorRef.current?.setSelection(range);
|
||||
},
|
||||
[source.pythonSource],
|
||||
);
|
||||
|
||||
const deferredSource = useDeferredValue(source);
|
||||
|
||||
const checkResult: CheckResult = useMemo(() => {
|
||||
@@ -149,20 +237,43 @@ export default function Editor({
|
||||
<>
|
||||
<PanelGroup direction="horizontal" autoSaveId="main">
|
||||
<PrimarySideBar onSelectTool={(tool) => setTab(tool)} selected={tab} />
|
||||
<Panel id="main" order={0} className="my-2" minSize={10}>
|
||||
<SourceEditor
|
||||
visible={tab === "Source"}
|
||||
source={source.pythonSource}
|
||||
theme={theme}
|
||||
diagnostics={checkResult.diagnostics}
|
||||
onChange={onSourceChanged}
|
||||
/>
|
||||
<SettingsEditor
|
||||
visible={tab === "Settings"}
|
||||
source={source.settingsSource}
|
||||
theme={theme}
|
||||
onChange={onSettingsChanged}
|
||||
/>
|
||||
|
||||
<Panel id="main" order={0} minSize={10}>
|
||||
<PanelGroup id="main" direction="vertical">
|
||||
<Panel minSize={10} className="my-2" order={0}>
|
||||
<SourceEditor
|
||||
visible={tab === "Source"}
|
||||
source={source.pythonSource}
|
||||
theme={theme}
|
||||
diagnostics={checkResult.diagnostics}
|
||||
onChange={onSourceChanged}
|
||||
onMount={handleSourceEditorMount}
|
||||
/>
|
||||
<SettingsEditor
|
||||
visible={tab === "Settings"}
|
||||
source={source.settingsSource}
|
||||
theme={theme}
|
||||
onChange={onSettingsChanged}
|
||||
/>
|
||||
</Panel>
|
||||
{tab === "Source" && (
|
||||
<>
|
||||
<VerticalResizeHandle />
|
||||
<Panel
|
||||
id="diagnostics"
|
||||
minSize={3}
|
||||
order={1}
|
||||
className="my-2 flex flex-grow"
|
||||
>
|
||||
<Diagnostics
|
||||
diagnostics={checkResult.diagnostics}
|
||||
onGoTo={handleGoTo}
|
||||
theme={theme}
|
||||
/>
|
||||
</Panel>
|
||||
</>
|
||||
)}
|
||||
</PanelGroup>
|
||||
</Panel>
|
||||
{secondaryTool != null && (
|
||||
<>
|
||||
@@ -177,6 +288,8 @@ export default function Editor({
|
||||
theme={theme}
|
||||
tool={secondaryTool}
|
||||
result={checkResult.secondary}
|
||||
selectionOffset={selection}
|
||||
onSourceByteRangeClicked={handleSelectByteRange}
|
||||
/>
|
||||
</Panel>
|
||||
</>
|
||||
@@ -210,3 +323,25 @@ function parseSecondaryTool(tool: string): SecondaryTool | null {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function byteOffsetToCharOffset(content: string, byteOffset: number): number {
|
||||
// Create a Uint8Array from the UTF-8 string
|
||||
const encoder = new TextEncoder();
|
||||
const utf8Bytes = encoder.encode(content);
|
||||
|
||||
// Slice the byte array up to the byteOffset
|
||||
const slicedBytes = utf8Bytes.slice(0, byteOffset);
|
||||
|
||||
// Decode the sliced bytes to get a substring
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
const decodedString = decoder.decode(slicedBytes);
|
||||
return decodedString.length;
|
||||
}
|
||||
|
||||
function charOffsetToByteOffset(content: string, charOffset: number): number {
|
||||
// Create a Uint8Array from the UTF-8 string
|
||||
const encoder = new TextEncoder();
|
||||
const utf8Bytes = encoder.encode(content.substring(0, charOffset));
|
||||
|
||||
return utf8Bytes.length;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function PrimarySideBar({
|
||||
title="Source"
|
||||
position={"left"}
|
||||
onClick={() => onSelectTool("Source")}
|
||||
selected={selected == "Source"}
|
||||
selected={selected === "Source"}
|
||||
>
|
||||
<FileIcon />
|
||||
</SideBarEntry>
|
||||
@@ -27,7 +27,7 @@ export default function PrimarySideBar({
|
||||
title="Settings"
|
||||
position={"left"}
|
||||
onClick={() => onSelectTool("Settings")}
|
||||
selected={selected == "Settings"}
|
||||
selected={selected === "Settings"}
|
||||
>
|
||||
<SettingsIcon />
|
||||
</SideBarEntry>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import MonacoEditor from "@monaco-editor/react";
|
||||
import { Theme } from "./theme";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { editor, Range } from "monaco-editor";
|
||||
import IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
|
||||
import MonacoEditor from "@monaco-editor/react";
|
||||
|
||||
export enum SecondaryTool {
|
||||
"Format" = "Format",
|
||||
@@ -18,17 +21,27 @@ export type SecondaryPanelProps = {
|
||||
tool: SecondaryTool;
|
||||
result: SecondaryPanelResult;
|
||||
theme: Theme;
|
||||
selectionOffset: number | null;
|
||||
onSourceByteRangeClicked(start: number, end: number): void;
|
||||
};
|
||||
|
||||
export default function SecondaryPanel({
|
||||
tool,
|
||||
result,
|
||||
theme,
|
||||
selectionOffset,
|
||||
onSourceByteRangeClicked,
|
||||
}: SecondaryPanelProps) {
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex-grow">
|
||||
<Content tool={tool} result={result} theme={theme} />
|
||||
<Content
|
||||
tool={tool}
|
||||
result={result}
|
||||
theme={theme}
|
||||
selectionOffset={selectionOffset}
|
||||
onSourceByteRangeClicked={onSourceByteRangeClicked}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -38,11 +51,135 @@ function Content({
|
||||
tool,
|
||||
result,
|
||||
theme,
|
||||
selectionOffset,
|
||||
onSourceByteRangeClicked,
|
||||
}: {
|
||||
tool: SecondaryTool;
|
||||
result: SecondaryPanelResult;
|
||||
theme: Theme;
|
||||
selectionOffset: number | null;
|
||||
onSourceByteRangeClicked(start: number, end: number): void;
|
||||
}) {
|
||||
const [editor, setEditor] = useState<IStandaloneCodeEditor | null>(null);
|
||||
const [prevSelection, setPrevSelection] = useState<number | null>(null);
|
||||
const [ranges, setRanges] = useState<
|
||||
Array<{ byteRange: { start: number; end: number }; textRange: Range }>
|
||||
>([]);
|
||||
|
||||
if (
|
||||
editor != null &&
|
||||
selectionOffset != null &&
|
||||
selectionOffset !== prevSelection
|
||||
) {
|
||||
const range = ranges.findLast(
|
||||
(range) =>
|
||||
range.byteRange.start <= selectionOffset &&
|
||||
range.byteRange.end >= selectionOffset,
|
||||
);
|
||||
|
||||
if (range != null) {
|
||||
editor.revealRange(range.textRange);
|
||||
editor.setSelection(range.textRange);
|
||||
}
|
||||
setPrevSelection(selectionOffset);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const model = editor?.getModel();
|
||||
if (editor == null || model == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = editor.onMouseDown((event) => {
|
||||
if (event.target.range == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const range = model
|
||||
.getDecorationsInRange(
|
||||
event.target.range,
|
||||
undefined,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.map((decoration) => {
|
||||
const decorationRange = decoration.range;
|
||||
return ranges.find((range) =>
|
||||
Range.equalsRange(range.textRange, decorationRange),
|
||||
);
|
||||
})
|
||||
.find((range) => range != null);
|
||||
|
||||
if (range == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
onSourceByteRangeClicked(range.byteRange.start, range.byteRange.end);
|
||||
});
|
||||
|
||||
return () => handler.dispose();
|
||||
}, [editor, onSourceByteRangeClicked, ranges]);
|
||||
|
||||
const handleDidMount = useCallback((editor: IStandaloneCodeEditor) => {
|
||||
setEditor(editor);
|
||||
|
||||
const model = editor.getModel();
|
||||
const collection = editor.createDecorationsCollection([]);
|
||||
|
||||
function updateRanges() {
|
||||
if (model == null) {
|
||||
setRanges([]);
|
||||
collection.set([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const matches = model.findMatches(
|
||||
String.raw`(\d+)\.\.(\d+)`,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
",",
|
||||
true,
|
||||
);
|
||||
|
||||
const ranges = matches
|
||||
.map((match) => {
|
||||
const startByteOffset = parseInt(match.matches![1] ?? "", 10);
|
||||
const endByteOffset = parseInt(match.matches![2] ?? "", 10);
|
||||
|
||||
if (Number.isNaN(startByteOffset) || Number.isNaN(endByteOffset)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
byteRange: { start: startByteOffset, end: endByteOffset },
|
||||
textRange: match.range,
|
||||
};
|
||||
})
|
||||
.filter((range) => range != null);
|
||||
|
||||
setRanges(ranges);
|
||||
|
||||
const decorations = ranges.map((range) => {
|
||||
return {
|
||||
range: range.textRange,
|
||||
options: {
|
||||
inlineClassName:
|
||||
"underline decoration-slate-600 decoration-1 cursor-pointer",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
collection.set(decorations);
|
||||
}
|
||||
|
||||
updateRanges();
|
||||
const handler = editor.onDidChangeModelContent(updateRanges);
|
||||
|
||||
return () => handler.dispose();
|
||||
}, []);
|
||||
|
||||
if (result == null) {
|
||||
return "";
|
||||
} else {
|
||||
@@ -81,6 +218,7 @@ function Content({
|
||||
scrollBeyondLastLine: false,
|
||||
contextmenu: false,
|
||||
}}
|
||||
onMount={handleDidMount}
|
||||
language={language}
|
||||
value={result.content}
|
||||
theme={theme === "light" ? "Ayu-Light" : "Ayu-Dark"}
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function SettingsEditor({
|
||||
await navigator.clipboard.writeText(tomlSettings);
|
||||
},
|
||||
});
|
||||
editor.onDidPaste((event) => {
|
||||
const didPaste = editor.onDidPaste((event) => {
|
||||
const model = editor.getModel();
|
||||
|
||||
if (model == null) {
|
||||
@@ -97,6 +97,8 @@ export default function SettingsEditor({
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => didPaste.dispose();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@@ -123,7 +125,7 @@ function stripToolRuff(settings: object) {
|
||||
const { tool, ...nonToolSettings } = settings as any;
|
||||
|
||||
// Flatten out `tool.ruff.x` to just `x`
|
||||
if (typeof tool == "object" && !Array.isArray(tool)) {
|
||||
if (typeof tool === "object" && !Array.isArray(tool)) {
|
||||
if (tool.ruff != null) {
|
||||
return { ...nonToolSettings, ...tool.ruff };
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useCallback, useEffect, useRef } from "react";
|
||||
import { Diagnostic } from "../pkg";
|
||||
import { Theme } from "./theme";
|
||||
import CodeActionProvider = languages.CodeActionProvider;
|
||||
import IStandaloneCodeEditor = editor.IStandaloneCodeEditor;
|
||||
|
||||
type MonacoEditorState = {
|
||||
monaco: Monaco;
|
||||
@@ -28,12 +29,14 @@ export default function SourceEditor({
|
||||
theme,
|
||||
diagnostics,
|
||||
onChange,
|
||||
onMount,
|
||||
}: {
|
||||
visible: boolean;
|
||||
source: string;
|
||||
diagnostics: Diagnostic[];
|
||||
theme: Theme;
|
||||
onChange: (pythonSource: string) => void;
|
||||
onChange(pythonSource: string): void;
|
||||
onMount(editor: IStandaloneCodeEditor): void;
|
||||
}) {
|
||||
const monacoRef = useRef<MonacoEditorState | null>(null);
|
||||
|
||||
@@ -70,7 +73,7 @@ export default function SourceEditor({
|
||||
);
|
||||
|
||||
const handleMount: OnMount = useCallback(
|
||||
(_editor, instance) => {
|
||||
(editor, instance) => {
|
||||
const ruffActionsProvider = new RuffCodeActionProvider(diagnostics);
|
||||
const disposeCodeActionProvider =
|
||||
instance.languages.registerCodeActionProvider(
|
||||
@@ -85,9 +88,11 @@ export default function SourceEditor({
|
||||
codeActionProvider: ruffActionsProvider,
|
||||
disposeCodeActionProvider,
|
||||
};
|
||||
|
||||
onMount(editor);
|
||||
},
|
||||
|
||||
[diagnostics],
|
||||
[diagnostics, onMount],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -100,7 +105,7 @@ export default function SourceEditor({
|
||||
fontSize: 14,
|
||||
roundedSelection: false,
|
||||
scrollBeyondLastLine: false,
|
||||
contextmenu: false,
|
||||
contextmenu: true,
|
||||
}}
|
||||
language={"python"}
|
||||
wrapperProps={visible ? {} : { style: { display: "none" } }}
|
||||
|
||||
Reference in New Issue
Block a user