Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird value of #function literal in Swift 3.1

Tags:

function

swift

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?

like image 624
Robo Robok Avatar asked Jul 13 '17 16:07

Robo Robok


1 Answers

Swift 5 update

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(_:_:)"

Pre Swift 5

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.

like image 184
Hamish Avatar answered Nov 19 '22 19:11

Hamish