1 use std::collections::HashMap;
2 use std::collections::HashSet;
3
4 use crate::descriptor::field_descriptor_proto::Label;
5 use crate::descriptor::FileDescriptorProto;
6 use crate::reflect::field::index::FieldIndex;
7 use crate::reflect::field::index::ForwardProtobufFieldType;
8 use crate::reflect::field::index::ForwardProtobufTypeBox;
9 use crate::reflect::file::index::MessageIndices;
10 use crate::reflect::MessageDescriptor;
11 use crate::reflect::RuntimeType;
12 use crate::reflect::Syntax;
13
compute_is_initialized_is_always_true( messages: &mut [MessageIndices], file_fields: &[FieldIndex], file: &FileDescriptorProto, )14 pub(crate) fn compute_is_initialized_is_always_true(
15 messages: &mut [MessageIndices],
16 file_fields: &[FieldIndex],
17 file: &FileDescriptorProto,
18 ) {
19 for message in messages.iter_mut() {
20 message.is_initialized_is_always_true =
21 is_initialized_is_always_true_ignoring_deps(message, file);
22 }
23
24 // Map from a message to messages who include it. E.g. for:
25 // ```
26 // 0: message A {}
27 // 1: message B { A a = 10; }
28 // ```
29 // This map will contain: `{0: [1]}`
30 let mut rdeps: HashMap<usize, Vec<usize>> = HashMap::new();
31
32 for i in 0..messages.len() {
33 let message = &mut messages[i];
34
35 if !message.is_initialized_is_always_true {
36 continue;
37 }
38
39 let mut is_initialized_is_always_true = true;
40 for ft in message_field_messages(message, file_fields) {
41 match ft {
42 MessageType::ThisFile(j) => {
43 rdeps.entry(j).or_default().push(i);
44 }
45 MessageType::OtherFile(m) => {
46 if !m.is_initialized_is_always_true() {
47 is_initialized_is_always_true = false;
48 }
49 }
50 }
51 }
52 message.is_initialized_is_always_true = is_initialized_is_always_true;
53 }
54
55 let mut invalidated: HashSet<usize> = HashSet::new();
56 let mut invalidate_stack: Vec<usize> = Vec::new();
57
58 for i in 0..messages.len() {
59 let message = &messages[i];
60 if message.is_initialized_is_always_true {
61 continue;
62 }
63
64 invalidate_stack.push(i);
65 }
66
67 while let Some(i) = invalidate_stack.pop() {
68 if !invalidated.insert(i) {
69 continue;
70 }
71
72 messages[i].is_initialized_is_always_true = false;
73 let next = rdeps.get(&i).map(|v| v.as_slice()).unwrap_or_default();
74 for next in next {
75 invalidate_stack.push(*next);
76 }
77 }
78 }
79
80 enum MessageType<'m> {
81 ThisFile(usize),
82 OtherFile(&'m MessageDescriptor),
83 }
84
message_field_messages<'a>( message: &'a MessageIndices, file_fields: &'a [FieldIndex], ) -> impl Iterator<Item = MessageType<'a>> + 'a85 fn message_field_messages<'a>(
86 message: &'a MessageIndices,
87 file_fields: &'a [FieldIndex],
88 ) -> impl Iterator<Item = MessageType<'a>> + 'a {
89 message_field_types(message, file_fields).filter_map(|f| match f {
90 ForwardProtobufTypeBox::ProtobufTypeBox(t) => match t.runtime() {
91 RuntimeType::Message(m) => Some(MessageType::OtherFile(m)),
92 _ => None,
93 },
94 ForwardProtobufTypeBox::CurrentFileEnum(_) => None,
95 ForwardProtobufTypeBox::CurrentFileMessage(i) => Some(MessageType::ThisFile(*i)),
96 })
97 }
98
message_field_types<'a>( message: &'a MessageIndices, file_fields: &'a [FieldIndex], ) -> impl Iterator<Item = &'a ForwardProtobufTypeBox>99 fn message_field_types<'a>(
100 message: &'a MessageIndices,
101 file_fields: &'a [FieldIndex],
102 ) -> impl Iterator<Item = &'a ForwardProtobufTypeBox> {
103 enum Either<A, B> {
104 Left(A),
105 Right(B),
106 }
107
108 impl<T, A: Iterator<Item = T>, B: Iterator<Item = T>> Iterator for Either<A, B> {
109 type Item = T;
110
111 fn next(&mut self) -> Option<T> {
112 match self {
113 Either::Left(a) => a.next(),
114 Either::Right(b) => b.next(),
115 }
116 }
117 }
118
119 message
120 .message_index
121 .slice_fields(file_fields)
122 .iter()
123 .flat_map(|f| match &f.field_type {
124 ForwardProtobufFieldType::Singular(t) => Either::Left([t].into_iter()),
125 ForwardProtobufFieldType::Repeated(t) => Either::Left([t].into_iter()),
126 ForwardProtobufFieldType::Map(k, v) => Either::Right([k, v].into_iter()),
127 })
128 }
129
is_initialized_is_always_true_ignoring_deps( message: &MessageIndices, file: &FileDescriptorProto, ) -> bool130 fn is_initialized_is_always_true_ignoring_deps(
131 message: &MessageIndices,
132 file: &FileDescriptorProto,
133 ) -> bool {
134 // Shortcut.
135 if Syntax::of_file(file) == Syntax::Proto3 {
136 return true;
137 }
138
139 // We don't support extensions properly but if we did,
140 // extensions should have been checked for `is_initialized`.
141 if !message.proto.extension_range.is_empty() {
142 return false;
143 }
144
145 for field in &message.proto.field {
146 if field.label() == Label::LABEL_REQUIRED {
147 return false;
148 }
149 }
150 true
151 }
152