Add #self, #Self to #function, #file, #line, #column

For #Self, it would still be more useful to have the actual type rather than a StaticString with the type's name.

Hm, is it the static type or the dynamic type?

(Meta-point: I guess you'd/we'd need to come up with some concrete use cases before proposing this.)

Honestly, I don't know what the limitations are between having a handle to a static or a dynamic type. Any documentation?

In general, I don't have any good concrete examples yet (I just think having more info on hand is always better), but you could imagine with a logging framework that if some object was a subclass of your special domain (like HXObject), then it would have its own overridable methods to print itself especially for the logging framework.

What would be the type of self or self? Any? (and Any.Type??) I'm generally uncomfortable with a language feature allowing you to inspect the caller's lexical scope, even indirectly. There should be no difference between calling something from a class method directly, or extracting part of the class method body into a top-level function and calling the top-level function, but with this change, you will see different behavior.

I think the introduction #self and #Self can enable whole new ways of doing things that go far beyond logging frameworks.

In order to decide whether this is a good idea or whether it should be mostly static or dynamic, we need a better understanding of the problem domains that can benefit from these features.

1 Like

Isn't that also true of #function?

I don't think we should use #Self for this, because Self usually refers to the current context (and will do so more universally once SE-0068 is implemented) and these kind of logging-macro-things are usually used as default arguments.

struct Logger {
  func doSomething1(_ callingType: Any.Type? = Self) // Logger.Type
  func doSomething2(_ callingType: Any.Type? = #Self) // Caller.Type
}

Rather than add a standalone thing for this, I'd rather put it inside of a #callingContext value which can include more-obvious .type and .instance members.

1 Like

Yes, but #function produces a String and its not as easily abused to implement context-specific behavior as passing in a value or a type. It's fine for logging output to depend on context, such as functions, files and lines. But I think self will encourage people to actually have behavior that depends on it, and not just logging output.

This is what I meant. Having this feature will have far reaching consequences. I think it is debatable whether this feature will be more harmful than useful.

2 Likes

I can see various nice debugging purposes to know the caller of the function (#self = self as Any) or at least the type of the called (Self). For example, during refactoring, you want to get rid of some parts of your app to call the other part, etc.

Other use cases can be e.g. security (disallow calls from outside of your framework), backward compatibility (calls from types that conform to deprecated protocols), etc.

See http://www.jot.fm/issues/issue_2009_01/article4/ for how it was envisioned for ObjC to have a sender within the method calls.

The availability of the sender in the lookup function enables to alter the dispatch strategy depending on the sender. This contributes to enhance the runtime ability for context-oriented programming. The notion of Context-oriented Programming (COP) directly supports variability depending on a large range of dynamic attributes. In e ect, it should be possible to dispatch runtime behavior on any properties of the execution context. First prototypes have illustrated how multidimensional dispatch can indeed be supported e ectively to achieve expressive runtime variation in behavior.

As someone who inherited a legacy codebase with years of technical debt, the idea of the callee inspecting the calling context for anything but logging purposes gives me the creeps, bringing in a whole new level of gore.

6 Likes

Regardless of whether #self and #Self are accepted, I think it's a good idea to move #function, #file, #line into a #context parameter.

I would also like to see #file become a relative path.

2 Likes

The value depends on what was passed when swiftc was invoked. Changing it (and having it honor some options) has come up a few times. Some likely related threads:

Haven't we all? Swift has other features which should help with that (e.g. static typing). This isn't like Obj-C where you can just invoke selectors on id and maybe they will crash at runtime. You need to cast the value to a known type before you can start doing useful things with it.

Really, I think people are exaggerating the impact this would have. This isn't any kind of stack-trace inspection - it's a value passed by the caller, like any other argument, with the benefit that it can be automatically filled-in using the default argument system. That's all.

Take the following example, which I believe is illustrative of what people are fretting about. I don't consider this to be particularly good style, but at the same time I don't consider it to be particularly fragile or world-breaking.

protocol Callback {
  func operationCompleted()
}
func doSomething1(_ callback: Any? = #self) {
  if let cb = callback as? Callback { cb.operationCompleted() }
}
func doSomething2(_ callback: Callback? = nil) {
  callback?.operationCompleted()
}

class ABC: Callback {
  func operationCompleted() { /*...*/ }

  func test1() { doSomething1() } // 'self' can be filled as a default argument.
  func test2() { doSomething2(self) }
}

You could also imagine a system where the type of callback isn't Any but some more-constrained type, with the #self default argument only being filled when called from a compatible context.

I think it makes the code harder to read and reason about, since self can now escape to the called code anytime without me noticing. And it’s not like any other argument – since the default values for other arguments are supplied by the code being called, not pulled from the current context. I love the clarity of functional style programming where you can easily reason about a function call just from the arguments.

2 Likes

I think having #self is necessary for Linux’s XCTestExpectation

1 Like

How so? You can create expectations decoupled from any XCTestCase instance using the normal initializer, and expectations that are associated with a test case using the expectation(description:) method defined in an extension on XCTestCase. No need to use a weird hidden side channel to pass the test instance.

If we're not going the #context way then I would also like to pitch the need for #module static string. And as mentioned in the other thread I would like to have a general #type(T) to extract generic type parameter as a static string (if that's technically possible). Or something like this maybe:

extension StaticString {
   init<T>(_ type: T.Type) { ... }
}

// The rest would be trivial
func work<T>(_ type: StaticString = .init(T.self)) { ... }
2 Likes

I realize this topic is now 4 years old. But just curious on any plans to possibly add an ability to include "#self" or "#Self" to Swift.

An earlier comment mentioned XCTestExpecation as a good use case, which is exactly the use case that would be helpful for me.

Trying to create a helpful function for testing, and would like to pass in a default "#self" parameter to the function, to be able to create expectations on that specific Test Case, so that waiting for expectations is done on that test.

Obviously, I can do this in other ways by just adding an optional parameter, or by just operating on a new instance of the test. (I could also not be so stubborn and make the function into a method of the testing class so I would easily have access to 'self').

Just trying to "have my cake and eat it too", and curious if anymore thought has gone into possibly have "#self" be a supported command for a default parameter that would grab the "self" of the calling object. So, be able to define something like:

public func XCTWaitForLogging(test: XCTestCase = #self) {
    let expectation = test.expectation(description: "wait for logging queue")
    staticLogger.waitForQueue {
        expectation.fulfill()
    }
    test.wait(for: [expectation], timeout: 10)
}

...
    XCTWaitForLogging()
...

// Instead of
    XCTWaitForLogging(self)

As other posters also called out, there could potentially be some other cooler things that could be done. Just would like to have that ability...

Never managed to have this working apart from passing individual # params:

func foo(funcAndLine v: (String, Int) = (#function, #line), function: String = #function, line: Int = #line) {
    print(v.0, v.1)        // 😢 foo(funcAndLine:function:line:) 17
    print(function, line)  // 😃 bar() 23
}

func bar() {
    foo()
}

bar()

This doesn't give the wanted result either:

func foo(funcAndLine: String = #function + String(#line), ......)

or even this:

func foo(funcAndLine: String = #function + "!", .....)

You may like #fileID instead which makes "ModuleName/fileName.swift" formatted string.

Another interesting application - you could define a method or a function that is only callable from certain classes, by enforcing that "self" (of the calling class/method) conforms to a certain protocol. Would be kind of cool to be sort of a more dynamic "delegate" model - where the object being called into doesn't have to store the delegate...

Just trying to think of any other justification I can for why adding in "#self" would be a worthwhile enhancement... :slight_smile: