1use crate::ast::{BinaryOp, Expr, Literal, TypeSpecifier};
23use crate::model::{FhirType, Model, field_type, is_a, named_type, specializes, variant_type};
24use fhir_types::schema::Kind;
25
26#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
28pub enum SemanticError {
29 #[error("{type_name} has no element {element}")]
31 UnknownElement {
32 type_name: String,
34 element: String,
36 },
37 #[error("the expression names the type {named}, the context is a {context}")]
39 ContextType {
40 named: String,
42 context: String,
44 },
45 #[error("{name} names no type of the model")]
47 UnknownType {
48 name: String,
50 },
51 #[error("{function}() takes a Boolean criterion, the expression gives a literal {found}")]
54 Criterion {
55 function: &'static str,
57 found: &'static str,
59 },
60 #[error("{function}() takes no {type_name}")]
63 Input {
64 function: &'static str,
66 type_name: String,
68 },
69}
70
71type Reach = Option<Vec<FhirType>>;
74
75pub fn check(expr: &Expr, model: &dyn Model, context: FhirType) -> Result<(), SemanticError> {
81 check_any(expr, model, &[context])
82}
83
84pub fn check_any(
92 expr: &Expr,
93 model: &dyn Model,
94 contexts: &[FhirType],
95) -> Result<(), SemanticError> {
96 let reach = Some(contexts.to_vec());
99 Checker { model }.infer(expr, &reach, &reach).map(|_| ())
100}
101
102struct Checker<'m> {
104 model: &'m dyn Model,
105}
106
107fn union(a: Reach, b: Reach) -> Reach {
109 let (mut a, b) = (a?, b?);
110 for ty in b {
111 if !a.contains(&ty) {
112 a.push(ty);
113 }
114 }
115 Some(a)
116}
117
118impl Checker<'_> {
119 fn infer(&self, expr: &Expr, focus: &Reach, this: &Reach) -> Result<Reach, SemanticError> {
121 match expr {
122 Expr::Literal(_) | Expr::Empty | Expr::External(_) | Expr::Index | Expr::Total => {
123 Ok(None)
124 }
125 Expr::This => Ok(this.clone()),
126 Expr::Member { input, name } => {
127 let from = match input {
128 Some(input) => self.infer(input, focus, this)?,
129 None => focus.clone(),
130 };
131 self.member(&from, name, input.is_none())
132 }
133 Expr::Call { input, name, args } => {
134 let from = match input {
135 Some(input) => self.infer(input, focus, this)?,
136 None => focus.clone(),
137 };
138 self.call(&from, name, args, focus, this)
139 }
140 Expr::Invocation { input, .. } => {
141 self.infer(input, focus, this)?;
142 Ok(None)
143 }
144 Expr::Indexer { input, index } => {
145 self.infer(index, focus, this)?;
146 self.infer(input, focus, this)
147 }
148 Expr::Negate(operand) | Expr::Plus(operand) => {
149 self.infer(operand, focus, this)?;
150 Ok(None)
151 }
152 Expr::Binary { op, left, right } => {
153 let a = self.infer(left, focus, this)?;
154 let b = match op {
155 BinaryOp::And | BinaryOp::Implies => self.infer(
156 right,
157 &self.narrow(left, true, focus),
158 &self.narrow(left, true, this),
159 )?,
160 BinaryOp::Or => self.infer(
161 right,
162 &self.narrow(left, false, focus),
163 &self.narrow(left, false, this),
164 )?,
165 _ => self.infer(right, focus, this)?,
166 };
167 Ok(if *op == BinaryOp::Union {
168 union(a, b)
169 } else {
170 None
171 })
172 }
173 Expr::Is {
174 operand,
175 type_specifier,
176 } => {
177 self.infer(operand, focus, this)?;
178 self.resolve(type_specifier)?;
179 Ok(None)
180 }
181 Expr::As {
182 operand,
183 type_specifier,
184 } => {
185 self.infer(operand, focus, this)?;
186 self.resolve(type_specifier)
187 }
188 Expr::SortKey { key, .. } => self.infer(key, focus, this),
189 }
190 }
191
192 fn resolve(&self, specifier: &TypeSpecifier) -> Result<Reach, SemanticError> {
194 let system = crate::value::SYSTEM_TYPES.contains(&specifier.name.as_str());
195 let fhir = || named_type(self.model, &specifier.name);
196 match specifier.namespace.as_deref() {
197 None | Some("FHIR") if fhir().is_some() => Ok(fhir().map(|ty| vec![ty])),
198 None | Some("System") if system => Ok(None),
199 _ => Err(SemanticError::UnknownType {
200 name: specifier.to_string(),
201 }),
202 }
203 }
204
205 fn narrow(&self, guard: &Expr, assume: bool, reach: &Reach) -> Reach {
211 match guard {
212 Expr::Is {
213 operand,
214 type_specifier,
215 } if matches!(operand.as_ref(), Expr::This) => {
216 self.narrow_to(type_specifier, assume, reach)
217 }
218 Expr::Call { input, name, args }
219 if name == "is" && input.as_deref().is_none_or(|i| matches!(i, Expr::This)) =>
220 {
221 match args.as_slice() {
222 [arg] => match arg.as_type_specifier() {
223 Some(specifier) => self.narrow_to(&specifier, assume, reach),
224 None => reach.clone(),
225 },
226 _ => reach.clone(),
227 }
228 }
229 Expr::Call {
230 input: Some(inner),
231 name,
232 args,
233 } if name == "not" && args.is_empty() => self.narrow(inner, !assume, reach),
234 Expr::Binary {
235 op: BinaryOp::And,
236 left,
237 right,
238 } => {
239 if assume {
240 self.narrow(right, true, &self.narrow(left, true, reach))
241 } else {
242 union(
243 self.narrow(left, false, reach),
244 self.narrow(right, false, reach),
245 )
246 }
247 }
248 Expr::Binary {
249 op: BinaryOp::Or,
250 left,
251 right,
252 } => {
253 if assume {
254 union(
255 self.narrow(left, true, reach),
256 self.narrow(right, true, reach),
257 )
258 } else {
259 self.narrow(right, false, &self.narrow(left, false, reach))
260 }
261 }
262 _ => reach.clone(),
263 }
264 }
265
266 fn narrow_to(&self, specifier: &TypeSpecifier, assume: bool, reach: &Reach) -> Reach {
269 let types = reach.as_ref()?;
270 let Ok(Some(named)) = self.resolve(specifier) else {
271 return reach.clone();
272 };
273 let Some(target) = named.first() else {
274 return reach.clone();
275 };
276 let mut kept: Vec<FhirType> = Vec::new();
277 for ty in types {
278 let found = if specializes(self.model, ty, target.name) {
279 assume.then_some(*ty)
280 } else if assume && specializes(self.model, target, ty.name) {
281 Some(*target)
282 } else if assume {
283 None
284 } else {
285 Some(*ty)
286 };
287 if let Some(found) = found
288 && !kept.contains(&found)
289 {
290 kept.push(found);
291 }
292 }
293 Some(kept)
294 }
295
296 fn member(&self, from: &Reach, name: &str, at_root: bool) -> Result<Reach, SemanticError> {
298 let Some(types) = from else {
299 return Ok(None);
300 };
301 if types.is_empty() {
302 return Ok(Some(Vec::new()));
303 }
304 let mut reached: Vec<FhirType> = Vec::new();
305 let mut known = true;
306 let mut defined = false;
307 for ty in types {
308 if at_root && !ty.is_primitive() && is_a(self.model, ty, name) {
309 defined = true;
310 reached.push(*ty);
311 continue;
312 }
313 let schema = match ty.schema() {
314 Some(schema) => Some(schema),
315 None => self.model.schemas().type_named("Element"),
316 };
317 let Some(field) =
318 schema.and_then(|schema| schema.fields.iter().find(|field| field.name == name))
319 else {
320 continue;
321 };
322 defined = true;
323 match field.kind {
324 Kind::Choice(variants) => {
325 for (suffix, kind) in variants {
326 match variant_type(self.model, field, suffix, *kind) {
327 Some(found) if !reached.contains(&found) => reached.push(found),
328 Some(_) => {}
329 None => known = false,
330 }
331 }
332 }
333 kind => match field_type(self.model, field, kind) {
334 Some(found) if !reached.contains(&found) => reached.push(found),
335 Some(_) => {}
336 None => known = false,
337 },
338 }
339 }
340 if !defined {
341 let names = types
342 .iter()
343 .map(|ty| ty.name)
344 .collect::<Vec<_>>()
345 .join(" | ");
346 if at_root && named_type(self.model, name).is_some() {
347 return Err(SemanticError::ContextType {
348 named: String::from(name),
349 context: names,
350 });
351 }
352 return Err(SemanticError::UnknownElement {
353 type_name: names,
354 element: String::from(name),
355 });
356 }
357 Ok(known.then_some(reached))
358 }
359
360 fn call(
362 &self,
363 from: &Reach,
364 name: &str,
365 args: &[Expr],
366 focus: &Reach,
367 this: &Reach,
368 ) -> Result<Reach, SemanticError> {
369 let lambdas: &[usize] = match name {
370 "where" | "exists" | "all" | "select" | "repeat" | "aggregate" => &[0],
371 "trace" => &[1],
372 "iif" => &[0, 1, 2],
373 _ => &[],
374 };
375 if name == "iif"
376 && let Some(Expr::Literal(literal)) = args.first()
377 {
378 let found = match literal {
379 Literal::Boolean(_) => None,
380 Literal::String(_) => Some("String"),
381 Literal::Integer(_) => Some("Integer"),
382 Literal::Decimal(_) => Some("Decimal"),
383 Literal::Date(_) => Some("Date"),
384 Literal::DateTime(_) => Some("DateTime"),
385 Literal::Time(_) => Some("Time"),
386 Literal::Quantity(..) => Some("Quantity"),
387 };
388 if let Some(found) = found {
389 return Err(SemanticError::Criterion {
390 function: "iif",
391 found,
392 });
393 }
394 }
395 if name == "toString"
396 && let Some(types) = from
397 && !types.is_empty()
398 && types
399 .iter()
400 .all(|ty| !ty.is_primitive() && !is_a(self.model, ty, "Quantity"))
401 {
402 return Err(SemanticError::Input {
405 function: "toString",
406 type_name: types
407 .iter()
408 .map(|ty| ty.name)
409 .collect::<Vec<_>>()
410 .join(" | "),
411 });
412 }
413 let mut outputs = Vec::new();
414 for (index, arg) in args.iter().enumerate() {
415 let typed_argument = matches!(name, "ofType" | "as" | "is") && index == 0;
416 if typed_argument {
417 if let Some(specifier) = arg.as_type_specifier() {
418 self.resolve(&specifier)?;
419 }
420 continue;
421 }
422 let reach = if name == "iif" && index > 0 {
423 let guarded = match args.first() {
424 Some(criterion) => self.narrow(criterion, index == 1, from),
425 None => from.clone(),
426 };
427 self.infer(arg, &guarded, &guarded)?
428 } else if lambdas.contains(&index) || name == "sort" {
429 self.infer(arg, from, from)?
430 } else {
431 self.infer(arg, focus, this)?
432 };
433 outputs.push(reach);
434 }
435 let first = outputs.first().cloned().flatten();
436 Ok(match name {
437 "where" => match args.first() {
438 Some(criterion) => self.narrow(criterion, true, from),
439 None => from.clone(),
440 },
441 "first" | "last" | "tail" | "skip" | "take" | "single" | "distinct" | "intersect"
442 | "exclude" | "trace" | "checkModifiers" | "slice" | "sort" => from.clone(),
443 "select" => first,
444 "union" | "combine" => union(from.clone(), first),
445 "extension" => named_type(self.model, "Extension").map(|ty| vec![ty]),
446 "ofType" | "as" => match args.first().and_then(Expr::as_type_specifier) {
447 Some(specifier) => self.resolve(&specifier)?,
448 None => None,
449 },
450 _ => None,
451 })
452 }
453}