I have uploaded an experimental build exploring some potential interrelated language changes.
https://www.autohotkey.com/download/2.0/AutoHotkey_2.0-a124-22-g0a70f190.zip
https://github.com/Lexikos/AutoHotkey_L/commits/x
# ByRef
ByRef has been replaced with the reference operator, using the symbol &. (v2.0-a111 replaced the address operator with separate functions, StrPtr and ObjPtr.)
&var produces a VarRef object, which can be used as follows:
- Dereference with %ref% to read or assign the target variable.
- Pass it to a ByRef parameter (see below).
- Pass it to an OutputVar parameter.
- Pass it to IsSet.
- Anything else one might do with a reference to an object, such as storing it in an array or binding it to a function parameter.
ByRef parameters are declared as &var instead of ByRef var, and always require a VarRef except when omitted (and optional). To permit a VarRef or any other value, declare a normal parameter and use explicit dereferencing where appropriate.
IsByRef has been removed due to ambiguity in the implementation (an alias may be due to passing a reference in, taking a reference to the parameter itself, or referring to it in a closure).
Only built-in functions have OutputVar parameters. Due to current technical limitations, taking a reference to a built-in variable is not permitted. However, if &var is passed directly to an OutputVar parameter, var is permitted to be built-in (but not read-only).
## VarRef Object
Like ComObject, properties and methods cannot be defined for a VarRef, and it has no base object.
# Var Unset Errors
The #Warn UseUnset warning type has been removed. Referencing an unset variable (except as the target of an assignment or the reference operator) now raises an error.
# Global
Why change?
## Assign-local
Within a function, if a given variable name is not used as the target of an assignment or reference (&) operator inside the function, it may resolve to an existing global variable even without declaration.
Classes are no longer super-global. For instance, inside a function which contains object := {}, object refers only to a local variable, not to the Object class. New classes can be added without changing the behaviour of existing functions (except functions which had erroneous references to undefined variables, or which set them only dynamically).
Declarations for a given variable can be limited to just the functions that modify that global variable, making global potentially useful as an indicator of side-effects. Since fewer functions will need to declare the global variable, there is less reason to declare it super-global. With fewer super-global variables, there is less risk of a function having unintended side-effects (assigning to a global variable), and less need for force-local.
### Known Issues
A dynamic assignment such as %'x'% := y (without assume-global) will create a local variable even if non-dynamic references to x were resolved to a global variable due to lacking any non-dynamic assignments. This local variable can only be referenced dynamically.
Resolving the dynamic assignment to a global variable would be inconsistent with non-dynamic assignments, would make unintended side-effects more likely, and would make identifying side-effects more difficult. It may be better to require assume-global or force-local when creating new variables at runtime, or disable the creation of new variables.
Assign-local resolves a non-dynamic reference in a function to a global variable only if it exists at load time. If it is not declared anywhere or referenced non-dynamically in global scope, non-dynamic references inside functions are resolved to (unassigned) local variables. These variables could be assigned dynamically.
# Merging of Variable and Function Namespaces
Function names are no longer kept separate to variable names. Instead, each function definition creates a "read-only variable" (constant). Virtually any sub-expression can be called by immediately following it with an open parentheses (with no leading space). For instance, MsgBox("Hello") is the same as (MsgBox)("Hello"); in both cases, MsgBox is a constant referring to the MsgBox function (unless shadowed by a local variable).
If the target of a function call is a function constant, parameters are validated as before. If a function name is mispelled, the error is usually detected by #Warn VarUnset. Even if it is not detected at load time, a runtime error is raised (as usual) if the value cannot be called.
%x%() now performs a double-deref and then calls the result, so %'MsgBox'%() performs as before but %MyObj%() is invalid (the percent signs should be removed).
Func("name") has been removed since name is sufficient. Other built-in functions which accepted function names have been changed to only accept references. For example, SetTimer MyFunc instead of SetTimer "MyFunc". IsFunc("name") has also been removed, and scripts should generally deal with function objects directly, not function names (strings). If a name string must be resolved to a function reference, it would be done via a double-deref. Function objects can be validated as before.
Due to the increased complexity and potential for accidents, the function library auto-include mechanism has been removed. (Another reason is that it might be superseded by module/namespace support.) #Include <Lib> still works as before.
Force-local now affects calls to global functions, since function calls are just as dependent on variable scope rules as naked variable references. For instance, global MsgBox is required before the global MsgBox function can be used in a force-local function, since MsgBox() is resolved the same way as f := MsgBox, f().
## Classes
ClassName.New() has been replaced with ClassName.Call(), since one can now simply write ClassName().
The Object(), Array() and Map() functions have been removed. Map now has a constructor which accepts "key, value" parameter pairs, and can be called as Map(key, value) since Map in this context is now the class rather than a function name. As the Object() constructor is inherited by derived classes (including all user-defined classes where the base class was unspecified), it (still) does not accept parameters, unlike the former Object() function.
Methods and properties are merged once again, so the following were removed: ObjOwnMethods, DefineMethod, DeleteMethod, HasOwnMethod, OwnMethods.
Property descriptors accepted by DefineProp and returned by GetOwnPropDesc have an additional property Call, which specifies a function object to be called when the property is called. If not defined for a call such as x.y(), the y property's getter is called to retrieve a function object, which is then called.
Methods of classes (both built-in and user-defined) are defined as simple value properties.
## Closures
Each nested function defines a local static constant. If a nested function captures a non-static local variable/constant of the outer function, that nested function becomes a Closure and the corresponding constant becomes non-static. In other words, nested functions are closures only when they have to be; a single function can contain both Func and Closure nested functions.
Before, if a function had any downvars (that is, if any of its local variables were referenced by a nested function), all of its (immediate) nested functions became closures. This was because even a nested function without any such references might call a closure directly or dynamically, or pass its name to a built-in function (such as SetTimer, Hotkey or Func). In order to instantiate a closure by name, it had to be a closure.
Because they are tied to local constants, named closures are only instantiated once each time the outer function is called. Before, each call to Func("name") instantiated a new Closure referring to the same variables. Something like OnMessage(n, Func("f")), OnMessage(n, Func("f"), 0) would fail if f was a closure since two different objects were passed to OnMessage.
Closure references within these non-static local constants are not counted; instead, the closures are kept alive for as long as any closure in the group has a non-zero reference count (or the outer function is still running). This allows recursive or inter-dependent closures to exist without creating a circular reference which locks them in memory. It also allows a closure to safely refer to itself, such as to pass itself to SetTimer, OnMessage, etc. However, copying a closure reference into a "captured" local variable (or an object contained by one) still causes a problematic circular reference.