Skip to main content

LanguageSemantics

Trait LanguageSemantics 

Source
pub trait LanguageSemantics<M: Default + Clone + PartialEq = ()> {
Show 19 methods // Required methods fn is_member_addition_breaking( &self, container: &Symbol<M>, member: &Symbol<M>, ) -> bool; fn same_family(&self, a: &Symbol<M>, b: &Symbol<M>) -> bool; fn same_identity(&self, a: &Symbol<M>, b: &Symbol<M>) -> bool; fn visibility_rank(&self, v: Visibility) -> u8; // Provided methods fn parse_union_values(&self, _type_str: &str) -> Option<BTreeSet<String>> { ... } fn is_async_wrapper(&self, _type_str: &str) -> bool { ... } fn format_import_change( &self, symbol: &str, old_path: &str, new_path: &str, ) -> String { ... } fn should_skip_symbol(&self, _sym: &Symbol<M>) -> bool { ... } fn member_label(&self) -> &'static str { ... } fn extract_rename_fallback_key(&self, _sym: &Symbol<M>) -> Option<String> { ... } fn canonical_name_for_relocation(&self, qualified_name: &str) -> String { ... } fn classify_relocation( &self, _old_qname: &str, _new_qname: &str, ) -> Option<&'static str> { ... } fn derive_import_subpath( &self, package: Option<&str>, _qualified_name: &str, ) -> String { ... } fn diff_language_data( &self, _old: &Symbol<M>, _new: &Symbol<M>, ) -> Vec<StructuralChange> { ... } fn post_process(&self, _changes: &mut Vec<StructuralChange>) { ... } fn hierarchy(&self) -> Option<&dyn HierarchySemantics<M>> { ... } fn renames(&self) -> Option<&dyn RenameSemantics> { ... } fn body_analyzer(&self) -> Option<&dyn BodyAnalysisSemantics> { ... } fn primitive_type_names(&self) -> &[&str] { ... }
}
Expand description

Language-specific semantic rules consumed by the diff engine.

These encode the places where “is this breaking?” or “are these related?” differ fundamentally by language. The diff engine calls these methods instead of hardcoding language-specific rules.

Required Methods§

Source

fn is_member_addition_breaking( &self, container: &Symbol<M>, member: &Symbol<M>, ) -> bool

Is adding this member to this container a breaking change?

This is the single rule that differs most fundamentally by language:

  • TypeScript: breaking only if the member is required (non-optional).
  • Go: ALWAYS breaking for interfaces (all implementors must add it).
  • Java: breaking for abstract methods, not for default methods.
  • C#: breaking for abstract members on interfaces.
  • Python: breaking for abstract methods on Protocol/ABC.
Source

fn same_family(&self, a: &Symbol<M>, b: &Symbol<M>) -> bool

Are these two symbols part of the same logical family/group?

Used to scope migration detection. When a symbol is removed, only symbols in the same family are considered as potential absorption targets.

  • TypeScript/React: same component directory
  • Go: same package
  • Java: same package
  • Python: same module
Source

fn same_identity(&self, a: &Symbol<M>, b: &Symbol<M>) -> bool

Are these two symbols the same concept, possibly at different paths?

When true, migration detection does a full member comparison (all members, not just newly-added ones) because the candidate is assumed to be a direct replacement for the removed symbol.

Resolves companion types linked by naming convention:

  • TypeScript: Button and ButtonProps (component + its props interface)
  • Go: Client and ClientOptions (struct + its configuration)
  • Java: UserService and UserServiceImpl (interface + implementation)
Source

fn visibility_rank(&self, v: Visibility) -> u8

Numeric rank for a visibility level (higher = more visible).

Used to determine if visibility was reduced (breaking) or increased. The ordering differs by language:

  • TypeScript: Private(0) < Internal(1) < Protected(1) < Public(2) < Exported(3)
  • Java: Private(0) < PackagePrivate(1) < Protected(2) < Public(3)
  • Go: Internal(0) < Exported(1)

Provided Methods§

Source

fn parse_union_values(&self, _type_str: &str) -> Option<BTreeSet<String>>

Parse union/constrained type values for fine-grained diffing.

TypeScript: parse 'primary' | 'secondary' | 'danger'. Python: parse Literal['a', 'b']. Most other languages return None.

Source

fn is_async_wrapper(&self, _type_str: &str) -> bool

Whether a return type string represents an async wrapper.

Used by the diff engine to detect sync→async and async→sync changes, which are always breaking regardless of the inner type.

TypeScript/JavaScript: Promise<T> Python: Coroutine[...], Awaitable[...] Java: CompletableFuture<T>, Future<T> Go: returns false (async handled via goroutines, not return types)

Source

fn format_import_change( &self, symbol: &str, old_path: &str, new_path: &str, ) -> String

Format an import/use statement change hint for migration descriptions.

When a symbol is renamed across packages, the diff engine includes import guidance so consumers know to update their import paths.

TypeScript: "replace \import { X } from ‘old-pkg’` with `import { X } from ‘new-pkg’`“Go:“replace `"old/pkg"` with `"new/pkg"`”` Default: generic format without language-specific syntax.

Source

fn should_skip_symbol(&self, _sym: &Symbol<M>) -> bool

Should this symbol be excluded from diff analysis?

Called by the diff engine to filter out symbols that should not be compared. The most common case is TypeScript’s export * from '...' star re-export directives.

TypeScript: sym.name == "*" (star re-exports) Default: false (all symbols are analyzed)

Source

fn member_label(&self) -> &'static str

Human-readable label for members when building migration descriptions.

TypeScript: "props" (component properties) Go: "fields" (struct fields) Default: "members"

Source

fn extract_rename_fallback_key(&self, _sym: &Symbol<M>) -> Option<String>

Extract a fallback key for rename matching from a symbol’s metadata.

When fingerprint-based rename detection fails, the diff engine uses this method to extract an alternative matching key. For TypeScript CSS tokens, this parses the resolved CSS value from the .d.ts type annotation (e.g., the string "#151515" from a CSS variable).

TypeScript: parses ["value"]: "..." from the return type annotation Default: None (no fallback key)

Source

fn canonical_name_for_relocation(&self, qualified_name: &str) -> String

Normalize a qualified name for relocation detection.

Strips language-specific path segments that represent lifecycle modifiers (e.g., TypeScript’s /deprecated/ and /next/ directories). Symbols with matching canonical names are detected as relocations rather than separate removals and additions.

TypeScript: strips /deprecated/ and /next/ segments Default: returns the name unchanged

Source

fn classify_relocation( &self, _old_qname: &str, _new_qname: &str, ) -> Option<&'static str>

Classify a relocation based on old and new qualified names.

Returns a human-readable label describing the relocation direction (e.g., “moved to deprecated exports”, “promoted from next to stable”). Returns None for generic relocations with no special classification.

TypeScript: detects /deprecated/ and /next/ transitions Default: None (no classification)

Source

fn derive_import_subpath( &self, package: Option<&str>, _qualified_name: &str, ) -> String

Derive the import subpath for a symbol, used in migration descriptions.

When a symbol moves between submodules (e.g., from main exports to /deprecated/ exports), the import path changes. This method derives the effective import path from the package name and qualified name.

TypeScript: appends /deprecated or /next based on qualified name Default: returns the package name unchanged

Source

fn diff_language_data( &self, _old: &Symbol<M>, _new: &Symbol<M>, ) -> Vec<StructuralChange>

Produce additional structural changes by diffing language-specific metadata on two matched symbols.

Called by the diff engine for each pair of symbols that matched by qualified name. The default implementation returns no changes.

TypeScript: could diff rendered_components or css metadata. Default: empty (no language-specific metadata diffing)

Source

fn post_process(&self, _changes: &mut Vec<StructuralChange>)

Post-process the change list before returning from diff_surfaces.

TypeScript: dedup default export changes. Most languages: no-op.

Source

fn hierarchy(&self) -> Option<&dyn HierarchySemantics<M>>

If this language supports component hierarchy inference (e.g., React, Vue, Django templates), return the hierarchy semantics implementation.

The orchestrator uses this to prepare data for LLM hierarchy inference. The trait is NOT responsible for LLM calls or prompt construction.

Source

fn renames(&self) -> Option<&dyn RenameSemantics>

If this language supports LLM-based rename inference (e.g., CSS physical→logical property renames, interface rename mappings), return the rename semantics implementation.

The orchestrator uses this to prepare data for LLM rename inference. The trait is NOT responsible for LLM calls or prompt construction.

Source

fn body_analyzer(&self) -> Option<&dyn BodyAnalysisSemantics>

If this language has deterministic body-level analysis (e.g., JSX diff, CSS variable scanning for TypeScript), return the body analysis implementation.

The orchestrator calls this during BU Phase 1 to detect behavioral breaks from function body changes without LLM assistance.

Source

fn primitive_type_names(&self) -> &[&str]

Primitive type names for this language, used by the diff engine’s structural similarity comparison.

When two types are compared for structural similarity (e.g., during rename detection), types matching these names are classified as primitives. Two primitives of different names are structurally similar (both are scalars), whereas a primitive vs a reference type is not.

Default: common cross-language primitives (string, number, boolean, void, null). Languages should override to add their own (e.g., TypeScript adds undefined, never, any, unknown; Java adds int, long, double, float, char, byte, short).

Implementors§