-
-
Notifications
You must be signed in to change notification settings - Fork 794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added extract constant code action #4228
base: main
Are you sure you want to change the base?
Conversation
3ebaaa4
to
3ccbe21
Compare
bd8e305
to
7f8aef4
Compare
FYI before this can be merged, you'll need to add tests. Those can be found in the |
d193b39
to
11ba75b
Compare
fbdf675
to
5398646
Compare
Thank you both! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!! Looking very nice.
Could you add tests for these please:
- Expressions with function calls
- Expressions using local variables
- Expressions using records
- Expressions use constants
- Expression with bool operators
- Expressions with logical operators
- When the name is taken by a const
- When the name is taken by a function
It would also be good to take local variables into account for the naming.
}) | ||
.collect(), | ||
}; | ||
name_generator.reserve_variable_names(module_constant_names); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Functions would also want to be taken into account here. Perhaps this would fit best in the walker? I've not looked at how we do it in the others.
if let Some(function_start) = container_function_start { | ||
self.edits.insert( | ||
function_start, | ||
format!("const {variable_name} = {content}\n\n"), | ||
); | ||
|
||
self.edits | ||
.replace(expression_span, String::from(variable_name)); | ||
} | ||
|
||
let mut action = Vec::with_capacity(1); | ||
CodeActionBuilder::new("Extract constant") | ||
.kind(CodeActionKind::REFACTOR_EXTRACT) | ||
.changes(self.params.text_document.uri.clone(), self.edits.edits) | ||
.preferred(false) | ||
.push_to(&mut action); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When is it possible for this to ever be None
? It looks like in that case it would add a refactoring that doesn't contain any edits?
} | ||
} | ||
|
||
ast::visit::visit_typed_expr(self, expr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we want to stop walking the tree if the expression has already been found?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not quite, it's required to continue in case we want to extract a literal that's inside, say, a function call or record constructor. Maybe it won't be needed after applying the rest of the changes, right now it's necessary.
let container_function_start = self | ||
.module | ||
.ast | ||
.definitions | ||
.iter() | ||
.filter(|definition| definition.is_function()) | ||
.map(|function| function.location().start) | ||
.filter(|function_start| function_start < &expression_span.start) | ||
.max(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we do this during the tree visiting?
a00559b
to
4e8e361
Compare
If you are ready for a review please un-draft the PR 🙏 |
83753c0
to
10d41e2
Compare
…ests and reworked them to cover more cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fab work! Thank you!
I've left some comments inline.
Could you add these tests also:
- Extracting results
- Extracting Nil
- Extracting bit arrays
- Extracting a custom type that has fields (a record)
- Extracting expressions which reference constant variables
- Extracting expressions from assignments that use complex patterns (the assignment can stay and just the value is replaced)
- Refusing to extract expressions which reference non-constant variables
- Refusing to extract expressions which call functions
And anything else you can think of. I may have missed something 😁
Thank you!
constructor.variant, | ||
type_::ValueConstructorVariant::Record { arity: 0, .. } | ||
), | ||
_ => false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No catch-all pattern please! They cause bugs as you don't have to update the code when new ones are added.
The nicest way to do this will be to move all the guards into the body of the clause, to avoid repeating each variant twice (once with a guard, once without).
Assignment, | ||
} | ||
|
||
fn is_literal_or_made_of_literals(expr: &TypedExpr) -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A list of literals is a literal also.
fn is_literal_or_made_of_literals(expr: &TypedExpr) -> bool { | |
fn is_literal(expr: &TypedExpr) -> bool { |
Though I think it is really trying to work out is if the expression is valid constant expression syntax, which you can't tell by looking at just the AST. You'd need to know if variables point to constants or not, and if all the calls are to record constructors.
true | ||
} | ||
// Attempt to extract whole bit array as long as it's made up of literals | ||
TypedExpr::BitArray { segments, .. } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You would need to check the options also to check for any variable usage there
value_to_use: Option<EcoString>, | ||
} | ||
|
||
enum ExtractableToConstant { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you document what this is and how it is used please, including each variant.
self.edits.delete(expr_span_with_new_line); | ||
} | ||
|
||
ExtractableToConstant::Collection | ExtractableToConstant::Value => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these two exactly the same then?
if matches!( | ||
self.type_of_extractable, | ||
None | Some(ExtractableToConstant::Collection) | ||
) && is_literal_or_made_of_literals(expr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this bit doing? Would be useful to have documentation comments explaining the intent
TypedExpr::Int { .. } | TypedExpr::Float { .. } | TypedExpr::String { .. } => { | ||
ExtractableToConstant::Value | ||
} | ||
_ => ExtractableToConstant::Collection, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mapping of types to ExtractableToConstant
isn't quite right. They're all values, and there are things that are not containers being marked as containers here.
container_function_start: Option<u32>, | ||
type_of_extractable: Option<ExtractableToConstant>, | ||
name_to_use: Option<EcoString>, | ||
value_to_use: Option<EcoString>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be useful to have these documented as it's a little unclear what they are.
What's the different between "value to use" and "selected expression"?
"type of extractable" seems to not be a type.
Addresses #3936