Skip to content

Conversation

llpaull
Copy link

@llpaull llpaull commented Jul 2, 2025

This PR addresses #4716

Copy link
Member

@lpil lpil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brilliant, thank you. This will be really nice.

I've left some notes inline, thank you

@llpaull llpaull marked this pull request as draft July 4, 2025 01:11
@lpil
Copy link
Member

lpil commented Jul 16, 2025

Hello! Just figuring out what is going to go into what release. Are you still working on this? :)

@llpaull
Copy link
Author

llpaull commented Jul 16, 2025

Yes I am! As for a progress update, most tests are written but still need to write some for guard clause and record access with same name as module. From what I can tell and have tested, all references to the module (alias) can be found and are appropriately renamed but it can't yet be renamed from every reference.

@llpaull llpaull marked this pull request as ready for review July 19, 2025 22:05
Copy link
Member

@lpil lpil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! This is looking fab!

I've not finished reviewing this yet, the snapshots are quite a bit more verbose than we usually go for, so it is taking me longer than usual and I've run out of time, but my comments so far should be useful if not complete.

It would be fab if the tests could be slimmed down to make them more focused- I've left some comments inline on that theme.
I think there's some repetition with the test also, with multiple tests testing the use of the AST visitor reaching everywhere.

I've not yet found any test for nested modules, if there are none we want those too.

Thanks again! Looking really nice, it'll be very nice to have this functionality in the language server.

.iter()
.zip(elements_type.iter())
.for_each(|(e, type_)| self.visit_type_ast(e, type_));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the tuple AST must always have a tuple type then panic in the case that it does not instead of silently skipping over the malformed code. We want to know when there is a bug.

Could it be linked here? Do the links need to be collapsed?

self.visit_type_ast(return_, ret_type);
}
}
_ => {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above- no catch alls, panic for impossible invariants, and what about links?


-- app.gleam
import mod
fn func() { let _: fn(mod.GenericType(mod.Type)) -> mod.GenericType(mod.Type) = fn(arg: mod.GenericType(mod.Type)) -> mod.GenericType(mod.Type) { arg } }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These snapshots with really long unformatted lines are hard to read and it's not immediately clear what bit is being focused. Please use more normal formatting.

const c10 = alias.const3 <> alias.const3
type Type1 { Var(alias.Type) }
type Type2 = alias.GenericType(alias.Type)
type Record { Record (bool: Bool) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't tell what this test is testing, it's very large!

pub const const2 = <<0:8>>
pub const const3 = "Hello"
pub type Type { Variant1 Variant2(Int) }
pub type GenericType(inner) { Node(inner, GenericType(inner)) Leaf }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nicer if this repeated code wasn't so large, each test uses very little of it. Ideally tests don't include things that they don't use, to make it clearer what they test and the snapshots easier to read. Currently it's quite slow to review these snapshots as it takes a moment for each one to spot the actual test code in the snapshot.

That said, a lot of code has been written using this boilerplate now, so would be annoying to change that.

@lpil lpil marked this pull request as draft July 21, 2025 12:29
@llpaull llpaull marked this pull request as ready for review August 18, 2025 22:53
@llpaull
Copy link
Author

llpaull commented Aug 18, 2025

Tests have been slimmed down quite extensively, if you want any specific or more tests let me know

Copy link
Member

@lpil lpil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello! This is looking fab! I've left a few small notes inline, and I think we may be missing tests to cover these situations:

  • Namespaced modules (e.g. one/two)
  • Imports with spaces (e.g. import one / two . { three , four } as five
  • Uses of modules with spaces (e.g. let x = thingy . Thingy)
  • Triggering rename of an aliased module from uses of it
  • Triggering rename of a namespaced module from uses of it

}
}

pub(crate) fn name_location(&self) -> SrcSpan {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the location of the module name, that would always be the value of the module_location field. Perhaps "used_name_location"? Please document what this function does also

) {
if *module_name == self.module_name {
self.references.push(ModuleNameReference {
location: SrcSpan::new(location.start, field_start - 1),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments start with a capital letter 🙏


for (arg, type_) in arguments.iter().zip(args_type) {
self.visit_type_ast(arg, type_);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we call the original method to do the iteration?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I was not using the original method was because I was using the full namespaced module name, and the TypeAstConstructor's module field contains the alias instead of the full name (as do other AST nodes). The type_ field however does contain the full name which is why I decided to use this custom method.

I have now been trying to use the alias instead of the full module name which has cleaned this Visit Impl quite a lot but causes the hover action to fail since they both use Located::ModuleName and the alias is not used as the key inside the ProjectCompiler's importable_modules hashmap.

So from what I can tell, either the AST nodes need to contain the full module name instead of/as well as the alias (see TypedExpr::ModuleSelect) or Located::ModuleName needs to contain both.

Which would you rather want or do you know of an alternative solution?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me.

}
// If new name is same as original, remove the alias
ModuleNameReferenceKind::AliasedImport if params.new_name == original => {
// subtract 1 from start to delete leading space added above
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capital letter please.

What leading space added above? What happens if there's multiple spaces?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are two scenarios imports can fall into:

1: Import does not contain unqualified member import -> delete all of (module_location.end+1, import.location.end), this will delete all spaces between module and alias as well as the alias itself but not remove any spacing after alias

2: Import does contain unqualified member import -> import AST node does not explicitly contain end location for unqualified imports so the only safe space to remove is the one required to separate tokens

By only deleting the required space, even if there are multiple, I tried to introduce consistency and allow the user to keep multiple import aliases aligned when using the LSP to rename.

Is that okay with you?


for (arg, type_) in arguments.iter().zip(args_type) {
self.visit_type_ast(arg, type_);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me.

match node {
TypeAst::Constructor(TypeAstConstructor { module, arguments, .. }) => {
let _ = type_.named_type_information().inspect(|(module_name, _, args_type)| {
if let Some((_, location)) = module {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the module check first as it's cheaper than the call to named_type_information.

Write these clauses using pattern matching instead of the inspect please, as it's a bit cryptic and the end result isn't used, making inspect not the right combinator for this action also.

@lpil lpil marked this pull request as draft August 25, 2025 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants