diff --git a/build-system/SwiftTL/Sources/SwiftTL/Resolution.swift b/build-system/SwiftTL/Sources/SwiftTL/Resolution.swift index 5fbd3a90d9..431300a431 100644 --- a/build-system/SwiftTL/Sources/SwiftTL/Resolution.swift +++ b/build-system/SwiftTL/Sources/SwiftTL/Resolution.swift @@ -199,7 +199,114 @@ enum Resolver { return types.values.sorted(by: { $0.name < $1.name }) } - + + static func resolveLayeredTypes( + layers: [(layerNumber: Int, constructors: [DescriptionParser.ConstructorDescription])] + ) throws -> [(layerNumber: Int, types: [SumType])] { + // Running state: for each constructor name, the target type name and the raw description. + // We keep raw descriptions (not resolved forms) because a later-layer constructor may + // introduce new target-type names, and resolveTypeReference needs the final target-type set. + var liveConstructors: [QualifiedName: (typeName: QualifiedName, description: DescriptionParser.ConstructorDescription)] = [:] + var result: [(layerNumber: Int, types: [SumType])] = [] + + for (layerNumber, layerConstructors) in layers { + // Apply this layer's constructors to the running map with last-wins semantics. + for constructorDescription in layerConstructors { + switch constructorDescription.type { + case let .type(name): + if !name.value[name.value.startIndex].isUppercase { + throw ResolutionError(text: "Type constructor \(constructorDescription.name) -> \(name): the resulting type name should begin with a capital letter") + } + liveConstructors[constructorDescription.name] = (name, constructorDescription) + case let .generic(name, argumentType): + throw ResolutionError(text: "Type constructor \(constructorDescription.name) can not be used to construct a generic type \(name)<\(argumentType)>") + } + } + + // Note: a constructor reassigned to a different target type in a later layer is + // removed from its old type's constructor set. If that drops the old type to zero + // constructors, it vanishes from this snapshot, and any unrelated argument that + // still references the old type via boxedType(...) will fail to resolve here. + // secret_scheme.tl does not exercise this case (same-name constructors always + // target the same type), but the rule applies if a future layered schema does. + // Snapshot: group by target type, resolve. + var constructedTypes: [QualifiedName: [DescriptionParser.ConstructorDescription]] = [:] + var constructorNameToType: [QualifiedName: QualifiedName] = [:] + for (ctorName, entry) in liveConstructors { + constructedTypes[entry.typeName, default: []].append(entry.description) + constructorNameToType[ctorName] = entry.typeName + } + + func resolveTypeReference(description: DescriptionParser.TypeReferenceDescription) throws -> TypeReference { + switch description { + case let .type(name): + if let resolvedBuiltinType = resolveBuiltinType(name: name) { + return resolvedBuiltinType + } + if name.value[name.value.startIndex].isUppercase { + if let _ = constructedTypes[name] { + return .boxedType(name) + } else { + throw ResolutionError(text: "Unresolved type \(name) in layer \(layerNumber)") + } + } else { + if let typeName = constructorNameToType[name] { + return .bareConstructor(typeName: typeName, name: name) + } else { + throw ResolutionError(text: "Unresolved type constructor \(name) in layer \(layerNumber)") + } + } + case let .generic(name, argumentType): + if name == "vector" { + return .bareVector(try resolveTypeReference(description: .type(name: argumentType))) + } else if name == "Vector" { + return .boxedVector(try resolveTypeReference(description: .type(name: argumentType))) + } else { + throw ResolutionError(text: "Unresolved generic type \(name) in layer \(layerNumber)") + } + } + } + + func resolveArgument(existingArguments: [Argument], description: DescriptionParser.ArgumentDescription) throws -> Argument { + return Argument( + name: description.name, + type: try resolveTypeReference(description: description.type), + condition: try description.condition.flatMap { condition -> Argument.Condition in + if !existingArguments.contains(where: { $0.name == condition.fieldName }) { + throw ResolutionError(text: "Unresolved conditional field reference to \(condition.fieldName) in layer \(layerNumber)") + } + return Argument.Condition(fieldName: condition.fieldName, bitIndex: condition.bitIndex) + } + ) + } + + var types: [QualifiedName: SumType] = [:] + for (typeName, constructorDescriptions) in constructedTypes { + let type = SumType(name: typeName) + for constructorDescription in constructorDescriptions { + var arguments: [Argument] = [] + for argumentDescription in constructorDescription.arguments { + arguments.append(try resolveArgument(existingArguments: arguments, description: argumentDescription)) + } + guard let id = constructorDescription.explicitId else { + throw ResolutionError(text: "Constructor \(constructorDescription.name) does not have an id") + } + type.constructors[constructorDescription.name] = SumType.Constructor( + name: constructorDescription.name, + id: id, + arguments: arguments + ) + } + types[type.name] = type + } + + let sortedTypes = types.values.sorted(by: { $0.name < $1.name }) + result.append((layerNumber, sortedTypes)) + } + + return result + } + static func resolveFunctions(types: [SumType], functionDescriptions: [DescriptionParser.ConstructorDescription]) throws -> [Function] { var functions: [QualifiedName: Function] = [:]