I found out that the String
returned by #function
literal in Swift 3.1 is... weird. Here:
class FunctionLiteralTest {
func weirdo() -> String {
return #function
}
func weirdo(parameter: Int) -> String {
return #function
}
func weirdo(_ parameter: Int) -> String {
return #function
}
func weirdo(_ parameter: Int, _ anotherParameter: Int) -> String {
return #function
}
}
let functionLiteralTest = FunctionLiteralTest()
functionLiteralTest.weirdo() // returns "weirdo()"
functionLiteralTest.weirdo(parameter: 1) // returns "weirdo(parameter:)"
functionLiteralTest.weirdo(1) // returns "weirdo"
functionLiteralTest.weirdo(1, 2) // returns "weirdo"
Parentheses are skipped entirely when all parameters are unlabeled. I would quite understand that if #function
returned function name without parentheses for functions without any parameters too.
Is that justified behavior or a bug?
In Swift 5 (not officially released yet, but you can pick up a master snapshot), this inconsistency is fixed thanks to #19062. Your code now outputs the following:
functionLiteralTest.weirdo() // returns "weirdo()"
functionLiteralTest.weirdo(parameter: 1) // returns "weirdo(parameter:)"
functionLiteralTest.weirdo(1) // returns "weirdo(_:)"
functionLiteralTest.weirdo(1, 2) // returns "weirdo(_:_:)"
I agree this is completely baffling behaviour, but it does appear to be intentional.
Function literals get "filled in" in the process of SILGen; and this is done through the SILGenFunction::emitLiteral
function in SILGenApply.cpp.
This then calls onto getMagicFunctionString
for a function literal:
static StringRef getMagicFunctionString(SILGenFunction &SGF) { assert(SGF.MagicFunctionName && "asking for #function but we don't have a function name?!"); if (SGF.MagicFunctionString.empty()) { llvm::raw_string_ostream os(SGF.MagicFunctionString); SGF.MagicFunctionName.printPretty(os); } return SGF.MagicFunctionString; }
Which, if not already generated, creates a new stream to output to MagicFunctionString
, and calls DeclName::printPretty
on MagicFunctionName
with this stream:
llvm::raw_ostream &DeclName::printPretty(llvm::raw_ostream &os) const { return print(os, /*skipEmptyArgumentNames=*/true); }
(MagicFunctionName
is assigned when the function is emitted; and is given the value of getFullName()
, which is just the Name
of the declaration)
This then calls onto DeclName::print
, which as its second parameter takes a boolean argument to determine whether to skip listing argument names if they're all empty:
llvm::raw_ostream &DeclName::print(llvm::raw_ostream &os, bool skipEmptyArgumentNames) const { // Print the base name. os << getBaseName(); // If this is a simple name, we're done. if (isSimpleName()) return os; if (skipEmptyArgumentNames) { // If there is more than one argument yet none of them have names, // we're done. if (getArgumentNames().size() > 0) { bool anyNonEmptyNames = false; for (auto c : getArgumentNames()) { if (!c.empty()) { anyNonEmptyNames = true; break; } } if (!anyNonEmptyNames) return os; } } // Print the argument names. os << "("; for (auto c : getArgumentNames()) { os << c << ':'; } os << ")"; return os; }
And you can see that because of the if condition if (getArgumentNames().size() > 0)
, functions with no parameters will skip the check for all empty argument names, leading them to being emitted with parentheses, e.g weirdo()
. But functions with one or more parameters, all with empty argument names, get emitted without parenthesis, e.g weirdo
.
So, given DeclName::printPretty
specifically passes true
for the skipEmptyArgumentNames
argument, it seems that the Swift team do specifically want this behaviour for #function
.
Furthermore, if we look at the blame, we can see that DeclName::printPretty
was added in this commit, with the commit message:
Pretty-print DeclNames with no keyword arguments by dropping the parenthsized bit.
Instead of printing "
f(_:_:)
", just print "f
".
That being said, I would still file a bug report over it, as it doesn't seem that intuitive for function literals.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With