Skip to content

[script] Fix $var not finding an exported variable when an entity of the same name is in scope - #2389

Open
andrewgazelka wants to merge 1 commit into
SanderMertens:masterfrom
andrewgazelka:script-global-var-shadowed-by-entity
Open

andrewgazelka wants to merge 1 commit into
SanderMertens:masterfrom
andrewgazelka:script-global-var-shadowed-by-entity

Conversation

@andrewgazelka

Copy link
Copy Markdown
Contributor

(AI found and written)

A $name reference to an exported variable fails with unresolved reference when an entity of the same name is in scope, most often the entity being defined. Renaming either one makes it work. I couldn't find an existing issue for it.

// script 1
export const x = 10

// script 2
parent {
  x {
    Position: {$x, 20}   // error: unresolved reference 'x'
  }
}

It also fails within one script (export const x = 10 followed by the parent block above), and when the variable is exported in an enclosing scope (outer { export const x = 10 }, then outer { x { inner { x { Position: {$x, 20} } } } }).

Cause. flecs_expr_global_variable_resolve (src/addons/script/expr/visit_type.c:1896) resolves $x with the regular entity lookup, flecs_script_symbol_lookup(..., FlecsScriptLookupAll), and accepts only the first match. That lookup finds the nearest entity named x: first the script's own declarations (flecs_script_type_symbol_lookup, which here returns the x being defined), then the world lookup from v->parent with recursive = true (src/addons/script/eval/visit_eval.c:251), which finds parent.x before the root x. When that entity isn't a variable, the resolve records an unresolved reference and stops, even though an exported variable named x is in an enclosing scope.

Fix. $ always means a variable, so an entity that isn't a variable shouldn't hide one. The script already treats a local variable that shadows an exported one as an error ("local variable '%s' shadows an exported variable"), so name hiding isn't something the lookup is meant to allow for variables. The new flecs_script_global_var_lookup (visit_eval.c) runs the same lookup first, so every case that resolves today resolves to the same thing. Only when the result is an entity that isn't a variable does it keep searching, innermost scope first, and return the nearest variable:

  • during the type pass, flecs_script_type_global_var_lookup (visit_type.c) walks the scope tables like flecs_script_type_find does, checking each scope for variables the script declares and for variables already in the world under that scope's entity. The scope entity is found without creating it (from its symbol slot, or as an existing child by name). This covers the nested case, where the type pass's v->parent is still the script root;
  • then the world scopes from v->parent up to the root, and the using scopes.

If no variable is found, the error is the same as before. The change only affects $name. Bare identifiers and assignment to exported mut variables still use the normal entity lookup.

Tests (test/script, Eval):

  • export_const_var_w_entity_same_name_in_scope: the example above, two scripts.
  • export_const_var_w_entity_same_name_in_scope_same_script
  • export_const_var_w_entity_same_name_in_nested_scope: variable exported in outer by one script, used from outer.x.inner.x by the next.
  • export_const_var_w_entity_same_name_nearest_var: with x exported at the root and in outer, $x in outer.inner.x gets the one from outer.
  • export_mut_var_w_entity_same_name_in_scope
  • var_w_entity_same_name_no_var: $x with only the entity x in scope still errors.

The first five fail on master. The whole script suite passes on this branch: 8880 passed, 0 failed, with IR both disabled and enabled (macOS arm64, clang, bake run test/script). distr/flecs.c also builds with the amalgamated CI flags (-pedantic -Wall -Wextra -Werror -Wshadow -Wconversion).

`$name` resolved an exported variable with the regular entity name lookup and
gave up when the first entity it found wasn't a variable. An entity with the
same name in an enclosing scope, such as the one being defined, hid the
variable:

    export const x = 10
    parent {
      x { Position: {$x, 20} }   // unresolved reference 'x'
    }

The lookup for `$name` now continues past entities that aren't variables, scope
by scope, through the variables declared in the script and the ones already in
the world.

This branch has not been deployed

No deployments
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.

1 participant