Skip to content

function symbol rename#10117

Open
Irev-Dev wants to merge 11 commits intomainfrom
kurt-function-rename
Open

function symbol rename#10117
Irev-Dev wants to merge 11 commits intomainfrom
kurt-function-rename

Conversation

@Irev-Dev
Copy link
Contributor

@Irev-Dev Irev-Dev commented Feb 19, 2026

Resolves #10114. Resolves #7751.

@vercel
Copy link

vercel bot commented Feb 19, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
modeling-app Ready Ready Preview, Comment Feb 27, 2026 4:06am

Request Review

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 20, 2026

Merging this PR will not alter performance

✅ 166 untouched benchmarks
⏩ 93 skipped benchmarks1


Comparing kurt-function-rename (a10494b) with main (788e1a7)2

Open in CodSpeed

Footnotes

  1. 93 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (2801a29) during the generation of this report, so 788e1a7 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe this is the script that YJ noticed it in the first place, so it makes sense this has updated.

Copy link
Contributor Author

@Irev-Dev Irev-Dev Feb 20, 2026

Choose a reason for hiding this comment

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

the riddle small input is

ANSWER = 41803

fn t(@s) {
  return (ANSWER * s + 12345) % 214748
}

xs = 205804
ys = 71816
ox = 35 - (t(xs) % 70)
oy = 35 - (t(ys) % 70)
r = startSketchOn(XZ)
  |> startProfile(at = [ox, oy])
  |> line(end = [1, 0])
  |> line(end = [0, -1])
  |> line(end = [-1, 0])
  |> close(%)
  |> extrude(length = 1)

And the answer inside the function did not get converted before, So this is also fixing an existing bug

fn rename_identifiers(&mut self, old_name: &str, new_name: &str, excluded: &[&str]) {
let param_names: Vec<&str> = self.params.iter().map(|p| p.identifier.name.as_str()).collect();
let excluded_for_body: Vec<&str> = excluded.iter().copied().chain(param_names.iter().copied()).collect();
self.body.rename_identifiers(old_name, new_name, &excluded_for_body);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason this couldn't be simplified to this, without needing excluded? Now that I'm writing this, I'm realizing... what happens if they're trying to rename to the same name as a parameter? That will be broken. But it's better than what we have now.

Suggested change
self.body.rename_identifiers(old_name, new_name, &excluded_for_body);
if !self.params.contains(old_name) {
self.body.rename_identifiers(old_name, new_name);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have changed again since see #10117 (comment)

And yeah I think we always need to recurse because it's not only looking for params.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, maybe I'm a little slow. But once we find that a name has been shadowed, why do we need to continue? Can't we stop there? We're not trying to do some kind of bulk rename of multiple things, where some things are shadowed and some aren't. Either the old_name is not re-bound and so we need to continue, or it is re-bound and so we're done. In the case of a local var being defined, we can break out of the loop of body items when we see old_name being redefined.

@Irev-Dev Irev-Dev marked this pull request as ready for review February 20, 2026 03:50
@Irev-Dev Irev-Dev requested a review from a team as a code owner February 20, 2026 03:50
let refs: Vec<&str> = excluded_owned.iter().map(String::as_str).collect();
item.rename_identifiers(old_name, new_name, &refs);
if let BodyItem::VariableDeclaration(vd) = item {
excluded_owned.push(vd.declaration.id.name.clone());
Copy link
Contributor

Choose a reason for hiding this comment

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

If we really want to be correct, it's a bit more complicated than this. Expressions can introduce name bindings. TagDeclarators are the obvious one, but there are others.

For the frontend refactoring, I made a function that finds all bindings in an expression.

fn find_defined_names_expr(expr: &Expr, defined_names: &mut HashSet<String>) {

But also... I don't think it's necessary to handle all these edge cases in this PR if it's complicated. What you have is already so much better than what's in main. OTOH, I suppose there's an argument that if we can't be correct, main not renaming enough is maybe better than renaming too much.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Still not sure if I'm covering everything.
675e327

Copy link
Contributor

Choose a reason for hiding this comment

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

Here's a test that seems to fail on your branch. Rename creates an error. Let me know if you think this is too obscure. But this is the recursive nature of the beast.

BEST = 2

fn foo() {
  sketch001 = startSketchOn(XY)
  profile001 = startProfile(sketch001, at = [0, 0])
    |> xLine(length = BEST)
    |> yLine(length = BEST, tag = $BEST)
    |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
    |> close()
  return profile001
}

foo()

…s, named fns)

- In function bodies, exclude a name only for items after the one that binds it,
  so use-before-local still renames (refers to global); bindings in the current
  item are excluded while processing it so tags/labels/named fns aren’t renamed.
- Add body_item_defined_names and collect_defined_names_expr/binary_part (mirror
  frontend find_defined_names) so tags, LabelledExpression labels, and optional
  function names are treated as bindings; VariableDeclaration still only excludes
  init bindings for the current item (not the decl id) so RHS sees the global.
- Make TagDeclarator respect excluded when renaming.
- Add tests: tag binding not renamed, named function expression not renamed.
/// Rename all identifiers that have the old name to the new given name.
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
fn rename_identifiers(&mut self, old_name: &str, new_name: &str, excluded: &[&str]) {
self.callee.rename(old_name, new_name);
Copy link
Contributor

Choose a reason for hiding this comment

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

The callee identifier is renamed without checking the excluded list. This will incorrectly rename function calls where the callee is a shadowed identifier (e.g., a function parameter or local variable with the same name as a global being renamed).

Impact: When renaming a global like FUNC, a call like FUNC() where FUNC is actually a parameter will be incorrectly renamed, breaking the code.

Fix:

if !excluded.contains(&self.callee.name.as_str()) {
    self.callee.rename(old_name, new_name);
}

This matches the pattern used for Expr::Name (lines 1356-1360) and BinaryPart::Name (lines 1851-1855).

Suggested change
self.callee.rename(old_name, new_name);
if !excluded.contains(&self.callee.name.as_str()) {
self.callee.rename(old_name, new_name);
}

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

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.

[BUG]: Renaming in KCL Rename variable should update references inside functions

2 participants