We need `#fileName`

I also make heavy use of it during development. Because the package manager and Xcode have different ideas about what the working directory for unit tests should be, it is the only way I know to reliably find the package directory in both contexts in order to load/export Git‐tracked specifications.

There is a lot of package testing code out there that relies on things like this:

let packageRoot = URL(fileURLWithPath: #file)
    .deletingLastPathComponent()
    .deletingLastPathComponent()
    .deletingLastPathComponent()

let resources = packageRoot.appendingPathComponent("Resources")

However, I agree that there is probably never a good reason to have full paths still kicking around in a binary that will be shipped without its source. fatalError() and friends are over‐eager about it and also hide what they are doing.

Something should be done, but completely removing the ability to discover the full path even during development would be crippling.

3 Likes

If we were going to make #file relative to a given source root, then maybe we could provide that #sourceRoot as a special value too. Would that work for your use cases?

11 Likes

Yes. With #sourceRoot too, all the use cases I can think of would still be possible. I like that idea much better than the status quo.

1 Like

Without derailing from the main topic, with all these new keywords being pitched I think the context is growing too fast. Would it make sense to generalize that into a special StaticContext<T> type where the generic parameter allows the users to specify the subset of all #keywords, so that the context value isn't exploding in size where not appropriate? This will also reduce the number of parameters a function must have to obtain such static context compared to current status quo.

There was a similar topic, but instead of smashing everything inside one type it would be far better if we could specify a subset of static information we want to obtain:

6 Likes

I agree, that would be a good idea, especially since people are in fact relying on the current behavior of #file and we don't want to disrupt them, so that suggests to me introducing new context values would be preferable to breaking the existing ones.

2 Likes

I really like the idea of StaticContext. It's much more usable than an ad-hoc collection of parameters that all have to be forwarded individually.

5 Likes

Aside the pitch from @davedelong, I tried to create my own type to accumulate the context but I failed because the current static capturing behavior isn't that trivial as I initially thought.

So it definitely requires a formal proposal and some compiler help to create such a type.

IIRC @beccadax has also taken a stab at implementing #context; I do not know how far he's gotten.

Now that __FILE_NAME__ exists in clang for C family languages, it seems like a no brainer to add #fileName for Swift -- same behavior available for all languages.

2 Likes

Which "size" are you concerned about, in particular? The memory of a Context object, or the "mental" size of a large Context API with many properties?

At least in the former case, we should implement #context in such a way that values can be emitted as static global data.

2 Likes

That's what I would have thought.

It would be an issue if all properties were stored, for all uses of #context, even if only a properties are every accessed.

Could there be an optimization to only populate that data which will actually be referenced? Though I imagine an optimization like that would be defeated as soon as someone passes a Context object across a module boundary, or dumps/prints the entire object.

It would be easy to make it so that if you did something like func foo(x: String = #context.file), we only instantiate the file string. Passing around whole context values might be trickier, though in most cases they would probably be passed around as read-only, so if the type is large enough that we pass it indirectly we would just pass the global pointer around.

3 Likes

It would be pretty cool to have a really cheap context like this, especially if it could support Hashable by just comparing / hashing the pointer value.

If it's acceptable to say that context values are only ever compiler-generated, then the type representation could be a plain pointer to the global compiler-generated value. That might cut off possibilities for manipulating context values when forwarding them through different logging APIs, though.

Could that be mitigated by making Context be a reference type? Then compiler generates references would be a pointer to the static information, but as a developer I’d still be able to lash up my own composite/altered contexts.

I'd very much love to be able to carry around source location information as some form of thing "together" very cheaply -- a pointer to a static, compiler generated, immutable "location context" would be quite great. We currently pass around #file and #line, though sometimes I think to myself "function might have been nice to carry here as well" but we don't since it's too much hassle at some point (debate-able, maybe worth it to carry around, maybe not).

Usage patterns we have for these things are usually:

  • carry around the location info always
  • very rarely actually access it -- only when things went wrong, then in asynchronous programs one can say "the thing that originated at line ... failed"
  • this isn't a replacement for full on tracing, but a simpler lightweight source location focused helper

Introducing anything reference type for this hurts the first point, as it'd have to be refcounted I guess; and I can't really afford risking those refcounts in hot code paths (where the carrying is used).

So an immutable compiler generated + direct pointer to it sounds quite great to me -- carrying it around would be cheap, just a pointer after all, and no refcount since the source location info is "immortal" <3


I think this is acceptable for source location; more advanced things I'd expect to go full on tracing (open tracing / dapper / pivot tracing style), and those are runtime generated and kind of expected to "weight" a bit.

1 Like

Don't value types that are bigger than a certain threshold (~40 bytes I think but not declared as it's implementation detail) behave like that under the hood?

I'm sure we could get around that. If there was something like a SourceContext type in the standard library (which only the compiler could construct), the compiler could also know that those objects are always singletons/immortal and omit reference counting.

True my bad actually about the worry about the reference types for this. As long as we know it’s immortal it’s fine indeed.