# Tag Variable Hoisting
## Scoping Rules
### Tag Scope
Tag Variables follow similar scoping rules to JavaScript’s `let` and `const`, but instead of [being _block-scoped_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block#block_scoping_rules_with_let_const_class_or_function_declaration_in_strict_mode), Tag Variables `<let>` and `<const>` are _tag-scoped_.
#### Redeclaring
A Tag Variable cannot be declared more than once per scope:
```htmlembedded=
<let/x = 0/>
<let/x = 1/> // SyntaxError: 'x' has already been declared in the current scope
<const/x = 1/> // Same error as above
```
#### Shadowing
Like JavaScript’s block-level variables, Tag Variables can be “shadowed” in child tags: you can reuse an existing variable name in a deeper scope.
If we have the same variable name outside and inside a tag, the variable _inside_ the tag takes priority over the outer variable:
```htmlembedded=
<let/x = 0/>
<div>
<div>
<let/x = 1/>
${x} // 1
</div>
${x} // 0
</div>
```
#### Preceding References
If a variable name is used before its Tag Variable’s declaration in a `<let>` or `<const>`, it is considered a _preceding reference_.
A _preceding reference_ cannot be read during the render phase. Attempting to do so will throw an error:
```htmlembedded=
<div>
${x} // ReferenceError: 'x' cannot be read during render (preceding reference)
</div>
<let/x = 0/>
```
### Hoisted References
Outside of the tag they are defined in, Tag Variables are not accessible under normal scoping rules. Referencing a variable from outside the tag it’s defined in is considered a _hoisted reference_.
```htmlembedded=
<div>
<span/el/>
</div>
<effect() {
el().textContent = "Hello";
}/>
```
Like _preceding references_, _hoisted references_ cannot be read during the render phase. Attempting to do so will throw an error:
```
ReferenceError: 'el' cannot be read during render (hoisted reference)
```
#### Shadowing
If a Tag Variable shadows another Tag Variable, the inner variable will _never_ have a hoisted reference. It's a sort of reverse shadowing for hoisting purposes.
```htmlembedded=
<div>
<span/el>
<button/el/>
</span>
</div>
<effect() {
el(); // HTMLSpanElement, not HTMLButtonElement
}/>
```
#### Conditional References
If a hoisted reference refers to a Tag Variable that is not rendered, it will resolve to `undefined`.
```htmlembedded=
<if=test>
<span/el/>
</if>
<effect() {
el?.().textContent = "Hello";
}/>
```
#### Dynamic References
Standard tag-scoped references are unambiguous: the closest scope wins and a variable cannot be redeclared in a scope.
However, with hoisted references, all references have the same specificity: they will refer to whatever Tag Variable is rendered.
```htmlembedded=
<if=test>
<span/el/>
</if>
<else>
<button/el/>
</else>
<effect() {
el().textContent = "Hello";
}/>
```
#### Ambiguous References
However, if more than one Tag Variable for a binding is rendered, it is considered an _ambiguous reference_.
```htmlembedded=
<div>
<span/el/>
<effect() {
el; // HTMLSpanElement
}/>
</div>
<div>
<button/el/>
<effect() {
el; // HTMLButtonElement
}/>
</div>
<effect() {
el; // ReferenceError: 'el' is an ambiguous reference
}/>
```
Even if one Tag Variable is "further" than another, it is considered ambiguous, because all hoisted references have the same specificity.
```htmlembedded=
<div>
<span/el/>
</div>
<div>
<div>
<button/el/>
</div>
</div>
<effect() {
el; // ReferenceError: 'el' is an ambiguous reference
}/>
```
This ambiguity holds if hoisting from a scope that renders multiple times:
```htmlembedded=
<for|item| of=["a", "b", "c"]>
<span/el>${item}</span>
</for>
<effect() {
el; // ReferenceError: 'el' is an ambiguous reference
}/>
```
## Implementation
- If there are hoisted references to a variable, it will be hoisted to a scope where it can be reached by all references.
- Hoisted references will use a `hoistedRead` helper that will throw if called during render.
## Rules
### When should something be hoisted?
1. From a tag variable, walk up the tree.
2. If a local definition is found, the variable is **not** hoistable.
3. Otherwise, the variable is available everywhere in the template.
#### Runtime implications
- When hoisting, hoist _just enough_ to capture all references. (This is an optimization; conceptually it is hoisted to the root.)
### How are references resolved?
1. If a local definition is discovered while walking up the tree, always use that.
2. If there is a no definition, should read a hoisted reference.
#### Runtime implications
- In dev-mode only, when reading a hoisted reference, all reads should be wrapped in a function that:
- Ensures there is only one declaration currently writing to the hoisted variable.
- Ensures that the read happens outside of the render phase.
- A workaround for transferring a hoisted variable to another tag in the render phase: wrap it in a closure that reads it _after_ the render phase, like `<some-tag ref=() => el()>`.
## Examples
### (1)
```htmlembedded
<div>
<const/x=1/>
</div>
<div>
${x}
</div>
```
Output:
```htmlembedded
<div></div>
<div>1</div>
```
### (2)
```htmlembedded
<const/x=1/>
<div>
<const/x=2/>
${x}
</div>
${x}
```
Output:
```htmlembedded
<div>2</div>
1
```
### (3)
```htmlembedded
<div>
<const/x=1/>
${x}
</div>
${x}
<const/x=2/>
```
Output:
```htmlembedded
<div>1</div>
2
```
### (4)
```htmlembedded
${x}
<div>
<if=true>
<const/x=1/>
</if>
</div>
```
Output:
```htmlembedded
1
<div></div>
```
<details>
- A tag variable is available everywhere within a template.
- A **standard reference** is a reference to a tag variable that precedes the reference and is owned by a direct parent tag of the reference.
```htmlembedded
<let/x = 0/>
<div>${x}</div>
```
- A **hoisted reference** is a reference to a tag variable that comes _after_ the reference, or is _not_ owned by a direct parent tag of the reference. **A hoisted reference cannot be read during render.**
```htmlembedded
<effect() {
console.log(x);
}/>
<let/x = 0/>
```
```htmlembedded
<div>
<let/x = 0/>
</div>
<div>
<effect() {
console.log(x);
}/>
</div>
```
- A tag variable can be shadowed in tags directly above or below them.
```htmlembedded
${x /* HIGH */}
<div>
<let/x = "HIGH"/>
${x /* HIGH */}
<div>
${x /* LOW (ReferenceError: read preceding reference in render) */}
<let/x = "LOW"/>
${x /* LOW */}
<div>
${x /* LOW */}
</div>
</div>
</div>
```
```htmlembedded
<div>
${x /* HIGH (Error: read hoisted in render) */}
<div>
<let/x = "LOW"/>
</div>
</div>
<let/x = "HIGH"/>
```
```htmlembedded
<div>
<let/x = "HIGH"/>
</div>
<div>
${x /* HIGH & LOW (Error: read hoisted reference in render) */}
<effect() {
x /* HIGH & LOW (Error: ambiguous reference) */
/>
<div>
<let/x = "LOW"/>
</div>
${x /* HIGH & LOW (Error: read hoisted in render) */}
</div>
```
</details>