mirror of
https://github.com/storytold/craft-anything.git
synced 2026-11-02 18:06:58 +00:00
Treat explicit interface property accessors as implicit (#443)
Explicit interface property accessors (Interface.get_X) count as implicit once their properties match, instead of showing as missing. Fixes #347.
This commit is contained in:
@@ -787,6 +787,23 @@ fn compiler_generated(name: &str) -> bool {
|
||||
name.contains('<') || name.contains('$') || name.starts_with("CS$")
|
||||
}
|
||||
|
||||
/// An accessor (`get_P`, `add_E`…) of a property or event in `props`. Explicit interface
|
||||
/// implementations keep the qualifier in front: `Iface.get_P` belongs to `Iface.P`.
|
||||
fn is_accessor(name: &str, props: &BTreeSet<&str>) -> bool {
|
||||
let (qualifier, local) = match name.rsplit_once('.') {
|
||||
Some((q, l)) => (Some(q), l),
|
||||
None => (None, name),
|
||||
};
|
||||
if !["get_", "set_", "add_", "remove_", "raise_"].iter().any(|p| local.starts_with(p)) {
|
||||
return false;
|
||||
}
|
||||
let Some((_, prop)) = local.split_once('_') else { return false };
|
||||
match qualifier {
|
||||
Some(q) => props.contains(format!("{q}.{prop}").as_str()),
|
||||
None => props.contains(prop),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check scanned declarations against metadata members.
|
||||
pub fn reconcile(members: &Members, scan: &CsScan) -> Reconciliation {
|
||||
let mut r = Reconciliation::default();
|
||||
@@ -834,8 +851,7 @@ pub fn reconcile(members: &Members, scan: &CsScan) -> Reconciliation {
|
||||
} else if compiler_generated(name)
|
||||
|| name == ".cctor"
|
||||
|| (name == ".ctor" && params.is_empty())
|
||||
|| (["get_", "set_", "add_", "remove_", "raise_"].iter().any(|p| name.starts_with(p))
|
||||
&& name.split_once('_').is_some_and(|(_, n)| props.contains(n)))
|
||||
|| is_accessor(name, &props)
|
||||
|| (ty.kind == "delegate" && ["Invoke", "BeginInvoke", "EndInvoke", ".ctor"].contains(&name))
|
||||
|| (ty.kind == "enum" && name == "value__")
|
||||
{
|
||||
@@ -883,6 +899,7 @@ pub fn reconcile(members: &Members, scan: &CsScan) -> Reconciliation {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::dotnet::model::{MethodInfo, PropertyInfo, TypeInfo};
|
||||
|
||||
#[test]
|
||||
fn scans_declarations() {
|
||||
@@ -939,6 +956,69 @@ public sealed class Box<T> : IDisposable where T : class
|
||||
assert_eq!(norm_type_name("Ns.Outer/Inner`1"), ("Ns.Outer.Inner".into(), 1));
|
||||
}
|
||||
|
||||
fn method(name: &str) -> MethodInfo {
|
||||
MethodInfo {
|
||||
token: String::new(),
|
||||
name: name.into(),
|
||||
key: String::new(),
|
||||
signature: name.into(),
|
||||
return_type: String::new(),
|
||||
params: vec![],
|
||||
generic_params: vec![],
|
||||
visibility: "private",
|
||||
modifiers: vec![],
|
||||
code_type: "il",
|
||||
internal_call: false,
|
||||
pinvoke: false,
|
||||
rva: 0,
|
||||
body: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn ty(name: &str, kind: &'static str, methods: &[&str], props: &[&str]) -> TypeInfo {
|
||||
TypeInfo {
|
||||
token: String::new(),
|
||||
full_name: name.into(),
|
||||
namespace: String::new(),
|
||||
name: name.into(),
|
||||
kind,
|
||||
visibility: "internal",
|
||||
modifiers: vec![],
|
||||
extends: None,
|
||||
enclosing_type: None,
|
||||
generic_params: vec![],
|
||||
interfaces: vec![],
|
||||
fields: vec![],
|
||||
methods: methods.iter().map(|m| method(m)).collect(),
|
||||
properties: props.iter().map(|p| PropertyInfo { name: (*p).into(), ty: "int".into() }).collect(),
|
||||
events: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_interface_property_accessors_are_implicit() {
|
||||
let members = Members {
|
||||
types: vec![
|
||||
ty("Interface", "interface", &["get_Property", "set_Property"], &["Property"]),
|
||||
ty("Class", "class", &["Interface.get_Property", "Interface.set_Property", ".ctor", "Other.get_X"], &["Interface.Property"]),
|
||||
],
|
||||
total_types: 2,
|
||||
total_methods: 6,
|
||||
total_fields: 0,
|
||||
truncated: false,
|
||||
warnings: vec![],
|
||||
};
|
||||
let s = scan("interface Interface { int Property { get; set; } } class Class : Interface { int Interface.Property { get; set; } }");
|
||||
let r = reconcile(&members, &s);
|
||||
let missing: Vec<&str> = r.missing.iter().map(|m| m.name.as_str()).collect();
|
||||
assert_eq!(missing, vec!["Other.get_X"]);
|
||||
assert!(r.extra.is_empty(), "{:?}", r.extra);
|
||||
assert_eq!(r.matched.iter().filter(|m| m.kind == "property").count(), 2);
|
||||
let implicit: Vec<&str> = r.implicit.iter().map(|m| m.name.as_str()).collect();
|
||||
assert!(implicit.contains(&"Interface.get_Property"), "{implicit:?}");
|
||||
assert!(implicit.contains(&"Interface.set_Property"), "{implicit:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn junk_does_not_panic() {
|
||||
for src in ["", "{{{{", "}}}}", "class", "class X {", "\"unterminated", "'", "/*", "@\"", "$\"\"\"", "namespace ;", "class X { void (", "enum E { , }"]
|
||||
|
||||
Reference in New Issue
Block a user