Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

check for namespace shadowing #126

Merged
merged 3 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 56 additions & 23 deletions prost-reflect/src/descriptor/build/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
FieldType,
}

enum ResolveNameResult<'a, 'b> {
enum ResolveNameResult<'a, 'b, 'c> {
Found {
name: Cow<'b, str>,
def: &'a Definition,
Expand All @@ -52,6 +52,10 @@
name: Cow<'b, str>,
file: FileIndex,
},
Shadowed {
name: Cow<'b, str>,
shadowed_name: Cow<'c, str>,
},
NotFound,
}

Expand Down Expand Up @@ -168,7 +172,7 @@
}
}

impl<'a, 'b> ResolveNameResult<'a, 'b> {
impl<'a, 'b, 'c> ResolveNameResult<'a, 'b, 'c> {
fn new(
dependencies: &HashSet<FileIndex>,
names: &'a HashMap<Box<str>, Definition>,
Expand All @@ -192,7 +196,7 @@
}
}

fn into_owned(self) -> ResolveNameResult<'a, 'static> {
fn into_owned(self) -> ResolveNameResult<'a, 'static, 'static> {
match self {
ResolveNameResult::Found { name, def } => ResolveNameResult::Found {
name: Cow::Owned(name.into_owned()),
Expand All @@ -209,6 +213,13 @@
name: Cow::Owned(name.into_owned()),
file,
},
ResolveNameResult::Shadowed {
name,
shadowed_name,

Check warning on line 218 in prost-reflect/src/descriptor/build/mod.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/descriptor/build/mod.rs#L216-L218

Added lines #L216 - L218 were not covered by tests
} => ResolveNameResult::Shadowed {
name: Cow::Owned(name.into_owned()),
shadowed_name: Cow::Owned(shadowed_name.into_owned()),

Check warning on line 221 in prost-reflect/src/descriptor/build/mod.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/descriptor/build/mod.rs#L220-L221

Added lines #L220 - L221 were not covered by tests
},
ResolveNameResult::NotFound => ResolveNameResult::NotFound,
}
}
Expand Down Expand Up @@ -268,6 +279,19 @@
),
help: None,
}),
ResolveNameResult::Shadowed { name, shadowed_name } => Err(DescriptorErrorKind::NameShadowed {
found: Label::new(
files,
"found here",
found_file,

Check warning on line 286 in prost-reflect/src/descriptor/build/mod.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/descriptor/build/mod.rs#L284-L286

Added lines #L284 - L286 were not covered by tests
join_path(found_path1, found_path2),
),
help: Some(format!(
"'{}' is is resolved to '{}', which is not defined. The innermost scope is searched first in name resolution. Consider using a leading '.'(i.e., '.{}') to start from the outermost scope.",
name, shadowed_name, name

Check warning on line 291 in prost-reflect/src/descriptor/build/mod.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/descriptor/build/mod.rs#L290-L291

Added lines #L290 - L291 were not covered by tests
)),
name: name.into_owned(),
}),
}
}
}
Expand All @@ -290,52 +314,61 @@
result
}

fn resolve_name<'a, 'b>(
fn resolve_name<'a, 'b, 'c>(
dependencies: &HashSet<FileIndex>,
names: &'a HashMap<Box<str>, Definition>,
scope: &str,
scope: &'c str,
name: &'b str,
filter: ResolveNameFilter,
) -> ResolveNameResult<'a, 'b> {
) -> ResolveNameResult<'a, 'b, 'c> {
match name.strip_prefix('.') {
Some(full_name) => ResolveNameResult::new(dependencies, names, full_name, filter),
None if scope.is_empty() => ResolveNameResult::new(dependencies, names, name, filter),
None => resolve_relative_name(dependencies, names, scope, name, filter),
}
}

fn resolve_relative_name<'a>(
fn resolve_relative_name<'a, 'b, 'c>(
dependencies: &HashSet<FileIndex>,
names: &'a HashMap<Box<str>, Definition>,
scope: &str,
relative_name: &str,
scope: &'c str,
relative_name: &'b str,
filter: ResolveNameFilter,
) -> ResolveNameResult<'a, 'static> {
) -> ResolveNameResult<'a, 'b, 'c> {
let mut err = ResolveNameResult::NotFound;

for candidate in resolve_relative_name_candidates(scope, relative_name) {
let relative_first_part = relative_name.split('.').next();
let mut last_candidate_last_part = None;
for candidate_parent in resolve_relative_candidate_parents(scope) {
let candidate = match candidate_parent {

Check warning on line 342 in prost-reflect/src/descriptor/build/mod.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/descriptor/build/mod.rs#L342

Added line #L342 was not covered by tests
"" => Cow::Borrowed(relative_name),
_ => Cow::Owned(format!("{}.{}", candidate_parent, relative_name)),
};
let res = ResolveNameResult::new(dependencies, names, candidate, filter);
if res.is_found() {
return res.into_owned();
} else if matches!(err, ResolveNameResult::NotFound) {
err = res;
}
// Check if the name is shadowed by last parent scope, but this is not the last candidate
if Some(relative_first_part) == Some(last_candidate_last_part)
&& !candidate_parent.is_empty()
{
let shadowed_name = format!("{}.{}", candidate_parent, relative_name);
return ResolveNameResult::Shadowed {
name: Cow::Borrowed(relative_name),
shadowed_name: Cow::Owned(shadowed_name),
};
}
last_candidate_last_part = candidate_parent.rsplit('.').next();
}

err.into_owned()
}

fn resolve_relative_name_candidates<'b: 'c, 'c>(
scope: &'c str,
relative_name: &'b str,
) -> impl Iterator<Item = Cow<'b, str>> + 'c {
iter::once(Cow::Owned(format!("{scope}.{relative_name}")))
.chain(
scope
.rmatch_indices('.')
.map(move |(i, _)| Cow::Owned(format!("{}.{relative_name}", &scope[..i]))),
)
.chain(iter::once(Cow::Borrowed(relative_name)))
fn resolve_relative_candidate_parents(scope: &str) -> impl Iterator<Item = &str> {
iter::once(scope)
.chain(scope.rmatch_indices('.').map(move |(i, _)| &scope[..i]))
.chain(iter::once(""))
}

fn join_path(path1: &[i32], path2: &[i32]) -> Box<[i32]> {
Expand Down
19 changes: 17 additions & 2 deletions prost-reflect/src/descriptor/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
#[cfg_attr(not(feature = "miette"), allow(dead_code))]
help: Option<String>,
},
NameShadowed {
name: String,
found: Label,
#[cfg_attr(not(feature = "miette"), allow(dead_code))]
help: Option<String>,
},
InvalidType {
name: String,
expected: String,
Expand Down Expand Up @@ -316,6 +322,7 @@
DescriptorErrorKind::FieldNumberInExtensionRange { found, .. } => Some(found),
DescriptorErrorKind::ExtensionNumberOutOfRange { found, .. } => Some(found),
DescriptorErrorKind::NameNotFound { found, .. } => Some(found),
DescriptorErrorKind::NameShadowed { found, .. } => Some(found),

Check warning on line 325 in prost-reflect/src/descriptor/error.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/descriptor/error.rs#L325

Added line #L325 was not covered by tests
DescriptorErrorKind::InvalidType { found, .. } => Some(found),
DescriptorErrorKind::InvalidFieldDefault { found, .. } => Some(found),
DescriptorErrorKind::EmptyEnum { found } => Some(found),
Expand Down Expand Up @@ -380,6 +387,9 @@
DescriptorErrorKind::NameNotFound { found, .. } => {
found.resolve_span(file, source);
}
DescriptorErrorKind::NameShadowed { found, .. } => {
found.resolve_span(file, source);

Check warning on line 391 in prost-reflect/src/descriptor/error.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/descriptor/error.rs#L390-L391

Added lines #L390 - L391 were not covered by tests
}
DescriptorErrorKind::InvalidType { found, defined, .. } => {
found.resolve_span(file, source);
defined.resolve_span(file, source);
Expand Down Expand Up @@ -522,6 +532,9 @@
DescriptorErrorKind::NameNotFound { name, .. } => {
write!(f, "name '{}' is not defined", name)
}
DescriptorErrorKind::NameShadowed { name, .. } => {
write!(f, "name '{}' is shadowed", name)
}
DescriptorErrorKind::InvalidType { name, expected, .. } => {
write!(f, "'{}' is not {}", name, expected)
}
Expand Down Expand Up @@ -634,7 +647,8 @@
DescriptorErrorKind::FieldNumberInExtensionRange { .. } => None,
DescriptorErrorKind::DuplicateFieldJsonName { .. } => None,
DescriptorErrorKind::DuplicateFieldCamelCaseName { .. } => None,
DescriptorErrorKind::NameNotFound { help, .. } => help
DescriptorErrorKind::NameNotFound { help, .. }
| DescriptorErrorKind::NameShadowed { help, .. } => help

Check warning on line 651 in prost-reflect/src/descriptor/error.rs

View check run for this annotation

Codecov / codecov/patch

prost-reflect/src/descriptor/error.rs#L651

Added line #L651 was not covered by tests
.as_ref()
.map(|h| -> Box<dyn fmt::Display> { Box::new(h.clone()) }),
DescriptorErrorKind::InvalidType { .. } => None,
Expand Down Expand Up @@ -685,7 +699,8 @@
spans.extend(first.to_span());
spans.extend(second.to_span());
}
DescriptorErrorKind::NameNotFound { found, .. } => {
DescriptorErrorKind::NameNotFound { found, .. }
| DescriptorErrorKind::NameShadowed { found, .. } => {
spans.extend(found.to_span());
}
DescriptorErrorKind::InvalidFieldNumber { found, .. } => {
Expand Down
4 changes: 2 additions & 2 deletions prost-reflect/src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,14 @@ struct Identity {
name_index: usize,
}

#[derive(Clone)]
#[derive(Clone, Debug)]
struct Definition {
file: FileIndex,
path: Box<[i32]>,
kind: DefinitionKind,
}

#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
enum DefinitionKind {
Package,
Message(MessageIndex),
Expand Down
11 changes: 2 additions & 9 deletions prost-reflect/src/descriptor/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,8 @@ fn resolve_message_name_conflict_with_field_name() {
}],
};

let descriptor_pool = DescriptorPool::from_file_descriptor_set(file_descriptor_set).unwrap();
let message = descriptor_pool
.get_message_by_name("my.package.MyMessage")
.unwrap();
let field = message.get_field_by_name("MyMessage").unwrap();
assert_eq!(
field.kind().as_message().unwrap().full_name(),
"my.package.MyMessage"
);
let e = DescriptorPool::from_file_descriptor_set(file_descriptor_set).unwrap_err();
assert_eq!(e.to_string(), "name 'MyMessage' is shadowed");
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion prost-reflect/tests/data/enum_field_invalid_default3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ file:
messageType:
- name: Bar
field:
- name: foo
- name: foo_field
number: 1
label: LABEL_OPTIONAL
typeName: foo.Foo
Expand Down
18 changes: 18 additions & 0 deletions prost-reflect/tests/data/name_resolution4.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
file:
- name: dep.proto
package: foo.bar
messageType:
- name: FooBar
- name: root.proto
package: com.foo.bar
dependency:
- dep.proto
messageType:
- name: Foo
field:
- name: foobar
number: 1
label: LABEL_OPTIONAL
typeName: foo.FooBar
jsonName: foobar
syntax: proto3
1 change: 1 addition & 0 deletions prost-reflect/tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ check_err!(invalid_service_type3);
check_err!(name_resolution1);
check_err!(name_resolution2);
check_ok!(name_resolution3);
check_err!(name_resolution4);
check_err!(name_collision1);
check_err!(name_collision2);
check_err!(name_collision3);
Expand Down
11 changes: 11 additions & 0 deletions prost-reflect/tests/snapshots/main__name_resolution4.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: prost-reflect/tests/main.rs
assertion_line: 79
expression: actual
---
causes: []
help: "'foo.FooBar' is is resolved to 'com.foo.FooBar', which is not defined. The innermost scope is searched first in name resolution. Consider using a leading '.'(i.e., '.foo.FooBar') to start from the outermost scope."
labels: []
message: "name 'foo.FooBar' is shadowed"
related: []
severity: error
Loading