The rule is specifically that unavailable declarations can be referenced without a diagnostic from contexts that are at least as unavailable. For example, a function that is @available(macOS, unavailable) can be referenced from a contexts that are either @available(macOS, unavailable) or @available(*, unavailable) when compiling for a macOS target. However, there's an exception to this logic in the compiler specifically for references to functions that are unavailable in *.
This logic that makes an exception for universally unavailable functions pre-dates my experience with the availability checker and I haven't found any concrete explanation for it, but I have a guess for the rationale. Since the compiler does not intend accept any program that would actually be able to call an @available(*, unavailable) function at runtime, it doesn't really make sense to references to them anywhere except for convenience. But there's a cost to that convenience during overload resolution. If @available(*, unavailable) functions were legal to call in @available(*, unavailable) contexts, then overload resolution in those contexts has to consider overloads that could otherwise be ruled out:
func f() -> String { "dynamic" }
@available(*, unavailable)
func f() -> StaticString { "static" }
@available(*, unavailable)
func g() {
print(f()) // ambiguous unless the overload returning StaticString is ruled out
}
This may seem pretty niche, but I've seen it be relevant for some code where @available(*, unavailable) is applied conditionally to certain functions based on build settings. I think custom availability would be a better tool for code that needs to become unavailable in certain build configurations, though.
Could you use deprecation and -Werror DeprecatedDeclaration? I suppose it depends on whether your code uses any other deprecated declarations, but it might be a reasonable workaround.