protobuf/reflect/message/
is_initialized_is_always_true.rs

1use std::collections::HashMap;
2use std::collections::HashSet;
3
4use crate::descriptor::field_descriptor_proto::Label;
5use crate::descriptor::FileDescriptorProto;
6use crate::reflect::field::index::FieldIndex;
7use crate::reflect::field::index::ForwardProtobufFieldType;
8use crate::reflect::field::index::ForwardProtobufTypeBox;
9use crate::reflect::file::index::MessageIndices;
10use crate::reflect::MessageDescriptor;
11use crate::reflect::RuntimeType;
12use crate::reflect::Syntax;
13
14pub(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
80enum MessageType<'m> {
81    ThisFile(usize),
82    OtherFile(&'m MessageDescriptor),
83}
84
85fn 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
99fn 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
130fn 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}