docs/semantics/scoping.md
Enso's scoping rules should be fairly familiar to those coming from other languages that are immutable (or make heavy use of immutability). In essence, Enso is a lexically-scoped language where bindings may be shadowed in child scopes.
<!-- MarkdownTOC levels="2,3" autolink="true" --> <!-- /MarkdownTOC -->A scope is the span in the code within which a set of accessible identifiers occurs. A nested scope may:
Identifier visibility behaves as follows:
s are accessible in s and all
the children of s, after the point at which they are introduced.The term accessible is defined to mean "can be referred to in the code as a valid entity," and hence implies "can have its value used."
The actionables for this section are:
- In the future we may want to relax the forward-definition restriction for pure bindings, allowing a form of recursive pure binding hoisting (like a let block). This would use the monadic context's
fixfunction.- Once we are capable of supporting
fixand recursive pure bindings in contexts, we need to revisit the above rules.
The following constructs introduce new scopes in Enso:
(->): The arrow operator introduces a new scope that
is shared by both of its operands. This is true both when it is used for a
lambda (value or type), and when used to denote case branches.A new scope is always introduced as a child of the scope in which the introducing construct occurs, unless explicitly noted otherwise.
There are other linguistic constructs that behave as if they introduce a scope, but this is purely down to the fact that they desugar to one or more of the above constructs:
The actionables for this section are:
- Write this out in more detail when we have time. The above is only intended as a brief summary.
- Decide if we want to support local overloads at all (differing in the type of
this). Local overloads are those that are not defined at the top level of a module, and are currently unsupported.- We need to refine the specification for body-signature mutual scoping.
- NOTE: The note about case-branch scoping needs to be refined as the implementation of
caseevolves.
Currently, type signatures in Enso obey a simple set of typing rules:
The actionables for this section are:
In order to enable much of the flexible metaprogramming ability that Enso aims for, we have an additional set of scoping rules for type signatures:
- Both operands of the type ascription operator share a scope.
- If two names are used on the type and term levels to refer to the same entity, both are valid but this issues a warning. Referring to the same entity means that they are two names for the same underlying object.
- Name clashes are disallowed unless the clashing names refer to the same entity.
- Do we actually want to support this?
- What complexities does this introduce wrt typechecking?
This section contains notes on the implementation of the Enso scoping rules in the interpreter.
In order to support suspended function arguments in the interpreter in a performant way, we implicitly wrap all function arguments in a suspension. In conjunction with making the function itself responsible for when its arguments are evaluated, this lets us have incredibly performant suspended computations in Enso.
However, it does require creating a small hack in the Alias Analysis process:
To this end, we account for this implementation detail in alias analysis.
Another quirk of the internal alias analysis process is down to the fact that the Enso IR represents Methods, functions, and blocks as separate constructs. This means that if you had a method containing a function containing a block, a naive implementation of alias analysis would allocate three scopes here.
This is incorrect, according to the semantic specification of the language, so the alias analysis process needs to handle the collapsing of these scopes as it allocates them. The rules are as follows: