PrintAsClang is supposed to emit declarations in the same order regardless of the compiler’s internal state, but we have repeatedly found that our current criteria are inadequate, resulting in non-functionality-affecting changes to generated header content. Add a diagnostic that’s emitted when this happens soliciting a bug report.
Since there *should* be no cases where the compiler fails to order declarations, this diagnostic is never actually emitted. Instead, we test this change by enabling `-verify` on nearly all PrintAsClang tests to make sure they are unaffected.
This did demonstrate a missing criterion that only mattered in C++ mode: extensions that varied only in their generic signature were not sorted stably. Add a sort criterion for this.
Technically, these operations belong in the ObjectiveC module, where NSObject
is defined. Keep them there. However, we need to build the mock ObjectiveC
overlay with `-disable-objc-attr-requires-foundation-module` now.
Like Swift generics, Objective-C generics may have constraints; unlike
Swift generics, Objective-C doesn't do separate parsing and
type-checking passes. This means that any generic arguments for
constrained generic parameters must be fully-defined, in order to
check that they satisfy the constraints.
This commit addresses this problem with three different mechanisms,
one for each kind of declaration that might run into this issue:
- For classes, if a member references a type with constrained generic
parameter, and the corresponding argument type hasn't been printed
yet, that member is "delayed", which means it is put into a category
at the end of the file.
- Protocols cannot have categories, so for protocols we instead see if
we can print the definition of the other type first. To break
circular dependencies, the printer will not attempt this if both the
type and the protocol are already being depended on. This isn't
perfect (see below).
- Rather than delaying members of extensions, we just delay them
wholesale. This keeps related members together, but also has
problems (see below).
These approaches solve the most common cases while still not crashing
in the uncommon ones. However, there are still a number of problems:
- The protocol heuristic is overly negative, which means we may generate
an invalid header even when there's a reasonable ordering. For example,
a single class might inherit from a class A and conform to protocol P,
and protocol P depends on class A as a generic argument. In this case,
defining class A first is the right thing to do, but it's possible for
the printer to decide that there's circularity here and just forward-
declare A instead.
- Protocols really can be circular. This can be fixed by printing a
forward-declared protocol alongside the generic constraints, i.e.
'id <MoreThanNSCopying, NSCopying>' instead of just
'id <MoreThanNSCopying>'.
- Extensions can introduce protocols as well. This is not modeled at
all; if a member depends on a protocol conformance, it's assumed
that simply printing the class would be sufficient. This could be
fixed by checking how a generic argument satisfies its constraints,
possibly delaying individual members from extensions in order to
print them sooner.
- More cases I haven't thought about.
Test cases for some of these problems are in the new
circularity-errors.swift file, mostly to make sure the ObjC printer
doesn't crash when it encounters them.
rdar://problem/27109377