To move this feature forward, i'd suggest an narrowed-down and implementable portion of the feature, with these modifications over the initial plan:
Instead of supporting any path, we should focus on items, excluding any fields or associated items. For consistency enum variants is also not supported (not an item), but this might be added later in certain form.
It's clear that we can only support items from dependency crates, the most important case to support is std. Also, we should avoid inferring dependency crate from the content of path, being more explicit.
It's been so long, I don't even remember what the blockers are. How does your proposal address these? Especially the final point — the change in syntax seems to have no benefit (and is a change for the worse imo).
Solution: by specifying the dependency crate as the starting point of the resolution, we can enforce that the resolution happens within a specific crate.
Solution: We only support item resolution in this feature, excluding all associated items, trait-based resolution complexities. This changed is reflected in the attr name change (accessible_item)
Syntactically i don't have much strong opinion. Just don't want to give people the incorrect impression that any usual path will work there. Ideas welcome!
Wouldn't it suffice to error if the path starts with a segment that does not resolve into the extern prelude? The attribute taking a plain path looks the most reasonable (and expected) to me.
Besides the fake impression that any path will be supported properly(but people can learn from the diagnostics), I guess the major downside will be that this cannot support Rust 2015 edition at all, which is … acceptable, i think.
I don't see how that is the case? I didn't mean that the path has to start with :: but that its first segment needs to resolve into the extern prelude, that is std::option::Option would work just fine, so would ::std::option::Option but foo::bar would not unless foo resolves to an extern crate (so also no local re-exports). I believe that should be compatible with 2015 name resolution?
Whether or not foo in foo::bar resolves to an extern crate depends on if there is a foo item in scope.
I don't see why cfg(accessible) can't work with paths like ::std::option::Option. There is no need for macros to parse paths using the rules of the exact same edition as the crate that uses the macro. And in fact proc macros don't as there is no way to get the edition of the invoking crate.
Not familiar with the compiler architecture and "when" "what" happens, but i wonder if requiring fully qualified/canonical paths and not supporting trait aliases could help at all, offloading the work by saying there is no scope, as a slightly less minimal, but that much more immediately useful, minimally viable feature, thats leaves open compatibility relaxing the path restrictions.
Macro expansion must have access to what things are in scope... is cfg evaluation separate from that so it has to run even earlier? If cfg runs with the rest of macro expansion, then name resolution information should be available, no?
Macro expansion and early name resolution runs interleaved with each other as fixpoint algorithm. If a macro definition isn't available yet, the macro expansion is simply delayed. cfg(accessible) can't be delayed until after foo is accessible as that may never happen. Macro expansion delaying works under thr assumption that the thing you are delaying based on will eventually be defined and otherwise an error can be returned. With cfg(accessible) you can easily create a paradox:
#[cfg(not(accessible(Foo)))]
struct Foo;
If Foo is not defined, then the definition will not be cfg'ed out making it defined, but then it should be cfg'ed out as the conditional is false, but that again requires Foo to be defined.
How is that related to the extern prelude though? I think no one will disagree that the path in question needs to resolve to a dependency precisely because of the fixpoint resolution. It seems questionable to me to use this feature for local items anyways (you are aware if things are available or not already).
My argument was that we should be able to put plain paths (opposed to requiring absolute (::path) paths) just fine if they resolve into the extern prelude. Notably that implies that the first segment needs to be a name of the extern prelude crates as using local renames (like extern crate dep as thing; would run into the same fixpoint algo again). rustc should be aware of the names of dependencies before starting the fixpoint resolution no?
You don't know before macro expansion if my_dep refers to an extern dependency or to the module inside the local crate that shadows the extern prelude.
Not sure this is fully workable... but would something like this be okay from both a "teachable" and implementation standpoint?
Only allow referencing items "higher in the module/crate tree"
Example:
crate A
mod part_of_a
struct ItemInA
crate B (depends on crate A)
mod part_of_b
struct ItemInParent
mod deeper_in_b
struct SiblingDirect
#[cfg(accessible)] invocation
mod another_in_b
struct ItemInB
Allowed in invocation of #[cfg(accessible)]
ItemInA (crate A is "parent/up the tree" from the root of crate B)
ItemInParent (defined in parent of invocation module)
4.? ItemInB (defined "above" deeper_in_b)... might be forbidden, to make rules simpler
Forbidden in invocation
SiblingDirect (defined in same module as invocation, so we know locally whether it exists (weaker motivation for using cfg), as an earlier reply mentioned above)
4.? if it makes things simpler to grok, ItemInB (defined in a local crate module that is not an ancestor of this module)
Theoretically, it could optimistically resolve to the external crate name, and register a requirement for that name to not become ambiguous later. We're effectively saying that the path must resolve to an external crate name, and it's an error if it doesn't.
I think this should work similarly to macro names ambiguous with macro expanded macro names.
Question: do we actually needcfg(accessible) to work for any path that resolves to within the current crate? Fundamentally, cfg(accessible) is for polyfills, and what polyfill authors need is the equivalent of this Python fragment:
try:
# may or may not be available depending on environment
import SomeClass from not_this_package
except ImportError:
class SomeClass:
# fallback definition here
In Python, you don't ever need to do this for imports resolving to within the package, because you control all the code within the package. You know whether every SomeClass defined by your package exists or not, you don't need to ask the interpreter. I can think of only one exception: if SomeClass is being conditionally defined based on some other environmental consideration. But the fact that you need a fallback for it means SomeClassshouldn't be conditionally defined; go fix that at the point of definition!
Similarly in Rust, I expect the normal usage pattern for cfg(accessible) to be like
and I suggest that it might be Just Fine if accessible(path::within::current::crate) was a compile error, at least in the minimum viable version of the feature. We can always relax that later.
I think the answer is no, and that's also the gist I am getting from the prior discussion. However, it's non-trivial to even figure out whether a given path resolves within the current crate or not: my_dep::foo could refer to an extern crate called my_dep, or a local module of the same name. That's the problem the last few posts have been discussing.
Would it make this less of an issue if (again, in the minimum viable feature) you had to write an absolute path in the argument of cfg(accessible), with the leading ::?