This seems like a good idea, primarily because of the non-hierarchical APIs issue.
I’d prefer to see this integrated with the same target configuration tokens used in cfg, rather than using a disjoint set of tokens. If this new mechanism needs to introduce a new set of target configuration tokens or expressions, then all those same tokens and expressions should also work in cfg, both for consistency and to provide more power to cfg. That then allows the use of cfg!, cfg_attr, and similar mechanisms. See the existing set of built-in cfg tokens.
The pre-RFC doesn’t mention whether the new scenario lint should warn or error by default. I would argue that it should error by default, but the error should detect attempts to use a symbol that requires a specific scenario and tell the user how to add the necessary scenario to their code. For instance, if you call a function to get a file descriptor, from a function that doesn’t explicitly say it only supports unix, you should get a compilation error, but the error should tell you exactly what you can copy and paste onto your function to make it work.
Also, in the example with platform_specific_helper, cross_platform doesn’t actually cover all scenarios; it runs on cfg(unix) and cfg(windows). Perhaps Rust should have an additional (optional) new lint for the benefit of porters, to identify functions like this that declare more portability than their implementation supports? (The RFC doesn’t necessarily need to depend on this, but perhaps you could list it as a useful additional enhancement?) This also ties into the question about whether “mainstream scenario” can change over time.
That said, this also does need a way to define functions that do runtime detection. For instance, a function portable to all x86 platforms might use cpuid to detect features at runtime, and then call a function that specifically requires avx instruction support, with fallback to a function that works everywhere. In that case, the avx-specific function can’t necessarily use cfg(cpu(avx)) or similar, because the function needs to exist for all CPUs; however, it should somehow declare itself as only running on such CPUs, so that if you call it from another such function it works, but if you call it from a more general function then that function has to explicitly tell Rust that it expands portability.