SwiftTL: add CodeGenerator.generateLayered for per-layer output

Emits one {apiPrefix}Layer{N}.swift file per layer: file-scope
dispatch table, public struct {apiPrefix}{N} with parse/parseVector/
serializeObject, nested public enums for each sum type using the
inline-args shape. Int256 dispatch entry emitted only when a layer's
constructors reference it. Reuses typeReferenceRepresentation /
generateFieldSerialization / generateFieldParsing unchanged, passing
the struct name as the apiPrefix so nested type refs render correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
isaac 2026-04-21 15:49:45 +04:00
parent 10054efd81
commit 803bf23269

View file

@ -198,7 +198,325 @@ enum CodeGenerator {
return files
}
static func generateLayered(
apiPrefix: String,
layerNumber: Int,
types: [Resolver.SumType]
) throws -> (filename: String, source: String) {
let structName = "\(apiPrefix)\(layerNumber)"
let filename = "\(apiPrefix)Layer\(layerNumber).swift"
// All nested type refs (inside the struct) use `structName` as their prefix
// `apiPrefix` is used only to compute `structName` and `filename`. Helpers like
// typeReferenceRepresentation and generateFieldParsing get `structName`, not
// `apiPrefix`, so e.g. fields render as `media: SecretApi8.DecryptedMessageMedia`.
var typeMap: [QualifiedName: Resolver.SumType] = [:]
for type in types {
typeMap[type.name] = type
}
// Detect whether any constructor argument uses Int256; if so, we need the int256 parser entry.
var usesInt256 = false
outer: for type in types {
for (_, constructor) in type.constructors {
for argument in constructor.arguments {
if containsInt256(argument.type) { usesInt256 = true; break outer }
}
}
}
var writer = CodeWriter()
writer.line()
// File-scope dispatch table
writer.line("fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {")
writer.indent()
writer.line("var dict: [Int32 : (BufferReader) -> Any?] = [:]")
writer.line("dict[-1471112230] = { return $0.readInt32() }")
writer.line("dict[570911930] = { return $0.readInt64() }")
writer.line("dict[571523412] = { return $0.readDouble() }")
writer.line("dict[-1255641564] = { return parseString($0) }")
if usesInt256 {
writer.line("dict[0x0929C32F] = { return parseInt256($0) }")
}
let sortedTypes = types.sorted(by: { $0.name < $1.name })
for type in sortedTypes {
let sortedConstructors = type.constructors.values.sorted(by: { $0.name < $1.name })
for constructor in sortedConstructors {
writer.line("dict[\(Int32(bitPattern: constructor.id))] = { return \(structName).\(type.name).parse_\(constructor.name.value)($0) }")
}
}
writer.line("return dict")
writer.dedent()
writer.line("}()")
writer.line()
// public struct {apiPrefix}{N} {
writer.line("public struct \(structName) {")
writer.indent()
// public static func parse(_ buffer: Buffer) -> Any?
writer.line("public static func parse(_ buffer: Buffer) -> Any? {")
writer.indent()
writer.line("let reader = BufferReader(buffer)")
writer.line("if let signature = reader.readInt32() {")
writer.indent()
writer.line("return parse(reader, signature: signature)")
writer.dedent()
writer.line("}")
writer.line("return nil")
writer.dedent()
writer.line("}")
writer.line()
// fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any?
writer.line("fileprivate static func parse(_ reader: BufferReader, signature: Int32) -> Any? {")
writer.indent()
writer.line("if let parser = parsers[signature] {")
writer.indent()
writer.line("return parser(reader)")
writer.dedent()
writer.line("}")
writer.line("else {")
writer.indent()
writer.line("telegramApiLog(\"Type constructor \\(String(signature, radix: 16, uppercase: false)) not found\")")
writer.line("return nil")
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("}")
writer.line()
// fileprivate static func parseVector
writer.line("fileprivate static func parseVector<T>(_ reader: BufferReader, elementSignature: Int32, elementType: T.Type) -> [T]? {")
writer.indent()
writer.line("if let count = reader.readInt32() {")
writer.indent()
writer.line("var array = [T]()")
writer.line("var i: Int32 = 0")
writer.line("while i < count {")
writer.indent()
writer.line("var signature = elementSignature")
writer.line("if elementSignature == 0 {")
writer.indent()
writer.line("if let unboxedSignature = reader.readInt32() {")
writer.indent()
writer.line("signature = unboxedSignature")
writer.dedent()
writer.line("}")
writer.line("else {")
writer.indent()
writer.line("return nil")
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("}")
writer.line("if let item = \(structName).parse(reader, signature: signature) as? T {")
writer.indent()
writer.line("array.append(item)")
writer.dedent()
writer.line("}")
writer.line("else {")
writer.indent()
writer.line("return nil")
writer.dedent()
writer.line("}")
writer.line("i += 1")
writer.dedent()
writer.line("}")
writer.line("return array")
writer.dedent()
writer.line("}")
writer.line("return nil")
writer.dedent()
writer.line("}")
writer.line()
// public static func serializeObject
writer.line("public static func serializeObject(_ object: Any, buffer: Buffer, boxed: Swift.Bool) {")
writer.indent()
writer.line("switch object {")
for type in sortedTypes {
writer.line("case let _1 as \(structName).\(type.name):")
writer.indent()
writer.line("_1.serialize(buffer, boxed)")
writer.dedent()
}
writer.line("default:")
writer.indent()
writer.line("break")
writer.dedent()
writer.line("}")
writer.dedent()
writer.line("}")
writer.line()
// Nested public enum <TypeName> { ... } for each type
for type in sortedTypes {
try emitLayeredType(writer: &writer, structName: structName, type: type, typeMap: typeMap)
}
writer.dedent()
writer.line("}") // close public struct
return (filename, writer.output())
}
private static func containsInt256(_ type: Resolver.TypeReference) -> Bool {
switch type {
case .int256:
return true
case .bareVector(let element), .boxedVector(let element):
return containsInt256(element)
case .int32, .int64, .double, .bytes, .string, .bool, .boolTrue, .bareConstructor, .boxedType:
return false
}
}
private static func emitLayeredType(
writer: inout CodeWriter,
structName: String,
type: Resolver.SumType,
typeMap: [QualifiedName: Resolver.SumType]
) throws {
let sortedConstructors = type.constructors.values.sorted(by: { $0.name < $1.name })
let indirectPrefix = try type.hasDirectReference(to: [type], typeMap: typeMap) ? "indirect " : ""
writer.line("\(indirectPrefix)public enum \(type.name.value) {")
writer.indent()
// case <ctor>(<args>) -- inline-args shape
for constructor in sortedConstructors {
var argumentsString = ""
for argument in constructor.arguments {
if case .boolTrue = argument.type { continue }
if !argumentsString.isEmpty { argumentsString.append(", ") }
argumentsString.append(argument.name.camelCased)
argumentsString.append(": ")
argumentsString.append(typeReferenceRepresentation(structName, argument.type))
if argument.condition != nil { argumentsString.append("?") }
}
writer.line("case \(constructor.name.value)\(argumentsString.isEmpty ? "" : "(\(argumentsString))")")
}
writer.line()
// public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool)
writer.line("public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {")
writer.indent()
writer.line("switch self {")
for constructor in sortedConstructors {
var bindString = ""
for argument in constructor.arguments {
if case .boolTrue = argument.type { continue }
if !bindString.isEmpty { bindString.append(", ") }
bindString.append("let ")
bindString.append(argument.name.camelCasedAndEscaped)
}
writer.line("case .\(constructor.name.value)\(bindString.isEmpty ? "" : "(\(bindString))"):")
writer.indent()
writer.line("if boxed {")
writer.indent()
writer.line("buffer.appendInt32(\(Int32(bitPattern: constructor.id)))")
writer.dedent()
writer.line("}")
for argument in constructor.arguments {
if case .boolTrue = argument.type { continue }
var argumentAccessor = "\(argument.name.camelCasedAndEscaped)"
if let condition = argument.condition {
writer.line("if Int(\(condition.fieldName)) & Int(1 << \(condition.bitIndex)) != 0 {")
writer.indent()
argumentAccessor.append("!")
generateFieldSerialization(writer: &writer, argument: argument, argumentAccessor: argumentAccessor)
writer.dedent()
writer.line("}")
} else {
generateFieldSerialization(writer: &writer, argument: argument, argumentAccessor: argumentAccessor)
}
}
writer.line("break")
writer.dedent()
}
writer.line("}")
writer.dedent()
writer.line("}")
writer.line()
// fileprivate static func parse_<ctor>(_ reader: BufferReader) -> <TypeName>?
for constructor in sortedConstructors {
writer.line("fileprivate static func parse_\(constructor.name.value)(_ reader: BufferReader) -> \(type.name.value)? {")
writer.indent()
if constructor.arguments.contains(where: { if case .boolTrue = $0.type { return false } else { return true } }) {
var argumentIndex = 0
var argumentCheckString = ""
var argumentCollectionString = ""
for argument in constructor.arguments {
if case .boolTrue = argument.type { continue }
writer.line("var _\(argumentIndex + 1): \(typeReferenceRepresentation(structName, argument.type))?")
if let condition = argument.condition {
guard let fieldIndex = constructor.arguments.filter({ if case .boolTrue = $0.type { return false } else { return true } }).firstIndex(where: { $0.name == condition.fieldName }) else {
throw CodeGenerationError(text: "Condition field \(condition.fieldName) not found")
}
writer.line("if Int(_\(fieldIndex + 1)!) & Int(1 << \(condition.bitIndex)) != 0 {")
writer.indent()
try generateFieldParsing(apiPrefix: structName, writer: &writer, typeMap: typeMap, argument: argument, argumentAccessor: "_\(argumentIndex + 1)")
writer.dedent()
writer.line("}")
} else {
try generateFieldParsing(apiPrefix: structName, writer: &writer, typeMap: typeMap, argument: argument, argumentAccessor: "_\(argumentIndex + 1)")
}
if !argumentCheckString.isEmpty { argumentCheckString.append(" && ") }
argumentCheckString.append("_c\(argumentIndex + 1)")
if !argumentCollectionString.isEmpty { argumentCollectionString.append(", ") }
argumentCollectionString.append("\(argument.name.camelCased): _\(argumentIndex + 1)")
if argument.condition == nil { argumentCollectionString.append("!") }
argumentIndex += 1
}
var checkIndex = 0
for argument in constructor.arguments {
if case .boolTrue = argument.type { continue }
if let condition = argument.condition {
guard let fieldIndex = constructor.arguments.filter({ if case .boolTrue = $0.type { return false } else { return true } }).firstIndex(where: { $0.name == condition.fieldName }) else {
throw CodeGenerationError(text: "Condition field \(condition.fieldName) not found")
}
writer.line("let _c\(checkIndex + 1) = (Int(_\(fieldIndex + 1)!) & Int(1 << \(condition.bitIndex)) == 0) || _\(checkIndex + 1) != nil")
} else {
writer.line("let _c\(checkIndex + 1) = _\(checkIndex + 1) != nil")
}
checkIndex += 1
}
writer.line("if \(argumentCheckString) {")
writer.indent()
writer.line("return \(structName).\(type.name).\(constructor.name.value)\(argumentCollectionString.isEmpty ? "" : "(\(argumentCollectionString))")")
writer.dedent()
writer.line("}")
writer.line("else {")
writer.indent()
writer.line("return nil")
writer.dedent()
writer.line("}")
} else {
writer.line("return \(structName).\(type.name).\(constructor.name.value)")
}
writer.dedent()
writer.line("}")
}
writer.dedent()
writer.line("}")
writer.line()
}
private static func generateMainFile(apiPrefix: String, types: [Resolver.SumType], functions: [Resolver.Function], constructorOrder: [(typeName: QualifiedName, constructorName: String)]) throws -> String {
var writer = CodeWriter()