1
0
Fork 0
gobject-generator/Sources/GObjectGeneratorCore/Analysis.swift

255 lines
12 KiB
Swift

/// The result of analyzing a GIR repository against a `GenerationConfig`.
///
/// Contains the resolved class hierarchy, the classification of each type
/// (generate, manual, or ignore), and any per-type or per-function overrides
/// from the configuration. This result drives the subsequent code-generation
/// phase.
public struct AnalysisResult {
/// Maps each type name to its parent type name, forming an inheritance tree.
///
/// Keys are fully-qualified type names (e.g. `"Gtk.Widget"`); values are the
/// fully-qualified parent name.
public var classHierarchy: [String: String]
/// The set of fully-qualified type names that should have Swift bindings generated.
public var generatedTypes: Set<String>
/// The set of fully-qualified type names that are implemented manually and
/// should be excluded from generation.
public var manualTypes: Set<String>
/// The set of fully-qualified type names that should be skipped entirely.
public var ignoredTypes: Set<String>
/// Per-type overrides keyed by fully-qualified type name.
public var classOverrides: [String: ObjectOverrides]
/// Per-function overrides keyed by `"ClassName.functionName"`.
public var functionOverrides: [String: FunctionOverrides]
/// Signal overrides keyed by `"ClassName.signalName"`.
public var signalOverrides: [String: SignalOverrides]
/// Property overrides keyed by `"ClassName.propertyName"`.
public var propertyOverrides: [String: PropertyOverrides]
/// Function pattern (glob-style) rename rules.
public var functionPatterns: [FunctionPatternEntry]
/// Maps class name (e.g., `"Gtk.Button"`) to the set of instance method
/// and static function names inherited from ancestor classes in the
/// same namespace. Used to emit `override` for subclass declarations
/// that shadow parent members. Does not include constructors or
/// cross-namespace ancestors (those are not real Swift superclasses).
public var inheritedMethodNames: [String: Set<String>]
/// Set of fully-qualified type names (e.g. `"Gtk.Widget"`) that have
/// at least one subclass in the same namespace.
public var typesWithSubclasses: Set<String> = []
/// A function pattern rename rule extracted from the config.
public struct FunctionPatternEntry {
/// The fully-qualified type name this pattern applies to.
public let typeName: String
/// The glob-style pattern to match function names against.
public let pattern: String
/// The rename rule to apply to matching functions.
public let rename: RenameRule
/// Creates a function pattern entry.
///
/// - Parameters:
/// - typeName: The fully-qualified type name.
/// - pattern: The glob-style pattern to match function names.
/// - rename: The rename rule to apply.
public init(typeName: String, pattern: String, rename: RenameRule) {
self.typeName = typeName
self.pattern = pattern
self.rename = rename
}
}
/// Creates an analysis result from the given component values.
///
/// - Parameters:
/// - classHierarchy: Inheritance map from each type to its parent.
/// - generatedTypes: Types that should be generated.
/// - manualTypes: Types that are implemented manually.
/// - ignoredTypes: Types that should be omitted.
/// - classOverrides: Per-type override configuration.
/// - functionOverrides: Per-function override configuration.
/// - signalOverrides: Per-signal override configuration.
/// - propertyOverrides: Per-property override configuration.
/// - functionPatterns: Glob-style function rename rules.
/// - inheritedMethodNames: Map from each class to the set of method
/// names it inherits transitively from its parent class chain.
/// - typesWithSubclasses: Set of fully-qualified type names that have
/// at least one same-namespace subclass.
public init(classHierarchy: [String: String] = [:], generatedTypes: Set<String> = [],
manualTypes: Set<String> = [], ignoredTypes: Set<String> = [],
classOverrides: [String: ObjectOverrides] = [:],
functionOverrides: [String: FunctionOverrides] = [:],
signalOverrides: [String: SignalOverrides] = [:],
propertyOverrides: [String: PropertyOverrides] = [:],
functionPatterns: [FunctionPatternEntry] = [],
inheritedMethodNames: [String: Set<String>] = [:],
typesWithSubclasses: Set<String> = []) {
self.classHierarchy = classHierarchy
self.generatedTypes = generatedTypes
self.manualTypes = manualTypes
self.ignoredTypes = ignoredTypes
self.classOverrides = classOverrides
self.functionOverrides = functionOverrides
self.signalOverrides = signalOverrides
self.propertyOverrides = propertyOverrides
self.functionPatterns = functionPatterns
self.inheritedMethodNames = inheritedMethodNames
self.typesWithSubclasses = typesWithSubclasses
}
}
/// Analyzes a GIR repository against a `GenerationConfig` to produce an `AnalysisResult`.
///
/// The analyzer walks the repository's type hierarchy and applies the
/// configuration's generate/manual/ignore lists and per-type overrides.
public struct Analyzer {
/// The generation configuration that guides the analysis.
public let config: GenerationConfig
/// Creates an analyzer with the given generation configuration.
///
/// - Parameter config: The configuration specifying which types to
/// generate, handle manually, or ignore, along with any overrides.
public init(config: GenerationConfig) {
self.config = config
}
/// Analyzes the provided GIR repository and returns the classification result.
///
/// The analysis builds a class inheritance hierarchy from the repository,
/// classifies each type according to the configuration's `generate`,
/// `manual`, and `ignore` lists, and extracts per-type and per-function
/// overrides.
///
/// - Parameter repository: The parsed GIR repository to analyze.
/// - Returns: An `AnalysisResult` containing the resolved hierarchy, type
/// classifications, and overrides.
public func analyze(repository: Repository) -> AnalysisResult {
var classHierarchy: [String: String] = [:]
var generatedTypes: Set<String> = []
var manualTypes: Set<String> = []
var ignoredTypes: Set<String> = []
var classOverrides: [String: ObjectOverrides] = [:]
var functionOverrides: [String: FunctionOverrides] = [:]
var signalOverrides: [String: SignalOverrides] = [:]
var propertyOverrides: [String: PropertyOverrides] = [:]
var functionPatterns: [AnalysisResult.FunctionPatternEntry] = []
// Build a lookup: class name (e.g. "Gtk.Widget") -> namespace + class
// object. Used to walk the inheritance chain and collect method names.
var classesByFullName: [String: Class] = [:]
for ns in repository.namespaces {
for cls in ns.classes {
classesByFullName["\(ns.name).\(cls.name)"] = cls
}
}
for ns in repository.namespaces {
for cls in ns.classes {
let fullName = "\(ns.name).\(cls.name)"
if let parent = cls.parent {
let parentFullName = parent.contains(".") ? parent : "\(ns.name).\(parent)"
classHierarchy[fullName] = parentFullName
}
}
}
// Invert the hierarchy to find parents that have at least one
// same-namespace subclass. Only same-namespace children are real
// Swift subclasses for the purpose of the "final" rule.
var typesWithSubclasses: Set<String> = []
for ns in repository.namespaces {
for cls in ns.classes {
let fullName = "\(ns.name).\(cls.name)"
guard let parentFullName = classHierarchy[fullName] else { continue }
let parentNs = parentFullName.split(separator: ".").first.map(String.init) ?? ""
if parentNs == ns.name {
typesWithSubclasses.insert(parentFullName)
}
}
}
// Build the inherited-method-name set for each class by walking the
// class hierarchy transitively. A subclass inherits the union of all
// method names declared by its parent, grandparent, etc.
var inheritedMethodNames: [String: Set<String>] = [:]
for ns in repository.namespaces {
for cls in ns.classes {
let fullName = "\(ns.name).\(cls.name)"
guard let parentFullName = classHierarchy[fullName] else { continue }
var inherited = Set<String>()
// Walk up the hierarchy, stopping at cross-namespace boundaries.
// A cross-namespace parent (e.g. "GObject.InitiallyUnowned") is
// not a real Swift superclass the child is a Swift root.
var currentParent: String? = parentFullName
let childNs = fullName.split(separator: ".").first.map(String.init) ?? ""
while let p = currentParent, let pCls = classesByFullName[p] {
let pNs = p.split(separator: ".").first.map(String.init) ?? ""
if pNs != childNs { break }
for method in pCls.methods { inherited.insert(method.name) }
for fn in pCls.functions { inherited.insert(fn.name) }
currentParent = classHierarchy[p]
}
inheritedMethodNames[fullName] = inherited
}
}
for fullName in config.generate {
generatedTypes.insert(fullName)
}
for fullName in config.manual {
manualTypes.insert(fullName)
}
for fullName in config.ignore {
ignoredTypes.insert(fullName)
}
for objConfig in config.objects {
switch objConfig {
case .object(let name, let overrides):
classOverrides[name] = overrides
if let status = overrides.status {
switch status {
case .generate: generatedTypes.insert(name)
case .manual: manualTypes.insert(name)
case .ignore: ignoredTypes.insert(name)
}
}
case .function(let className, let functionName, let overrides):
let key = "\(className).\(functionName)"
functionOverrides[key] = overrides
case .functionPattern(let type, let pattern, let rename):
functionPatterns.append(AnalysisResult.FunctionPatternEntry(typeName: type, pattern: pattern, rename: rename))
case .signal(let type, let name, let overrides):
signalOverrides["\(type).\(name)"] = overrides
case .property(let type, let name, let overrides):
propertyOverrides["\(type).\(name)"] = overrides
}
}
return AnalysisResult(
classHierarchy: classHierarchy,
generatedTypes: generatedTypes,
manualTypes: manualTypes,
ignoredTypes: ignoredTypes,
classOverrides: classOverrides,
functionOverrides: functionOverrides,
signalOverrides: signalOverrides,
propertyOverrides: propertyOverrides,
functionPatterns: functionPatterns,
inheritedMethodNames: inheritedMethodNames,
typesWithSubclasses: typesWithSubclasses
)
}
}