telltale_language/compiler/
projection.rs1mod merge;
4mod ops;
5
6use crate::ast::{Branch, Choreography, LocalType, MessageType, Protocol, Role, RoleParam};
7use std::collections::HashMap;
8
9pub use merge::merge_local_types;
10
11pub fn project(choreography: &Choreography, role: &Role) -> Result<LocalType, ProjectionError> {
13 let mut context = ProjectionContext::new(choreography, role);
14 context.project_protocol(&choreography.protocol)
15}
16
17#[derive(Debug, thiserror::Error)]
19pub enum ProjectionError {
20 #[error("cannot project choice for non-participant role")]
21 NonParticipantChoice,
22
23 #[error("parallel composition not supported for role {0}")]
24 UnsupportedParallel(String),
25
26 #[error("inconsistent projections in parallel branches")]
27 InconsistentParallel,
28
29 #[error("recursive variable {0} not in scope")]
30 UnboundVariable(String),
31
32 #[error("dynamic role {role} requires runtime context for projection")]
33 DynamicRoleProjection { role: String },
34
35 #[error("symbolic role parameter '{param}' not bound in context")]
36 UnboundSymbolic { param: String },
37
38 #[error("range role index cannot be projected to concrete local type")]
39 RangeProjection,
40
41 #[error("wildcard role index requires specialized projection context")]
42 WildcardProjection,
43
44 #[error("cannot merge branches: {0}")]
45 MergeFailure(String),
46
47 #[error("authority-local construct `{construct}` is not projectable without an explicit session-typing rule")]
48 UnsupportedAuthorityConstruct { construct: &'static str },
49}
50
51struct ProjectionContext<'a> {
53 role: &'a Role,
54 role_bindings: HashMap<String, u32>,
56}
57
58impl<'a> ProjectionContext<'a> {
59 fn new(_choreography: &'a Choreography, role: &'a Role) -> Self {
60 ProjectionContext {
61 role,
62 role_bindings: HashMap::new(),
63 }
64 }
65
66 fn role_matches(&self, protocol_role: &Role) -> Result<bool, ProjectionError> {
68 if self.role.name() != protocol_role.name() {
70 return Ok(false);
71 }
72
73 if !self.role.is_parameterized() && !protocol_role.is_parameterized() {
75 return Ok(true);
76 }
77
78 self.matches_dynamic_role(protocol_role)
80 }
81
82 fn matches_dynamic_role(&self, protocol_role: &Role) -> Result<bool, ProjectionError> {
84 match (self.role.param(), protocol_role.param()) {
85 (Some(RoleParam::Static(self_count)), Some(RoleParam::Static(proto_count))) => {
87 Ok(self_count == proto_count)
88 }
89 (Some(RoleParam::Static(self_count)), Some(RoleParam::Symbolic(sym_name))) => {
91 if let Some(&resolved_count) = self.role_bindings.get(sym_name) {
92 Ok(*self_count == resolved_count)
93 } else {
94 Err(ProjectionError::UnboundSymbolic {
95 param: sym_name.clone(),
96 })
97 }
98 }
99 (Some(RoleParam::Symbolic(sym_name)), Some(RoleParam::Static(proto_count))) => {
101 if let Some(&resolved_count) = self.role_bindings.get(sym_name) {
102 Ok(resolved_count == *proto_count)
103 } else {
104 Err(ProjectionError::UnboundSymbolic {
105 param: sym_name.clone(),
106 })
107 }
108 }
109 (Some(RoleParam::Symbolic(self_sym)), Some(RoleParam::Symbolic(proto_sym))) => {
111 let self_resolved = self.role_bindings.get(self_sym).ok_or_else(|| {
112 ProjectionError::UnboundSymbolic {
113 param: self_sym.clone(),
114 }
115 })?;
116 let proto_resolved = self.role_bindings.get(proto_sym).ok_or_else(|| {
117 ProjectionError::UnboundSymbolic {
118 param: proto_sym.clone(),
119 }
120 })?;
121 Ok(self_resolved == proto_resolved)
122 }
123 (_, Some(RoleParam::Runtime)) | (Some(RoleParam::Runtime), _) => {
125 Err(ProjectionError::DynamicRoleProjection {
126 role: protocol_role.name().to_string(),
127 })
128 }
129 (Some(_), None) | (None, Some(_)) => Ok(false),
131 (None, None) => Ok(true),
133 }
134 }
135
136 fn project_protocol(&mut self, protocol: &Protocol) -> Result<LocalType, ProjectionError> {
137 match protocol {
138 Protocol::Begin { .. } => {
139 Err(ProjectionError::UnsupportedAuthorityConstruct { construct: "begin" })
140 }
141
142 Protocol::Await { .. } => {
143 Err(ProjectionError::UnsupportedAuthorityConstruct { construct: "await" })
144 }
145
146 Protocol::Resolve { .. } => Err(ProjectionError::UnsupportedAuthorityConstruct {
147 construct: "resolve",
148 }),
149
150 Protocol::Invalidate { .. } => Err(ProjectionError::UnsupportedAuthorityConstruct {
151 construct: "invalidate",
152 }),
153
154 Protocol::Send {
155 from,
156 to,
157 message,
158 continuation,
159 ..
160 } => self.project_send(from, to, message, continuation),
161
162 Protocol::Broadcast {
163 from,
164 to_all,
165 message,
166 continuation,
167 ..
168 } => self.project_broadcast(from, to_all, message, continuation),
169
170 Protocol::Choice {
171 role: choice_role,
172 branches,
173 ..
174 } => self.project_choice(choice_role, branches),
175
176 Protocol::Let { continuation, .. } => self.project_protocol(continuation),
177
178 Protocol::Case { .. } => Err(ProjectionError::UnsupportedAuthorityConstruct {
179 construct: "case/of",
180 }),
181
182 Protocol::Timeout { .. } => Err(ProjectionError::UnsupportedAuthorityConstruct {
183 construct: "timeout",
184 }),
185
186 Protocol::Loop { condition, body } => self.project_loop(condition.as_ref(), body),
187
188 Protocol::Parallel { protocols } => self.project_parallel(protocols),
189
190 Protocol::Rec { label, body } => self.project_rec(label, body),
191
192 Protocol::Var(label) => self.project_var(label),
193
194 Protocol::Publish { .. } => Err(ProjectionError::UnsupportedAuthorityConstruct {
195 construct: "publish",
196 }),
197
198 Protocol::PublishAuthority { .. } => {
199 Err(ProjectionError::UnsupportedAuthorityConstruct {
200 construct: "publish as",
201 })
202 }
203
204 Protocol::Materialize { .. } => Err(ProjectionError::UnsupportedAuthorityConstruct {
205 construct: "materialize",
206 }),
207
208 Protocol::Handoff { .. } => Err(ProjectionError::UnsupportedAuthorityConstruct {
209 construct: "handoff",
210 }),
211
212 Protocol::DependentWork { .. } => Err(ProjectionError::UnsupportedAuthorityConstruct {
213 construct: "dependent work",
214 }),
215
216 Protocol::End => Ok(LocalType::End),
217
218 Protocol::Extension {
219 extension: _,
220 continuation,
221 annotations: _,
222 } => {
223 self.project_protocol(continuation)
226 }
227 }
228 }
229
230 fn project_send(
240 &mut self,
241 from: &Role,
242 to: &Role,
243 message: &MessageType,
244 continuation: &Protocol,
245 ) -> Result<LocalType, ProjectionError> {
246 let is_sender = self.role_matches(from)?;
247 let is_receiver = self.role_matches(to)?;
248
249 if is_sender {
250 Ok(LocalType::Send {
252 to: to.clone(),
253 message: message.clone(),
254 continuation: Box::new(self.project_protocol(continuation)?),
255 })
256 } else if is_receiver {
257 Ok(LocalType::Receive {
259 from: from.clone(),
260 message: message.clone(),
261 continuation: Box::new(self.project_protocol(continuation)?),
262 })
263 } else {
264 self.project_protocol(continuation)
266 }
267 }
268
269 fn project_broadcast(
281 &mut self,
282 from: &Role,
283 to_all: &[Role],
284 message: &MessageType,
285 continuation: &Protocol,
286 ) -> Result<LocalType, ProjectionError> {
287 let is_sender = self.role_matches(from)?;
288
289 let mut is_receiver = false;
291 for to_role in to_all {
292 if self.role_matches(to_role)? {
293 is_receiver = true;
294 break;
295 }
296 }
297
298 if is_sender {
299 let mut current = self.project_protocol(continuation)?;
301
302 for to in to_all.iter().rev() {
304 current = LocalType::Send {
305 to: to.clone(),
306 message: message.clone(),
307 continuation: Box::new(current),
308 };
309 }
310
311 Ok(current)
312 } else if is_receiver {
313 Ok(LocalType::Receive {
315 from: from.clone(),
316 message: message.clone(),
317 continuation: Box::new(self.project_protocol(continuation)?),
318 })
319 } else {
320 self.project_protocol(continuation)
322 }
323 }
324
325 fn project_choice(
338 &mut self,
339 choice_role: &Role,
340 branches: &[Branch],
341 ) -> Result<LocalType, ProjectionError> {
342 let is_choice_maker = self.role_matches(choice_role)?;
343
344 if is_choice_maker {
345 let first_sends = branches
348 .iter()
349 .all(|b| matches!(&b.protocol, Protocol::Send { .. }));
350
351 if first_sends && !branches.is_empty() {
352 let mut local_branches = Vec::new();
359
360 for branch in branches {
361 let label_matches_msg = match &branch.protocol {
362 Protocol::Send { message, .. } => branch.label == message.name,
363 _ => false,
364 };
365
366 let local_type = if label_matches_msg {
367 match &branch.protocol {
368 Protocol::Send { continuation, .. } => {
369 self.project_protocol(continuation)?
370 }
371 _ => return Err(ProjectionError::NonParticipantChoice),
372 }
373 } else {
374 self.project_protocol(&branch.protocol)?
375 };
376 local_branches.push((branch.label.clone(), local_type));
377 }
378
379 let recipient = match &branches[0].protocol {
381 Protocol::Send { to, .. } => to.clone(),
382 _ => {
383 return Err(ProjectionError::NonParticipantChoice);
384 }
385 };
386
387 Ok(LocalType::Select {
388 to: recipient,
389 branches: local_branches,
390 })
391 } else {
392 let mut local_branches = Vec::new();
394
395 for branch in branches {
396 let local_type = self.project_protocol(&branch.protocol)?;
397 local_branches.push((branch.label.clone(), local_type));
398 }
399
400 Ok(LocalType::LocalChoice {
401 branches: local_branches,
402 })
403 }
404 } else {
405 let mut receives_choice = false;
407 let mut sender = None;
408
409 for branch in branches {
410 if let Protocol::Send { from, to, .. } = &branch.protocol {
411 if self.role_matches(to)? {
412 receives_choice = true;
413 sender = Some(from.clone());
414 break;
415 }
416 }
417 }
418
419 if receives_choice {
420 let sender = sender.ok_or(ProjectionError::NonParticipantChoice)?;
427 let mut local_branches = Vec::new();
428
429 for branch in branches {
430 let label_matches_msg = match &branch.protocol {
431 Protocol::Send { message, .. } => branch.label == message.name,
432 _ => false,
433 };
434
435 let local_type = if label_matches_msg {
436 match &branch.protocol {
437 Protocol::Send { continuation, .. } => {
438 self.project_protocol(continuation)?
439 }
440 _ => self.project_protocol(&branch.protocol)?,
441 }
442 } else {
443 self.project_protocol(&branch.protocol)?
444 };
445 local_branches.push((branch.label.clone(), local_type));
446 }
447
448 Ok(LocalType::Branch {
449 from: sender,
450 branches: local_branches,
451 })
452 } else {
453 self.merge_choice_continuations(branches)
455 }
456 }
457 }
458
459 fn project_loop(
470 &mut self,
471 condition: Option<&crate::ast::protocol::Condition>,
472 body: &Protocol,
473 ) -> Result<LocalType, ProjectionError> {
474 let body_projection = self.project_protocol(body)?;
475
476 if body_projection == LocalType::End {
478 Ok(LocalType::End)
479 } else {
480 Ok(LocalType::Loop {
481 condition: condition.cloned(),
482 body: Box::new(body_projection),
483 })
484 }
485 }
486
487 fn project_parallel(&mut self, protocols: &[Protocol]) -> Result<LocalType, ProjectionError> {
501 let mut projections = Vec::new();
503 for protocol in protocols {
504 if protocol.mentions_role(self.role) {
505 projections.push(self.project_protocol(protocol)?);
506 }
507 }
508
509 match projections.len() {
510 0 => {
511 Ok(LocalType::End)
513 }
514 1 => {
515 Ok(projections.into_iter().next().unwrap_or(LocalType::End))
517 }
518 _ => {
519 self.merge_parallel_projections(projections)
522 }
523 }
524 }
525}