334

I was told today that it's possible to invoke a function without parentheses. The only ways I could think of was using functions like apply or call.

f.apply(this);
f.call(this);

But these require parentheses on apply and call leaving us at square one. I also considered the idea of passing the function to some sort of event handler such as setTimeout:

setTimeout(f, 500);

But then the question becomes "how do you invoke setTimeout without parentheses?"

So what's the solution to this riddle? How can you invoke a function in Javascript without using parentheses?

Mike Cluck
  • 31,869
  • 13
  • 80
  • 91
  • 65
    Not trying to be rude, but I must ask: Why? – jehna1 Mar 11 '16 at 20:45
  • 40
    @jehna1 Because I was [answering a question on how functions are invoked](http://stackoverflow.com/a/35947776/371184) and was corrected when I said that they required parentheses on the end. – Mike Cluck Mar 11 '16 at 20:46
  • 4
    Amazing! I didn't imagine this question would have that much traction.. Perhaps I should have asked it myself :-) – Amit Mar 12 '16 at 06:17
  • 2
    As a practical case, one could imagine being able to inject javascript into some context but they filtered out the `()` characters. – Yakk - Adam Nevraumont Mar 13 '16 at 13:16
  • 6
    This is the kind of question that breaks my heart. What good could possibly come through such abuse and exploitation? I want to know one valid use case where `` is objectively better or more useful than `f()`. – Mulan Mar 13 '16 at 19:37
  • @jehna1 Because the OP wants Javascript to look like VB6 / VBA / VB.NET? :) – Zev Spitz Mar 14 '16 at 07:58
  • 1
    Keyboard with broken `(` and `)` keys probably.. or a Lisp hater! – Déjà vu Mar 14 '16 at 09:13
  • 3
    @naomik There's no valid reason. This whole thread is a "riddle" (and an unhelpful, misleading one IMO). – Aaron Beall Mar 14 '16 at 15:46
  • 6
    @Aaron: you say there’s no valid reason, but, as [Alexander O’Mara’s answer](http://stackoverflow.com/a/35982227/4847772) points out, one may be considering the question whether removal of brackets is sufficient to prevent code execution — or what about an obfuscated Javascript competition? Of course it is an absurd objective when trying to write maintainable code, but it is an amusing quesetion. – PJTraill Mar 16 '16 at 15:23
  • 3
    This question was very interesting in researching usage and prevention of XSS techniques. – rob Jan 22 '18 at 21:24
  • 3
    @Aaron, how could you possibly think you know that there's no valid reason? It seems to me that all you can know is that *you* couldn't think of one. Not surprisingly, your assertion that there's no valid reason is false; see rob's comment about XSS techniques, and the conclusion of Alexander O'Mara's answer. Knowing what's possible and what's not is very important to both attackers and defenders in security. – Don Hatch Mar 24 '18 at 02:45
  • 1
    There's no "valid" reason, meaning reasons this might happen are abusive and exploitative or accidental. I maintain that's true, but "valid" was probably a poor word choice: I certainly stand corrected that there are valid reasons to *know* the answer to this riddle! – Aaron Beall Mar 26 '18 at 15:42

10 Answers10

537

There are several different ways to call a function without parentheses.

Let's assume you have this function defined:

function greet() {
    console.log('hello');
}

Then here follow some ways to call greet without parentheses:

1. As Constructor

With new you can invoke a function without parentheses:

new greet; // parentheses are optional in this construct.

From MDN on the new oprator:

Syntax

new constructor[([arguments])]

2. As toString or valueOf Implementation

toString and valueOf are special methods: they get called implicitly when a conversion is necessary:

var obj = {
    toString: function() {
         return 'hello';
    }
}

'' + obj; // concatenation forces cast to string and call to toString.

You could (ab)use this pattern to call greet without parentheses:

'' + { toString: greet };

Or with valueOf:

+{ valueOf: greet };

valueOf and toString are in fact called from the @@toPrimitive method (since ES6), and so you can also implement that method:

+{ [Symbol.toPrimitive]: greet }
"" + { [Symbol.toPrimitive]: greet }

2.b Overriding valueOf in Function Prototype

You could take the previous idea to override the valueOf method on the Function prototype:

Function.prototype.valueOf = function() {
    this.call(this);
    // Optional improvement: avoid `NaN` issues when used in expressions.
    return 0; 
};

Once you have done that, you can write:

+greet;

And although there are parentheses involved down the line, the actual triggering invocation has no parentheses. See more about this in the blog "Calling methods in JavaScript, without really calling them"

3. As Generator

You could define a generator function (with *), which returns an iterator. You can call it using the spread syntax or with the for...of syntax.

First we need a generator variant of the original greet function:

function* greet_gen() {
    console.log('hello');
}

And then we call it without parentheses by defining the @@iterator method:

[...{ [Symbol.iterator]: greet_gen }];

Normally generators would have a yield keyword somewhere, but it is not needed for the function to get called.

The last statement invokes the function, but that could also be done with destructuring:

[,] = { [Symbol.iterator]: greet_gen };

or a for ... of construct, but it has parentheses of its own:

for ({} of { [Symbol.iterator]: greet_gen });

Note that you can do the above with the original greet function as well, but it will trigger an exception in the process, after greet has been executed (tested on FF and Chrome). You could manage the exception with a try...catch block.

4. As Getter

@jehna1 has a full answer on this, so give him credit. Here is a way to call a function parentheses-less on the global scope, avoiding the deprecated __defineGetter__ method. It uses Object.defineProperty instead.

We need to create a variant of the original greet function for this:

Object.defineProperty(globalThis, 'greet_get', { get: greet });

And then:

greet_get;

You could call the original greet function without leaving a trace on the global object like this:

Object.defineProperty({}, 'greet', { get: greet }).greet;

But one could argue we do have parentheses here (although they are not involved in the actual invocation).

5. As Tag Function

Since ES6 you can call a function passing it a template literal with this syntax:

greet``;

See "Tagged Template Literals".

6. As Proxy Handler

Since ES6, you can define a proxy:

var proxy = new Proxy({}, { get: greet } );

And then reading any property value will invoke greet:

proxy._; // even if property not defined, it still triggers greet

There are many variations of this. One more example:

var proxy = new Proxy({}, { has: greet } );

1 in proxy; // triggers greet

7. As instance checker

The instanceof operator executes the @@hasInstance method on the second operand, when defined:

1 instanceof { [Symbol.hasInstance]: greet } // triggers greet
trincot
  • 317,000
  • 35
  • 244
  • 286
  • 1
    That's a very interesting approach using `valueOf`. – Mike Cluck Mar 11 '16 at 21:55
  • Interesting. You can also do: `Function.prototype.toString=function(){ this.call(this); return "" };`, after which you can also call the function with `+func` – jehna1 Mar 12 '16 at 00:33
  • Yep, same principle. – trincot Mar 12 '16 at 00:35
  • 7
    You forgot about ES6: ```func``;``` – Ismael Miguel Mar 12 '16 at 15:23
  • Thanks, @IsmaelMiguel, I have added a paragraph about that. I think the list is hardly complete... – trincot Mar 12 '16 at 15:38
  • You're welcome. Something weird you can do is nest calls: ```func1`${func2`${func3`...`}`}`;``` The list is hardly complete, but it is the best one so far. – Ismael Miguel Mar 12 '16 at 15:41
  • As I understand the question, the goal is to call a *specific* (*existing*) function `f`. Many of your approaches can be used for that, but not all; and all of them need to be written differently to avoid using parentheses. For example, the `toString` approach could be written as `var greet = { toString: f }; greet + '';` -- or, more tersely, as `'' + { toString : f }`. – ruakh Mar 13 '16 at 16:56
  • @ruakh, thank you for your comment! the question is indeed open for interpretation in that respect. Anyway I updated my answer a bit taking your comments into account. Thanks! – trincot Mar 13 '16 at 18:13
  • Hey, what about the evil eval? `eval("setTimeout< function <> { console.log('hello') }, 200 >".replace(//g , ")"))` – higuaro Mar 16 '16 at 13:57
  • 1
    You used brackets by calling eval :) – trincot Mar 16 '16 at 13:58
  • @trincot Not anymore: ``eval`${"setTimeout< function <> { console.log('hello') }, 200 >".replace(//g , ")")}`;`` – Ismael Miguel Mar 19 '16 at 20:01
  • Yes, but then why use `eval`? The solution you use now is in the template literal, not in using `eval`. Just put the target function in its place: ``greet` `; `` and you're done (point 5 in my answer). – trincot Mar 19 '16 at 20:47
  • Would mentioning the function without parentheses in a return statement also count? E.g. `return hello;` – Paul Ogilvie Apr 09 '17 at 09:36
  • 1
    In that case the function is not executed, but passed to the caller. – trincot Apr 09 '17 at 10:27
  • 3
    @trincot Another method will be possible soon with [pipeline operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Pipeline_operator): `'1' |> alert` – Artur Mar 11 '18 at 11:03
  • Interesting, @reski! Curious if that will make it to a future ES release. – trincot Mar 11 '18 at 11:05
  • this thread is incredible, I was super confused when I saw the func``; syntax for the first time – arispen Sep 11 '18 at 10:32
  • I like this a lot "Object.defineProperty({}, 'greet', { get: greet }).greet;" but is it possible to define a new statement so I could write "greet 'myself'" and get "Hello myself" as a result? a property does not have arguments.. but maybe there is another way – Zibri Feb 17 '19 at 15:47
  • @Zibri, that is possible with a template tag function: ``greet`myself`;`` – trincot Feb 17 '19 at 15:49
  • I know but I was asking a different thing... like "greet myself" without ` – Zibri Feb 17 '19 at 15:54
  • @Zibri, not like that, but one can imagine assigning "myself" to a property/variable and that the `greet` function would be designed to read it. – trincot Feb 17 '19 at 15:58
  • I could even do it in this way: Object.defineProperty(window, 'newcmd', { set: (arg)=>console.log(arg) }) newcmd = "hello" – Zibri Feb 17 '19 at 15:59
  • what I mean is something like the "let" statement. – Zibri Feb 17 '19 at 16:02
  • If you find something interesting in that way, post it as an answer ;-) Or ask a new question about it. – trincot Feb 17 '19 at 16:06
  • I was trying to understand how the `styled` function in emotion https://emotion.sh/docs/introduction was implemented. your answer helped me find the tagged templates. Thanks! – Michael Nov 03 '20 at 20:36
  • In what version of JavaScript did they make it so that you can instantiate an object using the "new" operator without parentheses? MDN makes no mention of it. – PHP Guru Nov 16 '20 at 01:34
  • @PHPGuru, it has always been like that. See for example the [EcmaScript 3](https://www-archive.mozilla.org/js/language/E262-3.pdf) specification in section 11.2.2, where the expression after `new` can evaluate to a constructor. MDN shows this in its syntax description (square brackets mean that something is optional). – trincot Nov 16 '20 at 07:23
  • @trincot Thank you for linking to a source. I had been trying to find one that shows support dating back to the early days of JavaScript, and that one is perfect. – PHP Guru Nov 17 '20 at 18:01
  • fwiw, [Prisma](https://www.prisma.io/) uses the aforementioned *Tagged Template Literals* for functions like `$queryRaw()`. This clever approach enables Prisma to "[create] prepared statements that are safe from SQL injections". ([Prisma Docs](https://www.prisma.io/docs/concepts/components/prisma-client/raw-database-access#queryraw)) – rinogo Dec 02 '21 at 20:58
227

The easiest way to do that is with the new operator:

function f() {
  alert('hello');
}

new f;

While that's unorthodox and unnatural, it works and is perfectly legal.

The new operator doesn't require parentheses if no parameters are used.

Amit
  • 45,440
  • 9
  • 78
  • 110
  • 6
    Correction, the easiest way to do this is to prepend an operand that forces the function expression to be evaluated. `!f` results in the same thing, without instantiating an instance of the constructor function (which requires creating a new this context). It's much more performant and is used in many popular libraries (like Bootstrap). – THEtheChad Mar 13 '16 at 05:11
  • 7
    @THEtheChad - evaluating an expression is not the same as executing a function, but if you have a different (nicer? more surprising?) method of invoking (causing execution) of a function - post an answer! – Amit Mar 13 '16 at 05:43
  • The point of prepending an exclamation mark, or any other single character unary operator (`+`, `-`, `~`) in such libraries, is to save a byte in the minimized version of the library where the return value isn't needed. (i.e. `!function(){}()`) The usual self-calling syntax is `(function(){})()` (unfortunately the syntax `function(){}()` isn't cross-browser) Since the top-most self-calling function usually has nothing to return, it's usually the target of this byte saving technique – Ultimater Nov 01 '16 at 06:05
  • 3
    @THEtheChad Does this hold true? I just tried it in Chrome, and wasn't getting function execution. `function foo() { alert("Foo!") };` For example: `!foo` returns `false` – Glenn 'devalias' Grant Mar 30 '17 at 00:11
106

You can use getters and setters.

var h = {
  get ello () {
    alert("World");
  }
}

Run this script just with:

h.ello  // Fires up alert "world"

Edit:

We can even do arguments!

var h = {
  set ello (what) {
    alert("Hello " + what);
  }
}

h.ello = "world" // Fires up alert "Hello world"

Edit 2:

You can also define global functions that can be run without parenthesis:

window.__defineGetter__("hello", function() { alert("world"); });
hello;  // Fires up alert "world"

And with arguments:

window.__defineSetter__("hello", function(what) { alert("Hello " + what); });
hello = "world";  // Fires up alert "Hello world"

Disclaimer:

As @MonkeyZeus stated: Never ever shall you use this piece of code in production, no matter how good your intentions.

jehna1
  • 3,110
  • 1
  • 19
  • 29
  • 9
    Strictly speaking from the POV of "I'm an axe wielding mad-man that has the honor of taking over your code because you've moved on to bigger and better things; yet I know where you live". I really hope this isn't normal lol. Using it inside of a plug-in which you maintain, acceptable. Writing business-logic code, please hold while I sharpen my axe :) – MonkeyZeus Mar 11 '16 at 22:15
  • 3
    @MonkeyZeus haha, yes! Added a disclaimer for coders of the future that can find this from Google – jehna1 Mar 11 '16 at 22:23
  • Actually this disclaimer is too harsh. There are legitimate use cases for "getter as a function call". In fact it's used extensively in a very successful library. (would you like a hint?? :-) – Amit Mar 12 '16 at 23:48
  • 1
    Note that `__defineGetter__` is deprecated. Use `Object.defineProperty` instead. – Felix Kling Mar 13 '16 at 19:33
  • @Amit According to my comment, using it in your library is acceptable. I presume it's not used in business-logic code which can easily be passed off to less skilled programmers as attrition and re-hiring occurs? – MonkeyZeus Mar 14 '16 at 14:37
  • 2
    @MonkeyZeus - as I *explicitly* wrote in the comment - this style of function invocation *is being used*, extensively & intentionally, in a very successful public library as the API itself, not internally. – Amit Mar 14 '16 at 23:00
  • @Amit Which one? – Ruben Martinez Jr. Sep 01 '17 at 13:20
  • The disclaimer almost look like a That looks like a Tagged Template Literal use case – Jeffrey Nicholson Carré Mar 01 '23 at 03:49
26

Here's an example for a particular situation:

window.onload = funcRef;

Although that statement is not actually invoking but will lead to a future invocation.

But, I figure grey-areas might be ok for riddles like this :)

Jonathan.Brink
  • 23,757
  • 20
  • 73
  • 115
  • 6
    That's nice but that's not JavaScript, it's DOM. – Amit Mar 11 '16 at 20:55
  • 6
    @Amit, the DOM is part of the browser's JS environment, which is still JavaScript. – zzzzBov Mar 11 '16 at 22:20
  • 3
    @zzzzBov Not all ECMAScript runs in a web browser. Or are you among the people who use "JavaScript" to refer specifically to combining ES with the HTML DOM, as opposed to Node? – Damian Yerrick Mar 12 '16 at 05:10
  • 10
    @DamianYerrick, I consider the DOM to be a library available in some JS environments, which doesn't make it any less JavaScript than underscore being available in some environments. It's still JavaScript, it's just not a part that's specced in the ECMAScript specification. – zzzzBov Mar 12 '16 at 22:05
  • 3
    @zzzzBov, ["Although the DOM is often accessed using JavaScript, it is not a part of the JavaScript language. It can also be accessed by other languages."](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model). For example, FireFox uses XPIDL and XPCOM for DOM implementation. It is not so self-evident that the call of your specified callback function is implemented in JavaScript. The DOM is not an API like other JavaScript libraries. – trincot Mar 13 '16 at 10:08
  • 1
    @trincot, I agree with everything up to "The DOM is not an API like other JavaScript libraries" because that's wrong. Even the title of the page *you* linked to disproves it: "Document Object Model (DOM) - Web APIs | MDN" The DOM is an API, its implementation may not be in JavaScript, but that more than solidifies the point that this answer is making in that the function can be executed without requiring parenthesis. – zzzzBov Mar 13 '16 at 19:34
  • @zzzzBov, indeed the DOM is an API, I didn't claim it wasn't. But it is not an API ***like*** other JavaScript libraries, in the sense that it would be pure JavaScript. And yes, this *might* show that the function can be invoked without parentheses, it is just that it is the DOM that invokes it, while the OP asked *How can you invoke a function in Javascript*. But I will not insist, this is probably a case of varying interpretations. – trincot Mar 13 '16 at 19:45
  • 1
    @zzzzBov I guess you are saying you can invoke your JavaScript function from native code, that's a stretch;) It's a stretch to call the DOM JavaScript, it can call JavaScript, but it's not JavaScript. – Ruan Mendes Jul 24 '17 at 11:22
  • @JuanMendes, why is it a stretch to call the DOM API JavaScript? Is it as much of a stretch to call the `fs` node API JavaScript? It's an API...for JavaScript. If you only count functions listed in the ECMAScript spec as "JavaScript" you'll find yourself with a language that can't do much at all. – zzzzBov Jul 24 '17 at 13:35
  • that is a normal Object setter... and can be done easily Object.defineProperty(window, 'newcmd', { set: (arg)=>console.log(arg) }) newcmd = "hello" – Zibri Feb 17 '19 at 15:58
20

If we accept a lateral thinking approach, in a browser there are several API's we can abuse to execute arbitrary JavaScript, including calling a function, without any actual parenthesis characters.

1. location and javascript: protocol:

One such technique is to abuse the javascript: protocol on location assignment.

Working Example:

location='javascript:alert\x281\x29'

Although technically \x28 and \x29 are still parenthesis once the code is evaluated, the actual ( and ) character does not appear. The parentheses are escaped in a string of JavaScript which gets evaluated on assignment.


2. onerror and eval:

Similarly, depending on the browser we can abuse the global onerror, by setting it to eval, and throwing something that will stringify to valid JavaScript. This one is trickier, because browsers are inconsistent in this behavior, but here's an example for Chrome.

Working example for Chrome (not Firefox, others untested):

window.onerror=eval;Uncaught=0;throw';alert\x281\x29';

This works in Chrome because throw'test' will pass 'Uncaught test' as the first argument to onerror, which is almost valid JavaScript. If we instead do throw';test' it will pass 'Uncaught ;test'. Now we have valid JavaScript! Just define Uncaught, and replace test with the payload.


In conclusion:

Such code is truly awful, and should never be used, but is sometimes used in XSS attacks, so the moral of the story is don't rely on filtering parenthesis to prevent XSS. Using a CSP to prevent such code would also be a good idea.

Alexander O'Mara
  • 58,688
  • 18
  • 163
  • 171
  • Isn't this the same as using `eval` and writing the string without actually using the parentheses characters. – Zev Spitz Mar 14 '16 at 08:03
  • @ZenSpitz Yep, but `eval` normally requires parentheses, hence this filter evasion hack. – Alexander O'Mara Mar 14 '16 at 08:05
  • 6
    Arguably `\x28` and `\x29` are still parentheses. `'\x28' === '('`. – trincot Mar 14 '16 at 10:01
  • @trincot Which is kind-of the point I covered in the answer, this is a lateral thinking approach which showcases a reason for which in which this might be done. I've made it more clear in the answer. – Alexander O'Mara Mar 14 '16 at 17:27
  • Also, whoever voted to delete this, please review [the guidelines for deleting answers](http://stackoverflow.com/help/privileges/trusted-user). – Alexander O'Mara Mar 14 '16 at 17:54
  • Letting the URL decoder do its work allows to use an escape mechanism other than javascript string literal's, location='javascript:alert%281%29' – eel ghEEz May 10 '17 at 14:58
7

In ES6, you have what's called Tagged Template Literals.

For example:

function foo(val) {
    console.log(val);
}

foo`Tagged Template Literals`;
Idan Dagan
  • 10,273
  • 5
  • 34
  • 41
  • 5
    Indeed, this is in the answer I posted 1.5 years before yours... not sure why it deserves a new answer? – trincot Jan 19 '18 at 15:12
  • 4
    because some other people, who want to implement the same thing right now can use the new answer. – mehta-rohan Sep 05 '18 at 16:36
  • 8
    @mehta-rohan, I don't understand that comment. If this would make sense then why not duplicate the same answer over and over gain? I can't see how that would be useful. – trincot Oct 11 '18 at 15:16
3
Array.constructor`alert\x28"invoke with whatever u want"\x29```;

Because Array.constructor is a Function object. When Function objects are invoked, they return a function which it's body is the arguments it got.

Abra Cadabra
  • 141
  • 6
  • 1
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. You can find more information on how to write good answers in the help center: https://stackoverflow.com/help/how-to-answer . Good luck – nima Nov 03 '22 at 08:34
  • Why did you reference the `Function` object with `Array.constructor` and not as ... `Function`? – trincot May 03 '23 at 17:39
1

Here is another example where I am passing function one in then function without parantheses and it is called.

function one() {
  console.log("one called");
}
function two() {
  return new Promise((resolve, reject) => {
    resolve();
  });
}
two().then(one);
  • There are a lot of parentheses here... – trincot May 02 '23 at 16:20
  • Please check again, the one function passed as argument in then() method without parenthesis – Mohsin Sunasara May 03 '23 at 17:12
  • Yes, which confirms you need parentheses to get the function called. Note how the asker already proposed such constructs, but with `apply` and `call` and then writes *"But these require parentheses on apply and call leaving us at square one."*. So also your answer brings us to square one. – trincot May 03 '23 at 17:20
0

Updated answer You can invoke a function (run js code) without parenthesis, while passing parameters This is a bit of a hack, but it definitly works To do it, you need a class or Object.defineProperty. Here's the class example:

class RunJs {
    static set funcName(params){
         // Code to run. Note, to pass multiple params, use an array.
    }
}
// To run it
RunJS.funcName = ["param1", "param2", "etc"];

When you call the function, you are using no parenthesis, and it runs code and can take parameters.

For an object, use Object.defineProperty

const obj = {
    thisCanBeAnthing:"and this"
};
Object.defineProperty(obj, "thisCanBeAnything", {
    set(params){
        // code to run
    }
});
And to call it: 
obj.thisCanBeAnthing = "param"; // Or an array of parameters.

When you call it, no parenthesis are used.

Fighter178
  • 303
  • 2
  • 9
-1

You can use an anonymous function. It only works using pre-ES6 syntax, but still, it works.

Code:

//ES6+ (using const, still not using arrow functions)
const myFunc = (function(args){console.log("no parenthesis")})(args);

and invoke it like this:

myFunc;
// ^^^ no parenthesis in invocation, but you do have parenthesis in definition
// also note that the semicolon is optional if it is the only thing on the line

Before ES6:

var myNonES6Func = (function(args){console.log("Use var before ES6.")})(args);

and invoke it like this

myNonES6Func;
// ^^^ same as the ES6 invocation.

const es6_func = (function() {
    alert("ES6!")
})();
var before_es6_func = (function() {
    alert("Before ES6.")
})();
const es6 = () => {
    es6_func
}

function before_es6() {
    before_es6_func
}
<button onclick="es6">ES6+</button>
<button onclick="before_es6">Before ES6</button>
NOTE: Modern browsers do not seem to have this functionality anymore (it calls itself on page load, but when you called it normally, it would also work but you would have to add some counting code to keep it from running on page load) I did test it on Internet Explorer 11, and it seems to still work though, but Chrome, Firefox, and Edge do not work, it might've just been a bug in IE.**
Fighter178
  • 303
  • 2
  • 9
  • No, this doesn't work like you described, and never has. What you have there is an IIFE, and it runs **immediately** -- because of the parentheses! --, not when you put the name of the function as a separate statement. – trincot May 03 '23 at 17:29
  • True, and I totally missed that. At the time I posted this (over a year ago) I was just getting into javascript so maybe I shouldnt've posted the answer, but anyway, thanks for catching that! – Fighter178 May 15 '23 at 04:39