feat: establish first-class lexical language parity #2

Merged
trevon merged 14 commits from feat/language-parity-foundation into bench/yams-architecture-reproducibility 2026-08-30 22:09:40 +00:00
4 changed files with 76 additions and 23 deletions
Showing only changes of commit 42bda779f3 - Show all commits

feat(scan): expand Dart architecture relationships

trvon 2026-08-30 15:42:11 -06:00
Signed by: trevon
GPG key ID: 38686EA797D7A8A7

View file

@ -13,7 +13,7 @@ contracts and fixtures reach the same standard.
| Language | Useful current coverage | Important gaps | Tier |
| --- | --- | --- | --- |
| Dart | Classes, mixins, enums, extensions, many methods, imports, `extends`, and ordinary calls | `with`/`implements`, export/part, named constructors, accessors/operators, and multiline declarations | first-class target |
| Dart | Classes, mixins, enums, extensions, methods, named constructors, accessors, import/export/part directives, `extends`/`with`/`implements`/`on`, and ordinary calls | operators, generic relationship targets, conditional directives, and multiline declarations | first-class target |
| Rust | Structs, enums, traits, ordinary/async/const/unsafe/extern functions, simple `use`, calls, and raw-string masking | impl/module ownership, unions/type aliases, macros, turbofish calls, and braced/glob imports | first-class target |
| C | Files, basic includes, structs, and narrow function forms through the shared C/C++ profile | distinct C identity, enums/typedefs/function pointers, multiline declarations, macros, and declaration masking | first-class target |
| C++ | Basic classes/structs, narrow function forms, calls, and local include resolution | templates, constructors/destructors/operators, multiple inheritance, namespaces, overloads, lambdas, qualifiers, and multiline declarations | first-class target |

View file

@ -290,10 +290,26 @@ impl ScanState {
NodeKind::Class,
line_number,
);
if let Some(base) = captures.name("base") {
let mut bases: Vec<String> = patterns
.relationships
.as_ref()
.into_iter()
.flat_map(|pattern| pattern.captures_iter(line))
.filter_map(|captures| captures.name("names"))
.flat_map(|names| names.as_str().split(','))
.map(str::trim)
.filter(|name| !name.is_empty())
.map(clean_target)
.collect();
if bases.is_empty()
&& let Some(base) = captures.name("base")
{
bases.push(clean_target(base.as_str()));
}
for base in bases {
self.pending.push(PendingRelationship {
source: id.clone(),
target_name: clean_target(base.as_str()),
target_name: base,
kind: EdgeKind::Inherits,
language: language.into(),
member_call: false,
@ -310,11 +326,13 @@ impl ScanState {
}
let mut definition_name = None;
if let Some(captures) = patterns.function.captures(line)
&& let Some(name) = captures.name("name")
&& !is_keyword(name.as_str())
if let Some(name) = patterns.function.iter().find_map(|pattern| {
pattern
.captures(line)
.and_then(|captures| captures.name("name").map(|name| name.as_str().to_owned()))
}) && !is_keyword(&name)
{
definition_name = Some(name.as_str().to_owned());
definition_name = Some(name.clone());
let parent_id = scopes
.iter()
.rev()
@ -325,7 +343,7 @@ impl ScanState {
parent_id,
path,
language,
name.as_str(),
&name,
NodeKind::Function,
line_number,
);
@ -365,7 +383,10 @@ impl ScanState {
let target = target_match.as_str();
let prefix = call_line[..target_match.start()].trim_end();
let member_call = prefix.ends_with('.') || prefix.ends_with("->");
if definition_name.as_deref() != Some(target) && !is_keyword(target) {
let definition_call_name = definition_name
.as_deref()
.and_then(|name| name.rsplit('.').next());
if definition_call_name != Some(target) && !is_keyword(target) {
self.pending.push(PendingRelationship {
source: source.clone(),
target_name: clean_target(target),
@ -579,46 +600,68 @@ struct Scope {
struct Patterns {
class: Regex,
function: Regex,
function: Vec<Regex>,
import: Regex,
call: Regex,
relationships: Option<Regex>,
}
impl Patterns {
fn new(language: &str) -> Self {
let (class, function, import) = match language {
let (class, functions, import, relationships) = match language {
"python" => (
r"^\s*class\s+(?P<name>[A-Za-z_]\w*)(?:\((?P<base>[A-Za-z_][\w.]*)\))?",
r"^\s*(?:async\s+)?def\s+(?P<name>[A-Za-z_]\w*)\s*\(",
vec![r"^\s*(?:async\s+)?def\s+(?P<name>[A-Za-z_]\w*)\s*\("],
r"^\s*(?:from\s+(?P<name>[A-Za-z_][\w.]*)\s+import|import\s+(?P<name2>[A-Za-z_][\w.]*))",
None,
),
"rust" => (
r"^\s*(?:pub(?:\([^)]*\))?\s+)?(?:struct|enum|trait)\s+(?P<name>[A-Za-z_]\w*)",
r#"^\s*(?:pub(?:\([^)]*\))?\s+)?(?:(?:const|async|unsafe)\s+)*(?:extern(?:\s+"[^"]+")?\s+)?fn\s+(?P<name>[A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\("#,
vec![
r#"^\s*(?:pub(?:\([^)]*\))?\s+)?(?:(?:const|async|unsafe)\s+)*(?:extern(?:\s+"[^"]+")?\s+)?fn\s+(?P<name>[A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\("#,
],
r"^\s*use\s+(?P<name>[A-Za-z_][\w:]*)",
None,
),
"javascript" | "typescript" => (
r"^\s*(?:export\s+)?class\s+(?P<name>[A-Za-z_$][\w$]*)(?:\s+extends\s+(?P<base>[A-Za-z_$][\w$]*))?",
r"^\s*(?:export\s+)?(?:async\s+)?function\s+(?P<name>[A-Za-z_$][\w$]*)\s*\(",
vec![r"^\s*(?:export\s+)?(?:async\s+)?function\s+(?P<name>[A-Za-z_$][\w$]*)\s*\("],
r#"^\s*import(?:[^'\"]*from\s*)?['\"](?P<name>[^'\"]+)['\"]"#,
None,
),
"dart" => (
r"^\s*(?:(?:abstract|base|final|interface|sealed)\s+)?(?:class|mixin|enum|extension(?:\s+type)?)\s+(?P<name>[A-Za-z_]\w*)(?:\s+extends\s+(?P<base>[A-Za-z_]\w*))?",
r"^\s*(?:(?:external|static|abstract|covariant|late)\s+)*(?:(?:[A-Za-z_]\w*(?:<[^>{};()]+>)?[?!]?)\s+)?(?P<name>[A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:(?:async|sync)\*?\s*)?(?:\{|=>)",
r#"^\s*import\s+['\"](?P<name>[^'\"]+)['\"]"#,
vec![
r"^\s*(?:(?:external|static|abstract|covariant|late)\s+)*(?:(?:[A-Za-z_]\w*(?:<[^>{};()]+>)?[?!]?)\s+)?(?P<name>[A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:(?:async|sync)\*?\s*)?(?:\{|=>)",
r"^\s*(?P<name>[A-Za-z_]\w*\.[A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:(?:async|sync)\*?\s*)?(?:\{|=>|;)",
r"^\s*(?:(?:external|static|abstract|covariant|late)\s+)*(?:(?:[A-Za-z_]\w*(?:<[^>{};()]+>)?[?!]?)\s+)?get\s+(?P<name>[A-Za-z_]\w*)\s*(?:=>|\{)",
r"^\s*(?:(?:external|static|abstract|covariant|late)\s+)*set\s+(?P<name>[A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:=>|\{|;)",
],
r#"^\s*(?:import|export|part)\s+['\"](?P<name>[^'\"]+)['\"]"#,
Some(
r"\b(?:extends|with|implements|on)\s+(?P<names>[A-Za-z_]\w*(?:\s*,\s*[A-Za-z_]\w*)*)",
),
),
_ => (
r"^\s*(?:class|struct)\s+(?P<name>[A-Za-z_]\w*)(?:\s*:\s*(?:public\s+|protected\s+|private\s+)?(?P<base>[A-Za-z_:]\w*(?:::\w+)*))?",
r"^\s*(?:[\w:<>,*&]+\s+)+(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*\([^;{}]*\)\s*(?:const\s*)?(?:noexcept\s*)?\{?\s*$",
vec![
r"^\s*(?:[\w:<>,*&]+\s+)+(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*\([^;{}]*\)\s*(?:const\s*)?(?:noexcept\s*)?\{?\s*$",
],
r#"^\s*#\s*include\s*[<\"](?P<name>[^>\"]+)[>\"]"#,
None,
),
};
Self {
class: Regex::new(class).expect("class regex is static"),
function: Regex::new(function).expect("function regex is static"),
function: functions
.into_iter()
.map(|pattern| Regex::new(pattern).expect("function regex is static"))
.collect(),
import: Regex::new(import).expect("import regex is static"),
call: Regex::new(r"(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*\(")
.expect("call regex is static"),
relationships: relationships
.map(|pattern| Regex::new(pattern).expect("relationship regex is static")),
}
}
}

View file

@ -1,6 +1,16 @@
import 'package:example/base.dart';
export 'package:example/public.dart';
part 'generated.dart';
mixin Logging {}
abstract class Controller extends Base with Logging implements Runnable {
Controller.named();
String get label => 'controller';
set label(String value) {}
class Controller extends Base {
void start() {
helper();
}

View file

@ -18,10 +18,10 @@ fn first_class_language_fixtures_have_stable_baseline_evidence() {
FixtureCase {
directory: "dart",
language: "dart",
classes: &["Controller"],
functions: &["helper", "start"],
imports: 1,
inherits: 1,
classes: &["Controller", "Logging"],
functions: &["Controller.named", "helper", "label", "label", "start"],
imports: 3,
inherits: 3,
calls: 1,
},
FixtureCase {