IsVarRef for optional ByRef function parameters

Propose new features and changes
just me
Posts: 9542
Joined: 02 Oct 2013, 08:51
Location: Germany

IsVarRef for optional ByRef function parameters

Post by just me » 29 Aug 2023, 06:38

I'd like to have a function to detect whether a VarRef has been passed to an optional ByRef parameter corresponding to IsByRef() in v1.1.

Background:
lexikos wrote:
26 Aug 2023, 22:05
...
just me wrote:BTW: How can I check whether a VarRef has been passed for an optional ByRef parameter?
You can't.
...

Descolada
Posts: 1183
Joined: 23 Dec 2021, 02:30

Re: IsVarRef for optional ByRef function parameters

Post by Descolada » 29 Aug 2023, 07:28

There are already at least two ways to do that:

Code: Select all

var := "Hello"
F(&var)
F()
G(&var)
G()

F(&opt?) {
    MsgBox(IsSet(opt) ? "Passed: " opt : "No VarRef passed")
}

G(opt:="Default") {
    MsgBox(opt is VarRef ? "Passed: " %opt% : "No VarRef passed")
}
Is there some sort of a specific situation requiring such a function?

Edit: also, if you don't mind undocumented "features":

Code: Select all

var := "Hello"
H(&var)
H(&opt1:="Default", &opt2:="Hello") {
    MsgBox("opt1 is " (IsByRef(&opt1) ? "ByRef" : "Default value"))
    MsgBox("opt2 is " (IsByRef(&opt2) ? "ByRef" : "Default value"))
}

IsByRef(&var) => !(NumGet(ObjPtr(&var) + (A_PtrSize = 4 ? 28 : 48)+2, "uchar") & 0x40)
Edit2: note that this "IsByRef" function *actually* checks whether the variable has been declared or was an optional parameter. This means that a local variable of H() will also be considered "ByRef", and if a VarRef was passed to H() from another function where it was a default value then it'd also be considered "Default value". See more @ lexikos' post #p536913.
Last edited by Descolada on 01 Sep 2023, 00:05, edited 1 time in total.

just me
Posts: 9542
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: IsVarRef for optional ByRef function parameters

Post by just me » 30 Aug 2023, 10:08

@Descolada,

please try the following:

Code: Select all

; var := "Hello"
F(&var)
F()
G(&var)
G()

F(&opt?) {
    MsgBox(IsSet(opt) ? "Passed: " opt : "No VarRef passed")
}

G(opt:="Default") {
    MsgBox(opt is VarRef ? "Passed: " %opt% : "No VarRef passed")
}
It's possible to pass VarRefs of uninitialized variables to the built-in functions. In a function with optional parameters defined like &Param? I want to check whether a VarRef (even if uninitialized) has actually been passed to avoid unnecessary actions.

Also, I don't want to use undocumented implementation details.

Descolada
Posts: 1183
Joined: 23 Dec 2021, 02:30

Re: IsVarRef for optional ByRef function parameters

Post by Descolada » 30 Aug 2023, 10:40

Valid point, I totally forgot that an unset VarRef could be passed ;) I blame it on me using too much of DllCall, since it doesn't allow uninitialized VarRefs and I got so used to initializing them: DllCall("QueryPerformanceFrequency", "Int64*", &freq) throws an unset variable error.

Though I mean you can still account for such situations by combining the two methods, though it gets clunky and adds a lot of input validation:

Code: Select all

F(&var)
F()

F(opt?) {
    MsgBox(IsSet(opt) && opt is VarRef ? "Passed VarRef" : "No VarRef passed")
}
Also it doesn't make it clear from reading F function definition that opt should be a VarRef. So the easier way would be to force VarRef initialization in the same manner as DllCall does... I concede that in such niche cases IsVarRef could be useful.

lexikos
Posts: 9665
Joined: 30 Sep 2013, 04:07
Contact:

Re: IsVarRef for optional ByRef function parameters

Post by lexikos » 31 Aug 2023, 22:18

https://www.autohotkey.com/boards/viewtopic.php?f=37&t=120994

@Descolada I don't know why you thought checking for the VAR_DECLARED scope flag would be a solution. If the caller passes a reference to any declared variable, your function will tell you that they did not pass a reference.
Also it doesn't make it clear from reading F function definition that opt should be a VarRef.
What about functions where opt should be a Map, or an Integer, or a String, or... ? This and the type constraint with automatic error reporting are very limited benefits of &ByRef. Even if you think these benefits are important, it is not a good reason to further extend the redundant functionality of ByRef. It would be better as motivation to implement more general type constraints, such as what I hinted at with typed properties, or a more generally applicable method of making an alias with the same benefits as ByRef but without the function call (e.g. defining a block wherein X becomes an alias for %Y%).

Note: thqby's VS Code extension supports TypeScript-style parameter annotations, which affect auto-completion, parameter info, and warnings produced by static analysis.

Descolada
Posts: 1183
Joined: 23 Dec 2021, 02:30

Re: IsVarRef for optional ByRef function parameters

Post by Descolada » 01 Sep 2023, 00:26

@lexikos, I didn't think it was a proper solution (if it being undocumented isn't bad enough...), it simply solves the immediate issue pointed out by user just me. I forgot to add all the warnings and pitfalls associated with that method and have hence addended my original post accordingly.
What about functions where opt should be a Map, or an Integer, or a String, or... ?
Yes, optional type constraints would be a much better alternative solution and I hope one day to see it in AHK. I do use thqby's extension and find it very useful, but haven't noticed any warnings from static analysis. That is, the following example doesn't produce problems/warnings in my setup:

Code: Select all

G(var:=0)

/**
 * VarRef type test
 * @param {VarRef} opt 
 */
G(opt) {
    MsgBox(opt is VarRef ? "Passed: " %opt% : "No VarRef passed")
}

lexikos
Posts: 9665
Joined: 30 Sep 2013, 04:07
Contact:

Re: IsVarRef for optional ByRef function parameters

Post by lexikos » 01 Sep 2023, 00:38

An incorrect assumption on my part. Still, it could produce warnings. This produces a warning:

Code: Select all

FF(&a) => 0
FF(123)
This does:

Code: Select all

class X {
    static M(&a) => 0
}
X.M(123)
This does not:

Code: Select all

class X {
    M(&a) => 0
}
/* @param {X} z
*/
F(y) => y.M(123)
... even though the parameter info for y.M() shows (X) M(&a).

I didn't mean to say that the type annotation would produce warnings specifically based on type-checking, but I did assume it would affect warnings like the above, where the type annotation allows it to determine the usage of the method.

It is perhaps an opportunity for improvement, though maybe @thqby has a reason for not implementing it.

User avatar
thqby
Posts: 431
Joined: 16 Apr 2021, 11:18
Contact:

Re: IsVarRef for optional ByRef function parameters

Post by thqby » 01 Sep 2023, 08:40

Static analysis of the source code is necessary to provide features such as intellisense, but the type annotations in comments will only be obtained when necessary, because many comments do not contain this information, and it is a bit wasteful to analyze them, so as a hint only.

lexikos
Posts: 9665
Joined: 30 Sep 2013, 04:07
Contact:

Re: IsVarRef for optional ByRef function parameters

Post by lexikos » 01 Sep 2023, 23:40

Better to waste CPU time than the developer's time. But anyway, I guess what you're saying is that the type annotations are only parsed when the editor is about to display the parameter info?

Post Reply

Return to “Wish List”