But if that was true s1 would return ˋtestˋ as well, but it returns ˋfoo()ˋ as expected. So I don‘t understand the difference between s1 and s2 or s3 and would also expect those to return ˋfoo()ˋ rather than ˋtestˋ.
If this really is wrong and a bug I‘ll file it, but I want to be certain. Maybe someone with insights in the compiler of that area can provide a detailed answer.
This does not make any sense to me. s2 and s3 are clearly not inside the scope of the test function. Don't get me wrong, the values of s2 and s3 are, but not default values and the calls to obtain are not, from my point of view. Both values have to be generated and passed to test. I don't know if function parameters are * lazy * or something but calling obtain inside test does not mean that s2 is inside the scope of test, similarly how s3 has no visibility of s2 which in the same scope it would have.
To put it in a different perspective, I expect the above example to behave like this:
Inside a function, the value of `#function` is the name of that function, inside a method it is
the name of that method, inside a property getter or setter it is the name of that property,
inside special members like `init` or `subscript` it is the name of that keyword,
and at the top level of a file it is the name of the current module.
When used as the default value of a function or method parameter, the special literal’s value is determined when the default value expression is evaluated at the call site.
1. func logFunctionName(string: String = #function) {
2. print(string)
3. }
4. func myFunction() {
5. logFunctionName() // Prints "myFunction()".
6. }
For me the "inside something" is always inside the body which is { ... } that's why test(s1: StaticString = #function) will return the outer scope not the name of the function where it's declared.
As the document says, when #function is the default value of a parameter, it evaluates to the name of the function or method that calls it. So:
s1 evaluates to "foo" because foo calls test, which has such a parameter
s2 evaluates to "test" because foo calls test, which calls obtain, which has such a parameter
In all other circumstances, #function evaluates to the name of the function it's lexically "inside" (that's why TSPL says it's the "name of the declaration in which it appears"). This has nothing to do with where default arguments are evaluated because it's a literal expression that isn't evaluated like, say, a function call. That's why there's the # in front of it. So:
s3 evaluates to "test" because foo calls test, which calls obtain with an explicit argument #function, which evaluates to test because the argument is written in the declaration for test
This is SR-6913, and I'm actually sympathetic to changing the behavior here.
Some version of this is important if you do something like this:
var globalFallbackValue: Int?
func globalFallbackAssertingIfNotSet(file: StaticString = #file, line: Int = #line) -> Int {
guard let result = globalFallbackValue else {
preconditionFailure("global fallback not set yet", file: file, line: line)
}
return result
}
func test(_ x: Int = globalFallbackAssertingIfNotSet()) {
print(x)
}
test()
In this case, the code that should get blamed for the precondition violation is the place where the call to globalFallbackAssertingIfNotSet is written. (Okay, I guess that's arguable, but I didn't want to come up with a less contrived example.) To make that happen, the #file/#line in the arguments of globalFallbackAssertingIfNotSet have to be evaluated in the context of the declaration of test, not the call. But we could say that's different because it's not explicitly written in the call to globalFallbackAssertingIfNotSet.
1. var globalFallbackValue: Int?
2.
3. func globalFallbackAssertingIfNotSet(file: StaticString = #file, line: Int = #line) -> Int {
4. guard let result = globalFallbackValue else {
5. preconditionFailure("global fallback not set yet", file: file, line: line)
6. }
7. return result
8. }
9.
10. func test(_ x: Int = globalFallbackAssertingIfNotSet()) {
11. print(x)
12. }
13.
14. test()
I expect this to point to line 10 when the failure happens, not line 14. That's what it does today, and I think it should continue to do so even if we make the change we've been talking about. I can see arguments both ways, though.