AHK development shows great progress this year. I piled up a list of bugs/typos/my point of view suggestions accumulated recently. I prefer this format of keeping it all together in 1 updating post instead of creating multiple topics. The post will be revisited again and again by re-phrasing/clarifying the root of the problems, adding the community's opinions, removing the irrelevant and misleading stuff (errors happen quite often), adding more indicative short examples and half-way solutions, hiding resolved issues under spoilers and so on. Some of my points might be irrelevant (as proved by @Helgef, @swagfag, @nnnik, @lexikos) and I keep discarding them.

Most of my previous and following points propose command name's/syntax slight changes/addons/removals and focus on bringing more uniformity to the syntax: 5, 6, 13, 15, 25, 26, 27, 29
Some others concern debugging facility extension and seem to be not so hard to implement: 2, 3, 35, 36, 50. These, I believe, would greatly help with testing and debugging new alpha versions.
utils used throughout the topic
to collect and print out debugging info.
2. Ability to get global AHK vars (user and built-in) by enumerating them or something. And same for local ones. One step closer to managing namespaces.
e.g. listvars could take arguments and return some array or map instead of printing out in a window. Tbh, its current realization is totally useless for my taste: I can't use its output programmatically!
I recall in the past there were some hackery topics of patching AHK interpreter's binary code segment at runtime just to get these vars for further analyzing programmatically.
3. A_LineNumber, A_ThisFunc, A_LineFile, A_ThisLabel - to add similar command or expand the existing ones to expose callee's line number and function name (in other words, to expose calls' stack similar to exception()).
this would be very useful for debugging and logging purpose.
5. Also it would be nice to have a shorthand for "background" keyword like "bg" or "b". Similar to "c" for "color".
Essentially, "background" and "section" r the only keywords that don't have a shorthand:
Code: Select all
;an array of options for gui.add (for its 2nd parameter)
["section background" b, "xs r1 w" w " background" b, "xs background" b, "xs r1 w" w " background" b]
6. (bug?) if an object has __Enum method, it should return something for for-in loop and don't throw. I found irregular behavior for Map's extension classes:
Code: Select all
class dbg{
}
class dbg2 extends Map{
}
msgbox("dbg.prototype.HasMethod('__Enum'):" dbg.prototype.HasMethod("__Enum")) ;0
msgbox("dbg2.prototype.HasMethod('__Enum'):" dbg2.prototype.HasMethod("__Enum")) ;1
for k,v in dbg2.prototype ;error: Type Mismatch
msgbox("here")
Code: Select all
for k,v in (_o.hasmethod("__Enum") ? _o:_o.ownprops())
7. File and some other built-in objects should expose more properties for an enumeration.
Usage? Self-logging script. File.pos, etc In the case of file. One could just do a general and simple for-in loop thru classes/global vars to save the current script's state. File object might be not the best example. But exposing as many properties as possible is not a bad thing for built-in objects.
9. Function to support Map creation.
To get the idea of what I mean, in an example we split a string with ";" and ":" delimiters:
Code: Select all
s:="
(
margin:1.0pt;
text-indent:8.5pt;
font-family:"Verdana";
)"
t:=Map(StrSplit(s, [";",":"])*) ;relatively slow
;and throws run-time errors on empty array members corresponding to key-names instead of ignoring them..
;it should also left-trim/right-trim white space characters like \n\r\t for key-names.
It should be a lot faster doing this with a new separate command like StrSplitM with native c++ code.
12. local/global keywords for fat-arrow functions. In general it needs some vars declaration ability within an expression of fat-arrow. To be able to inline local vars usage. Right now I cannot use local variables inside fat-arrow function that declared at global scope.
So I have to resort to using a "hack" of allocating local vars in the args list, e.g.:
Code: Select all
gp_Pen(_c, _w){
local h
DllCall("gdiplus\GdipCreatePen1", 'uint',_c, 'float',_w, 'int',2, 'ptrP',h)
return h
}
Code: Select all
;a fat-arrow function declaration outside class/function body:
gp_Pen(_c, _w, h:=0)=>(DllCall("gdiplus\GdipCreatePen1", 'uint',_c, 'float',_w, 'int',2, 'ptrP',h), h)

13. NumGet to have a mode similar to NumPut "type1",output var1, "typeN",output varN ... address,offset. So it could automatically read multiple values by reading a value and changing the offset to the next one...
Code: Select all
r:={}, b:=bufferalloc(16)
r.x:=numget(b,0, 'float'), r.y:=numget(b,4, 'float'), r.w:=numget(b,8, 'float'), r.h:=numget(b,12, 'float')
Code: Select all
r:={}, b:=bufferalloc(16)
numget('float',r.x, 'float',r.y, 'float',r.w, 'float',r.h, b)
14. Recently I found this tendency of super-long keywords adoption. An average word in English is just 4.5 letters and computer languages go even further using some sort of abbreviations. For example, the majority of Basic's keywords is 3-5 letters long: dim, rnd, wend... Who knows, in the future AHK might get a command line interpreter mode like Python - the shorter keywords the better typing performance, and good stuff for inliners.

Recent updates have brought this monstrosity: varsetstrcapacity: 4 words 17 letters within 1 keyword, why so long? Why not SetStrCapacity, StrCapacity or something?
Another monster is GetOwnPropDesc. These Own* methods r too long to my liking, I would get rid of "Own" suffix. GetProp -> DefineProp, GetMethod -> DefineMethod pairs seem to be more appropriate.
And enumerators could be reduced down to .Methods() and .Props(): s and () at their end implies that they r enumerators.
15.
Further exploiting of .new method for built-in objects. Built-in objects system to be unified and consistent!.
fileopen -> file.new
bufferalloc -> buffer.new
maybe some cool syntax for StrSplit as well as long as it creates Array object. Array.FromStr() and Array.ToStr() (missing join, the opposite to StrSplit).

17. After having read the AHK v2 subforum, I came across with @Lexikos talking about BufferFrom(Ptr, Size) or similar command for memory range re-usage. This one would be very dangerous, possible to Page faults and other severe errors, but sometimes while working with huge memory arrays, re-usage instead of copying is a matter of life and death.
Lets imagine, we have a huge memory region created not by AHK's BufferAlloc/File.RawRead or others. It might be an aftereffect of some external library call. We do have a pointer and we know a current size of that region and we want to use Buffer object facilities to control its boundaries and to tackle it using NumPut or other Buffer-aware functions. And we don't wanna waste CPU and memory resources by copying that memory region.
b:=BufferFrom(memory_addr, current_length) and voila! We've just reduced our carbon dioxide emission level!

18. Preprocessor. Macro.
Why is this so important? Nowdays even assemblers or simple scripting languages like NSIS have an extensive preprocessor system. Google "ahk+preprocessor" https://www.google.com/search?sxsrf=ALe ... CAw&uact=5 - hundred requests - might be the most sought-after feature to be implemented.
in the simplest form,
Code: Select all
#define var_name
#if defined var_name
#else
#endif
Also, having
Code: Select all
#macro
#endm
All in all, preprocessor+macro is a language inside language with infinite posibilities and it won't hurt run-time performance.
19. loop keyword keyword keyword annoyance. Its so AHK1ish!
I recall a few alpha versions had LoopX commands coalesced: LoopParse, LoopFile etc. Loved it. Why? It facilitates code searching/replacing, it would allow loopsomething to be a function so one could insert it inside an expression...
20. Speaking of commands. Its cool to have function's call f() to be shorthanded to command syntax like the 2nd line in the example:
Code: Select all
f(a,b)=>a+b
f
For example, let's look at the Python's loops inside square brackets: [f(x) for x in xs if condition]
For readability, some ppl prefer each command to be on a separate line, but others would inline everything!
22. Refactoring... .exe compiler idea. NSIS is the only full-fledged script language competing with AHK by generating the smallest possible compiled standalone file: some standalone executable files could be as small as 25-40KB! Its done by separating interpreter's code into dlls and including only needed at "compile" time. NSIS script is not compiled into machine instructions or some pseudo-code, it remains in .exe as a source with removed var/subroutine names/comments/etc - same as AHK.
An example of NSIS compiled .exe's content (here we got some "built-in" and 3rd party libraries like 7z support):
23. Zero index for arrays. Recent versions made it mandatory to use indexing starting from 1. And [0] is an illegal index now. Alas, it does require additional work converting old scripts and worsens cross-language tasks. But also I do understand that having zero index reserved fits perfectly for many usage cases enabling the simplicity and power of boolean logic like in this case:
Code: Select all
h:="many words here!"
if(h~="word") ...
Please remove Match[0] (equal to a whole found string). There is an alternative Match.len(0) anyways.
24. (bug) Concerning line continuation section start mark.
https://lexikos.github.io/v2/docs/Scripts.htm#continuation
If a line begins with a non-escaped open parenthesis ((), it is considered to be the start of a continuation section unless there is a closing parenthesis ()) on the same line.
That brings issues of mixing continuation section with expressions divided by continuation prefix:
Code: Select all
a:=1
(b(0
, 0)) && a:=2
Line 2 starts with an expression in parenthesis (function call with 2 args) and that expression's result used as 1st operand of && operator.
When a regular continuation section starts, it looks like
Code: Select all
Var := "
(
Line 1 of the text.
Line 2 of the text. By default, a linefeed (`n) is present between lines.
)"
The current state leads to the following preprocessor printout (the same behavior with AHK v2 builds 103,108,115):
Code: Select all
Error at line 1.
Line Text: a:=1, 0)) && a:=2
Error: missing ")"
26. Similar to 25, ability to test whether a gui control is "destroyed" w/o throwing.
27. Naming inconsistency. Some built-in functions use Len prefix, other properties have a Length moniker. e.g. StrLen, Match.Len() vs File.Length, Array.Length. For consistency, I would prefer unified Len everywhere.
28. Callback for regexreplace. So that it would be possible to exploit a custom function instead of replace argument's string pattern.
NB: Not to confuse Callback with the existing Callout debugging facility!
Real-world use of that functionality (regexf function). Example does decimal html entities -> unicode) conversion:
Code: Select all
s:="разобрат"
o:=str.regexf(s, '(?:&#(\d+);)', (_m)=>chr(_m[1]), c)
msgbox(s "`n->`n" o "`nReplaced <" c "> times")
class str{
;RegExReplace with callback. _s:haystack, _q:needle, _f:replace func, _c:cnt
static regexf(_s, _q, _f, byref _c:=unset){
n:=1, s:="", c:=0
while(i:=regexmatch(_s, _q, m, n))
c++, s.=substr(_s, n, i-n) %_f%(m), n:=i+m.len(0)
isset(c) && _c:=c
return s substr(_s, n)
}
}
29. Addition of FileWrite (symmetrical to FileRead). Well, or removal of FileRead, FileAppend. Commands set should be consistent: writes mirror reads.
Code: Select all
d:=fileopen(_s, "W"), n:=d.rawwrite(b), d.close()
Code: Select all
n:=FileWrite(b, "Raw")
30. When we assign a "text" to a gc control, AHK automatically measures its extents and resizes it accordingly. It would be logical when changing text style/etc to have the same auto-functionality. And ability to disable any autosizing at all like setting or not setting "AutoSize" attribute for gui.show().
Example (I used a snippet from a singlet class I use for miscellaneous gui utility functions). Uncomment 4th line:
Code: Select all
Button.AutoSize=true;
Button.AutoSizeMode=GrowAndShrink;
Button.TextAlign=Left;
Button.Padding=new Padding(0, 0, 0, 0);
31. Continuation of the previous idea.
gui.add("text","xp xs+3 w45 cWhite") syntax might be handy for simple gui control layouts, but for complex ones one might wanna populate gui controls programmatically.
So having additional commands changing gc style similar to recently added gc.getpos(x,y,w,h) is reasonably necessary: gc.setpos(), gc.settextstyle( [bold/italic/other flags,size, kerning/etc]), gc.setcolor(color [,background]), gc.autosize, gc.settextalign(), gc.padding, gc.marginx, gc.marginy, etc.
IMHO, both "text string of params" and "set of methods" approaches could co-exist for a while until the former to be phased out.
32. Concerning another AHK's quirk mitigation: runtime checkup for a passed byref argument not being a variable. There is an existing IsByRef function already, but manually providing a checkup of every passed argument for being a not valid "expression" like obj.property is a pain.
Having an ability to enable/disable that check for all the byref arguments globally with 1 #warn directive would be great. If it doesn't dramatically hurt runtime performance ofc.
33. this one is a general observation concerning non-preprocessor directives system: some in form of #command, others in form of a_variable. No uniformity here.
Code: Select all
#SingleInstance force
#KeyHistory 0
a_listlines:=0, a_iconhidden:=1, a_fileencoding:="UTF-8"
;...
34.
35. Additional debug info. To let a script know of how much time (in milliseconds) pre-processing took. It might be A_LoadingTime built-in variable denoting a tick count of reaching WinMain() of AHK interpreter and preprocessor start, A_RunTime denoting a tick count of the beginning of script's run-time.
Code: Select all
msgbox("preprocessing took " A_LoadingTime-A_RunTime " ms")
36. Alternative to A_TickCount returning microseconds. A_TickCountExt? A_McsCount?
Code: Select all
dllcall('QueryPerformanceCounter', 'int64P',c:=0)
Code: Select all
ticks2mcs(_t){ ;perf counter ticks to mcs (microseconds)
;The freq of the perf counter is fixed at system boot and is consistent across all processors
; We query it upon app init and cache
static f:=0
;If the installed hardware supports a high-resolution perf counter, the retval is nonzero
(f) || dllcall("QueryPerformanceFrequency", 'int64P',f)
;To guard against loss-of-precision, we convert to microseconds *before* dividing by ticks-per-second:
return(_t*1000000/f)
}
37. object.property use for dllcall.
a) working:
Code: Select all
dllcall("some_dll_function", 'ptr',obj.p:=0)
Code: Select all
dllcall("another_dll_function_receiving_pointer_to_a_pointer", 'ptrP',obj.p:=0)
Could we have some preprocessor's latent propagation of additional commands: allocating a temporary_variable for .property and committing obj.p:=temporary_variable right after the function call:
Code: Select all
obj.p:=0
dllcall("dll_function", 'ptrp',obj.p)
Code: Select all
obj.p:=0
dllcall("dll_function", 'ptrp',tmpN)
obj.p:=tmpN
38. a namespace management syntax ideas
PHP-like: declaring globals inside the included file as belonging to namespace utils:
Code: Select all
namespace utils{
#include utils.ahk2
}
And the script code located outside utils.ahk2 to address those vars/classes/functions as utils.name (utils:name, etc)
Python-like:
Code: Select all
#from utils.ahk2 #import function_name #as fn
That would greatly help to resolve global var naming conflicts.
39. #requires
I can't apprehend what syntax is required to make some special script version run ONLY with the exact alpha version. I tried
Code: Select all
#Requires AutoHotkey v2.0-a103
Request: ability to set a range, e.g. v2.0-a103, v2.0-a107 with comma or just whitespace between version numbers. E.g.:
Code: Select all
#requires AutoHotkey v2 ;doesnt work with AHK V1 or AHK V3, tries to run with any AHK V2 version
#requires AutoHotkey v2-a116 ;works with this exact AHK V2 version ONLY.
#requires AutoHotkey v2-a116 V2-a119 ;expects only this range of AHK versions.
40. Enumerators. One-parameter mode for array returns a value yet for map it returns a key.
NB: the majority seems to be against this change. But its my wishlist so I keep my reasoning here:
Code: Select all
a:=["aa","bb"]
b:=map("a","aa","b","bb")
for v in a
msgbox(v)
for v in b
msgbox(v)
Was it made on purpose to differentiate array from map? In my opinion, both have similar access operator [] and this inconsistency with one-parameter for-loop might cause many unexpected issues. We just need a unified (expected) rule here.
So my try to bring it to consistency is to propose a 1-var for-loop to return a key (essentially only changing for k in array implementation (but not for k,v in array or both for-loop for map), it forces to use 2-vars instead of 1 for old for k in array behavior). It would make things plain simple and logical: 1-var for-loop returns keys always, 2-var for-loop returns keys,values.
The majority of script writers uses 2-vars mode anyways so the transition might be not so painful. It may be too late to change this behavior, or may be not - we have this opportunity while being in an alpha stage. Such quirks will be an eternal source of frustration.
alternative opinion (to let it as is):
@swagfag: it doesnt matter that its not consistent with Map - it doesn't have to be; a Map is a Map and an Array is an Array, theyre different data structures. In the future if more data structures are added, id want them to enumerate in a natural way that makes sense for them, not to satisfy an arbitrary constraint.
41. Variadic function/method vs (essentially) variadic syntax __call meta-function.
__call(_a*) returns array of argument(s) nested in another array (unexpected).
An agreement required: for all the functions/methods to have variadic syntax if they return a variable number of parameters. Also that
requires that old and future built-in functions to have variadic params* to be the last in the parameters list.
42.
I experience an issue of not being able to exit the misbehaving script on a repeating continuable error "Target control not found". Try to continue anyway? I press No. It exits the thread then creates it anew and the same error pops up again and again... I'll add an example later.Sometimes continuing to the next line is adequate, or just saves time during debugging (vs. editing and restarting the script to bypass a trivial error).
Request: To add a third button (LOL) to exit all the threads and terminate the application.
43. Similar to #42, a button to unconditionally exit a parsing process (terminate AHK session) after the first warning dialogue at loading time. Quite often, the script writer just wants to stop repeated warnings and get back to editing.
44.
And it throws:To stop a file that is currently playing, use SoundPlay on a nonexistent filename as in this example: SoundPlay "Nonexistent.avi".
Code: Select all
soundplay("-1")
Code: Select all
try soundplay("-1")
catch e{
}
Now that AHK discerns strings from pure numbers, it could be a pure number 0 (NULL, FALSE) as a soundplay first parameter to stop playing.
Also, I believe we might have another command to simply stop playing w/o throwing. soundstop() or similar.
@swagfag proposed a custom function:
Code: Select all
soundstop()=>dllcall('winmm\mciSendString', 'str','stop AHK_PlayMe', 'ptr',0, 'uint',0, 'ptr',0)
45. Having new load-time warning This var appears to never be assigned a value is absolutely amazing, but what about output variables like x,y,w,h in getpos(x,y,w,h)
One has to initialize them: getpos(x:=0,y:=0,w:=0,h:=0)
but these variables r used to retrieve values only and initialization is redundant here.
A preposition to add additional syntax keyword to declare output variables so as not to be checked for initialization/assignment.
thx to @iseahound, C# syntax looks pretty much relevant here: getpos(out x, out y, out w, out h)
My proposal is not to make out keyword mandatory, but to give a script writer 3 choices:C# out and ref keywords wrote: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/out-parameter-modifier
The out keyword causes arguments to be passed by reference. It is like the ref keyword, except that ref requires that the variable be initialized before it is passed.
1. disable all warnings with #warn and have some output variables unassigned.
2. explicitly assign some values to them, e.g.: getpos(x:=0, y:=0)
3. do not assign any values but declare them as output variables: getpos(out x, out y)
Also it helps to memorize parameters' order.
@swagfag: If adding extra special operators(eg * ^ & out) would simplify the implementation/provide more robust error detection, go for it.
at the very least it would serve to better document outparam usage
46. Revolt! Revolt! the majority wants command syntax to be completely removed. Some ppl do like to use function calls w/o (). How about a compromise: #nocommands (#commands off, #syntax strict, whatever) pre-processor directive to disable command syntax (forcing to use () for function calls) in the script being parsed so that f (37,38) lines would finally trigger a warning or error.
Basically the major issue of command and function syntax co-existence is the general expectation that follows other languages (THE MAJORITY of them) design rule: a variable/object member's name with () following it executes the function/method with that name, a name w/o () is treated as a variable/object/pointer. And I can't help it - I keep shooting at my foot.
Code: Select all
f 1 ;function's call
f ? 1:0 ;variable's access
(f 1) ;variable's access
f(*)=>msgbox()
47. abbreviations for dllcall parameters types. Similar to AHK_H V2 branch (by @HotKeyIt) with DllCall's equivalent named DynaCall.
Code: Select all
Int i
Str s
AStr a
WStr w
Short h
Char c
Float f
Double d
PTR p
Int64 i64
PTRP pp, etc
example:
Code: Select all
gp_ellipsefill(g,b, x,y,w,h)=>dllcall("gdiplus\GdipFillEllipse", 'ptr',g, 'ptr',b, 'float',x, 'float',y, 'float',w, 'float',h)
Code: Select all
gp_ellipsefill(g,b, x,y,w,h)=>dllcall("gdiplus\GdipFillEllipse", 'p',g, 'p',b, 'f',x, 'f',y, 'f',w, 'f',h)
48. (bug) Some control flow statements don't allow a space between a command and its expression: try(f.type) ;syntax error vs if(g) ;OK
Another inconsistency with control flow statements: if (expr)=if(expr), but return (expr) is not the same as return(expr).
Code: Select all
f:=1
if(f){ ;OK
}
if (f){ ;OK
}
e()
e(){
f:=1
return(f) ;call to non-existent function (unexpected)
;return (f) ;OK
}
- Some coding guidelines consider using braces a bad practice, yet other stylistic conventions require them.
- Whitespace (or space(s)/tab(s) at least) between non-literal symbols is optional.
- Originally old convention C had parenthesis mandatory for returned values.
- Parenthesis can be used to define return as a macro and then insert some logging code to trace all the return () statements.
back to AHK V2: we've got this inconsistency:
Code: Select all
try f ;f considered as a function call
return f ;f considered as a variable
49.
https://lexikos.github.io/v2/docs/commands/Trim.htm
Quote: Any string value or variable. Numbers are not supported.
Code: Select all
s:=7700.4 ;type=float
msgbox(ltrim(s,"7"))
Code: Select all
00.3999999999996 ;type=string
Either the documentation needs to be updated to mention that behavior
Any string value or variable. Numbers are not supported. -> Any string/numeric value or variable.
or the command trim|ltrim|rtrim to be changed to throw ("expected a number but got a string" or something like that).
50.
Debugging facility. To add ObjRefs(obj) returning object's reference-counting to replace this undocumented object's data use with a built-in function:
Code: Select all
objrefs(_o)=>isobject(_o) ? numget(objptr(_o), 8, 'int64'):0 ;get obj ref counter
update notes
1. Please, always specify full function/method/property names b/c it has to be searchable:
Merged ControlGet/SetTab into ControlGet/ChooseIndex.
->
Merged ControlGetTab/ControlSetTab into ControlGetIndex/ControlChooseIndex.
Changed Group(De)Activate/Close to throw
->
Changed GroupActivate/GroupDeactivate/Close to throw
2. Implemented #Requires is skipped for v2-113 notes.

3. I feel like people r reluctant to upgrade. I know, for the developers, its hard to keep in mind and cover all the stuff in the update notes, but a workaround/replacement for removed/restricted/changed features should be provided whenever possible. E.g.: "for //, //= old behavior use floor(a/b)"
Having instructions of what exactly has to be replaced with what exactly in order to upgrade from previous version(s) would greatly motivate people upgrading.
--------------------------
documentation
There r lots of typos, I lost some notes so I recall only a few for now:
6.
https://lexikos.github.io/v2/docs/objects/Buffer.htm
Kinda redundant phrase

ClipboardAll returns a sub-type of Buffer, also named ClipboardAll.
7.
https://lexikos.github.io/v2/docs/objects/Map.htm
Recent built-in objects and functions definitely require more simple examples of usage in the documentation, like this one for Map (example by @Helgef):
Code: Select all
;create a map with a customizable default value:
class m extends map {
__new(default := 0) => this.default := default
__item[key] => this.has(key) ? base[key] : this.default
}
8. https://lexikos.github.io/v2/docs/Functions.htm#Local
Some "pay attention" warning in the documentation concerning local, global, static commands in conjunction with chaining assignments.
local a:=0, b:=0, c:=0 and local a:=b:=0, c:=0. The latter is not declaring all the 3 vars as local. Sometimes its easy to overlook...
11. obj.HasProp: returns true if the value has a property by this name, otherwise false
https://lexikos.github.io/v2/docs/objects/Any.htm#HasProp
obj.HasOwnProp: returns true if the object owns a property by this name, otherwise false.
https://lexikos.github.io/v2/docs/objects/Object.htm#HasOwnProp
To add an explanation of the difference (by Helgef): HasOwnProp returns true if the object owns it, HasProp returns true if it or one of its bases owns it.
12. https://lexikos.github.io/v2/docs/Objects.htm#ObjPtr
The current object structure detailed explanation (offset/description) that might be very useful for ones trying to figure out how to organize an interface between compiled code and AHK objects.
Code: Select all
0 vTbl pointer
8 mRefCount
;...
