Seems to be read wrong by my ClangImporter.
My understanding (possibly incorrect) is the behavior is "as expected" if you haven't modified your CTypes.swift
. unsigned long
gets imported as CUnsignedLong
as per BuiltinMappedTypes.def
. CTypes.swift
has CUnsignedLong = UInt
as you mentioned for non-Windows and non-x86_64 platforms at the moment, so it gets translated to UInt
. UInt
has the same width as Int
. It is a bit mysterious as to why it is i16
and not u16
though.
If you think about the mapping, there are lots of different kinds of types. There are built-ins atoms like int
, unsigned int
etc, there are built-in composites (like function types, which have a bunch of argument types and one return type), there are "core" typedefs (like uint32_t
, this is somewhat fuzzy but you get what I mean), and there are user-defined types and typedefs.
If you think of a type as a tree structure, the built-in atoms are the leaves of the tree. BuiltinMappedTypes.def
describes how Clang's built-in atoms translate to Swift (technically, these are just called builtins), barring special hacks cases.
Following the tree structure, translating types between languages is like writing a tree interpreter.
- There is a Clang type -> Swift type tree interpreter in
ImportType.cpp
.
- There are Swift type -> Clang type tree interpreters in
GenClangType.cpp
/ClangTypeConverter.cpp
(these are very similar, so you just look at one without worrying about why there are two of them).
What is confusing me is this, I thought that MappedTypes.def
determined the mapping, but this is only used in one place I can see, getSwiftStdlibType
in ImportDecl.cpp
MappedTypes.def
is essentially a list of special-cases that are handled for better user ergonomics (I might be wrong on the purpose, maybe @jrose can describe it better). For example, typedefs can be imported as the underlying type (i.e. erasure-on-import) or they can be translated to typealiases in Swift (i.e. transliteration-on-import). With these specific types, we want to make sure that no new typealiases are introduced, instead, we hard-code what they get imported to. For example, if a platform header defines typedef int32_t int
because it has 32-bit ints. As a base assumption, let's assume that int
gets imported as Int
(hopefully uncontroversial). Then we do not want the typedef to be erased (e.g. functions that look like they take int32_t
arguments look like they take Int
arguments in Swift), and we do not want to introduce a new typealias like typealias Int32 = Int
. Instead, we want to make sure that int32_t
gets mapped to Int32
, which is a struct in the stdlib, directly without any extra indirection from a typealias. Since this doesn't fit cleanly into the two most common import strategies, and we care about ergonomics, and this is probably a more scalable solution than modifying headers with attributes for every platform we care about, we hardcode these.