Not understanding `FilePath` and string literals

i don’t really understand why this is allowed

let executable:FilePath = ".build.aarch64/release/the-tool"
let compressed:FilePath = ".build.aarch64/release/the-tool.gz"

but not this

let executable:FilePath = ".build.aarch64/release/\(name)"
let compressed:FilePath = ".build.aarch64/release/\(name).gz"

which instead needs to be spelled

let executable:FilePath = .init(".build.aarch64/release/\(name)")
let compressed:FilePath = .init(".build.aarch64/release/\(name).gz")

yes, i know ExpressibleByStringLiteral is not the same as ExpressibleByStringInterpolation, but my question is why FilePath does not conform to the latter.


Because interpolating strings that are supposed to be path components but instead are full-on relative paths is a classic injection attack, and not providing the API forces you to think about that. They could have offered labeled interpolation, though.


i agree that in general this is hazardous, but i don’t think the current positioning of the guardrails makes sense. when you realize

let executable:FilePath = ".build.aarch64/release/\(name)"
let compressed:FilePath = ".build.aarch64/release/\(name).gz"

does not compile, you could either wrap the string literal in init() (which is non-failable) or use FilePath.appending(_:) to make the error go away. but if you had an injection vulnerability before, you still have an injection vulnerability afterwards.

in particular, if for some reason you had instead started with

let executable:FilePath = release.appending(name)
let compressed:FilePath = release.appending("\(name).gz")

then you would never have hit a speed bump in the first place and would be happily chugging along with a possible injection vulnerability.

i don’t think the API is really doing a consistent job of guiding developers towards safer patterns, it is just singling out string literals for added friction without adding corresponding safeguards to FilePath.appending(_:).


I don't think "FilePath.appending does the unsafe thing" is a good reason for interpolation to be an even easier way to do the unsafe thing, but notably there is a method that protects against path traversal: FilePath.lexicallyResolving. It would be nice if FilePath had an ExpressibleByStringInterpolation variant that defaulted to using that method.


Part of the problem with that is that it requires the base to be a complete path, which string interpolation can’t do. If I have "/foo/bar\(baz)", is the baz part relative to a file named /foo/bar, or is it going to assume bar is a directory and its relative to /foo/bar/, or is it going to be relative to foo but then "bar" is prefixed onto baz? The last is probably most useful, but still a bit non-obvious.

Of course, these things can be added, though I’m not sure what development process System is using. But they do need to be carefully thought through.


or alternatively perhaps the user intended to concatenate baz to the filename, forming something like /foo/bar123 (edit: I see that's exactly what you meant by your third point!) Definitely agree that this is much harder to solve with interpolation than, say, SQLi :neutral_face:

1 Like

I do wonder about the "wrap in init(_:)" part. :-/ Something that has me much less excited about special string interpolation in general. So maybe you're right that an unrestricted literal would be ""fine"".