witx/
validate.rs

1use crate::{
2    io::{Filesystem, WitxIo},
3    parser::{
4        CommentSyntax, DeclSyntax, Documented, EnumSyntax, ExpectedSyntax, FlagsSyntax,
5        HandleSyntax, ImportTypeSyntax, ModuleDeclSyntax, RecordSyntax, TupleSyntax, TypedefSyntax,
6        UnionSyntax, VariantSyntax,
7    },
8    Abi, BuiltinType, Case, Constant, Definition, Document, Entry, HandleDatatype, Id, IntRepr,
9    InterfaceFunc, InterfaceFuncParam, Location, Module, ModuleDefinition, ModuleEntry,
10    ModuleImport, ModuleImportVariant, NamedType, RecordDatatype, RecordKind, RecordMember, Type,
11    TypeRef, Variant,
12};
13use std::collections::{HashMap, HashSet};
14use std::path::Path;
15use std::rc::Rc;
16use thiserror::Error;
17
18#[derive(Debug, Error)]
19pub enum ValidationError {
20    #[error("Unknown name `{name}`")]
21    UnknownName { name: String, location: Location },
22    #[error("Redefinition of name `{name}`")]
23    NameAlreadyExists {
24        name: String,
25        at_location: Location,
26        previous_location: Location,
27    },
28    #[error("Wrong kind of name `{name}`: expected {expected}, got {got}")]
29    WrongKindName {
30        name: String,
31        location: Location,
32        expected: &'static str,
33        got: &'static str,
34    },
35    #[error("Recursive definition of name `{name}`")]
36    Recursive { name: String, location: Location },
37    #[error("Invalid representation `{repr:?}`")]
38    InvalidRepr {
39        repr: BuiltinType,
40        location: Location,
41    },
42    #[error("ABI error: {reason}")]
43    Abi { reason: String, location: Location },
44    #[error("Anonymous structured types (struct, union, enum, flags, handle) are not permitted")]
45    AnonymousRecord { location: Location },
46    #[error("Union expected {expected} variants, found {found}")]
47    UnionSizeMismatch {
48        expected: usize,
49        found: usize,
50        location: Location,
51    },
52    #[error("Invalid union tag: {reason}")]
53    InvalidUnionTag { reason: String, location: Location },
54    #[error("Invalid union field `{name}`: {reason}")]
55    InvalidUnionField {
56        name: String,
57        reason: String,
58        location: Location,
59    },
60}
61
62impl ValidationError {
63    pub fn report_with(&self, witxio: &dyn WitxIo) -> String {
64        use ValidationError::*;
65        match self {
66            UnknownName { location, .. }
67            | WrongKindName { location, .. }
68            | Recursive { location, .. }
69            | InvalidRepr { location, .. }
70            | Abi { location, .. }
71            | AnonymousRecord { location, .. }
72            | UnionSizeMismatch { location, .. }
73            | InvalidUnionField { location, .. }
74            | InvalidUnionTag { location, .. } => {
75                format!("{}\n{}", location.highlight_source_with(witxio), &self)
76            }
77            NameAlreadyExists {
78                at_location,
79                previous_location,
80                ..
81            } => format!(
82                "{}\n{}\nOriginally defined at:\n{}",
83                at_location.highlight_source_with(witxio),
84                &self,
85                previous_location.highlight_source_with(witxio),
86            ),
87        }
88    }
89    pub fn report(&self) -> String {
90        self.report_with(&Filesystem)
91    }
92}
93
94struct IdentValidation {
95    names: HashMap<String, Location>,
96}
97
98impl IdentValidation {
99    fn new() -> Self {
100        Self {
101            names: HashMap::new(),
102        }
103    }
104
105    fn introduce(&mut self, syntax: &str, location: Location) -> Result<Id, ValidationError> {
106        if let Some(introduced) = self.names.get(syntax) {
107            Err(ValidationError::NameAlreadyExists {
108                name: syntax.to_string(),
109                at_location: location,
110                previous_location: introduced.clone(),
111            })
112        } else {
113            self.names.insert(syntax.to_string(), location);
114            Ok(Id::new(syntax))
115        }
116    }
117
118    fn get(&self, syntax: &str, location: Location) -> Result<Id, ValidationError> {
119        if self.names.get(syntax).is_some() {
120            Ok(Id::new(syntax))
121        } else {
122            Err(ValidationError::UnknownName {
123                name: syntax.to_string(),
124                location,
125            })
126        }
127    }
128}
129
130pub struct DocValidation {
131    scope: IdentValidation,
132    entries: HashMap<Id, Entry>,
133    constant_scopes: HashMap<Id, IdentValidation>,
134    bool_ty: TypeRef,
135}
136
137pub struct DocValidationScope<'a> {
138    doc: &'a mut DocValidation,
139    text: &'a str,
140    path: &'a Path,
141}
142
143impl DocValidation {
144    pub fn new() -> Self {
145        Self {
146            scope: IdentValidation::new(),
147            entries: HashMap::new(),
148            constant_scopes: HashMap::new(),
149            bool_ty: TypeRef::Value(Rc::new(Type::Variant(Variant {
150                tag_repr: IntRepr::U32,
151                cases: vec![
152                    Case {
153                        name: Id::new("false"),
154                        tref: None,
155                        docs: String::new(),
156                    },
157                    Case {
158                        name: Id::new("true"),
159                        tref: None,
160                        docs: String::new(),
161                    },
162                ],
163            }))),
164        }
165    }
166
167    pub fn scope<'a>(&'a mut self, text: &'a str, path: &'a Path) -> DocValidationScope<'a> {
168        DocValidationScope {
169            doc: self,
170            text,
171            path,
172        }
173    }
174
175    pub fn into_document(self, defs: Vec<Definition>) -> Document {
176        Document::new(defs, self.entries)
177    }
178}
179
180impl DocValidationScope<'_> {
181    fn location(&self, span: wast::Span) -> Location {
182        // Wast Span gives 0-indexed lines and columns. Location is 1-indexed.
183        let (line, column) = span.linecol_in(self.text);
184        Location {
185            line: line + 1,
186            column: column + 1,
187            path: self.path.to_path_buf(),
188        }
189    }
190
191    fn introduce(&mut self, name: &wast::Id<'_>) -> Result<Id, ValidationError> {
192        let loc = self.location(name.span());
193        self.doc.scope.introduce(name.name(), loc)
194    }
195
196    fn get(&self, name: &wast::Id<'_>) -> Result<Id, ValidationError> {
197        let loc = self.location(name.span());
198        self.doc.scope.get(name.name(), loc)
199    }
200
201    pub fn validate_decl(
202        &mut self,
203        decl: &DeclSyntax,
204        comments: &CommentSyntax,
205        definitions: &mut Vec<Definition>,
206    ) -> Result<(), ValidationError> {
207        match decl {
208            DeclSyntax::Typename(decl) => {
209                let name = self.introduce(&decl.ident)?;
210                let docs = comments.docs();
211                let tref = self.validate_datatype(&decl.def, true, decl.ident.span())?;
212
213                let rc_datatype = Rc::new(NamedType {
214                    name: name.clone(),
215                    tref,
216                    docs,
217                });
218                self.doc
219                    .entries
220                    .insert(name.clone(), Entry::Typename(Rc::downgrade(&rc_datatype)));
221                definitions.push(Definition::Typename(rc_datatype));
222            }
223
224            DeclSyntax::Module(syntax) => {
225                let name = self.introduce(&syntax.name)?;
226                let mut module_validator = ModuleValidation::new(self);
227                let decls = syntax
228                    .decls
229                    .iter()
230                    .map(|d| module_validator.validate_decl(&d))
231                    .collect::<Result<Vec<_>, _>>()?;
232
233                let rc_module = Rc::new(Module::new(
234                    name.clone(),
235                    decls,
236                    module_validator.entries,
237                    comments.docs(),
238                ));
239                self.doc
240                    .entries
241                    .insert(name, Entry::Module(Rc::downgrade(&rc_module)));
242                definitions.push(Definition::Module(rc_module));
243            }
244
245            DeclSyntax::Const(syntax) => {
246                let ty = Id::new(syntax.item.ty.name());
247                let loc = self.location(syntax.item.name.span());
248                let scope = self
249                    .doc
250                    .constant_scopes
251                    .entry(ty.clone())
252                    .or_insert_with(IdentValidation::new);
253                let name = scope.introduce(syntax.item.name.name(), loc)?;
254                // TODO: validate `ty` is a integer datatype that `syntax.value`
255                // fits within.
256                definitions.push(Definition::Constant(Constant {
257                    ty,
258                    name,
259                    value: syntax.item.value,
260                    docs: syntax.comments.docs(),
261                }));
262            }
263        }
264        Ok(())
265    }
266
267    fn validate_datatype(
268        &self,
269        syntax: &TypedefSyntax,
270        named: bool,
271        span: wast::Span,
272    ) -> Result<TypeRef, ValidationError> {
273        match syntax {
274            TypedefSyntax::Ident(syntax) => {
275                let i = self.get(syntax)?;
276                match self.doc.entries.get(&i) {
277                    Some(Entry::Typename(weak_ref)) => Ok(TypeRef::Name(
278                        weak_ref.upgrade().expect("weak backref to defined type"),
279                    )),
280                    Some(e) => Err(ValidationError::WrongKindName {
281                        name: i.as_str().to_string(),
282                        location: self.location(syntax.span()),
283                        expected: "datatype",
284                        got: e.kind(),
285                    }),
286                    None => Err(ValidationError::Recursive {
287                        name: i.as_str().to_string(),
288                        location: self.location(syntax.span()),
289                    }),
290                }
291            }
292            TypedefSyntax::Enum { .. }
293            | TypedefSyntax::Flags { .. }
294            | TypedefSyntax::Record { .. }
295            | TypedefSyntax::Union { .. }
296            | TypedefSyntax::Handle { .. }
297                if !named =>
298            {
299                Err(ValidationError::AnonymousRecord {
300                    location: self.location(span),
301                })
302            }
303            other => Ok(TypeRef::Value(Rc::new(match other {
304                TypedefSyntax::Enum(syntax) => Type::Variant(self.validate_enum(&syntax, span)?),
305                TypedefSyntax::Tuple(syntax) => Type::Record(self.validate_tuple(&syntax, span)?),
306                TypedefSyntax::Expected(syntax) => {
307                    Type::Variant(self.validate_expected(&syntax, span)?)
308                }
309                TypedefSyntax::Flags(syntax) => Type::Record(self.validate_flags(&syntax, span)?),
310                TypedefSyntax::Record(syntax) => Type::Record(self.validate_record(&syntax, span)?),
311                TypedefSyntax::Union(syntax) => Type::Variant(self.validate_union(&syntax, span)?),
312                TypedefSyntax::Variant(syntax) => {
313                    Type::Variant(self.validate_variant(&syntax, span)?)
314                }
315                TypedefSyntax::Handle(syntax) => Type::Handle(self.validate_handle(syntax, span)?),
316                TypedefSyntax::List(syntax) => {
317                    Type::List(self.validate_datatype(syntax, false, span)?)
318                }
319                TypedefSyntax::Pointer(syntax) => {
320                    Type::Pointer(self.validate_datatype(syntax, false, span)?)
321                }
322                TypedefSyntax::ConstPointer(syntax) => {
323                    Type::ConstPointer(self.validate_datatype(syntax, false, span)?)
324                }
325                TypedefSyntax::Builtin(builtin) => Type::Builtin(*builtin),
326                TypedefSyntax::String => {
327                    Type::List(TypeRef::Value(Rc::new(Type::Builtin(BuiltinType::Char))))
328                }
329                TypedefSyntax::Bool => return Ok(self.doc.bool_ty.clone()),
330                TypedefSyntax::Ident { .. } => unreachable!(),
331            }))),
332        }
333    }
334
335    fn validate_enum(
336        &self,
337        syntax: &EnumSyntax,
338        span: wast::Span,
339    ) -> Result<Variant, ValidationError> {
340        let mut enum_scope = IdentValidation::new();
341        let tag_repr = match &syntax.repr {
342            Some(repr) => self.validate_int_repr(repr, span)?,
343            None => IntRepr::U32,
344        };
345        let cases = syntax
346            .members
347            .iter()
348            .map(|i| {
349                let name = enum_scope.introduce(i.item.name(), self.location(i.item.span()))?;
350                let docs = i.comments.docs();
351                Ok(Case {
352                    name,
353                    tref: None,
354                    docs,
355                })
356            })
357            .collect::<Result<Vec<_>, _>>()?;
358
359        Ok(Variant { tag_repr, cases })
360    }
361
362    fn validate_tuple(
363        &self,
364        syntax: &TupleSyntax,
365        span: wast::Span,
366    ) -> Result<RecordDatatype, ValidationError> {
367        let members = syntax
368            .types
369            .iter()
370            .enumerate()
371            .map(|(i, ty)| {
372                Ok(RecordMember {
373                    name: Id::new(i.to_string()),
374                    tref: self.validate_datatype(ty, false, span)?,
375                    docs: String::new(),
376                })
377            })
378            .collect::<Result<Vec<_>, _>>()?;
379
380        Ok(RecordDatatype {
381            kind: RecordKind::Tuple,
382            members,
383        })
384    }
385
386    fn validate_expected(
387        &self,
388        syntax: &ExpectedSyntax,
389        span: wast::Span,
390    ) -> Result<Variant, ValidationError> {
391        let ok_ty = match &syntax.ok {
392            Some(ok) => Some(self.validate_datatype(ok, false, span)?),
393            None => None,
394        };
395        let err_ty = match &syntax.err {
396            Some(err) => Some(self.validate_datatype(err, false, span)?),
397            None => None,
398        };
399        Ok(Variant {
400            tag_repr: IntRepr::U32,
401            cases: vec![
402                Case {
403                    name: Id::new("ok"),
404                    tref: ok_ty,
405                    docs: String::new(),
406                },
407                Case {
408                    name: Id::new("err"),
409                    tref: err_ty,
410                    docs: String::new(),
411                },
412            ],
413        })
414    }
415
416    fn validate_flags(
417        &self,
418        syntax: &FlagsSyntax,
419        span: wast::Span,
420    ) -> Result<RecordDatatype, ValidationError> {
421        let repr = match syntax.repr {
422            Some(ty) => self.validate_int_repr(&ty, span)?,
423            None => IntRepr::U32,
424        };
425        let mut flags_scope = IdentValidation::new();
426        let mut members = Vec::new();
427        for flag in syntax.flags.iter() {
428            let name = flags_scope.introduce(flag.item.name(), self.location(flag.item.span()))?;
429            let docs = flag.comments.docs();
430            members.push(RecordMember {
431                name,
432                docs,
433                tref: self.doc.bool_ty.clone(),
434            });
435        }
436        Ok(RecordDatatype {
437            kind: RecordKind::Bitflags(repr),
438            members,
439        })
440    }
441
442    fn validate_record(
443        &self,
444        syntax: &RecordSyntax,
445        _span: wast::Span,
446    ) -> Result<RecordDatatype, ValidationError> {
447        let mut member_scope = IdentValidation::new();
448        let members = syntax
449            .fields
450            .iter()
451            .map(|f| {
452                let name = member_scope
453                    .introduce(f.item.name.name(), self.location(f.item.name.span()))?;
454                let tref = self.validate_datatype(&f.item.type_, false, f.item.name.span())?;
455                let docs = f.comments.docs();
456                Ok(RecordMember { name, tref, docs })
457            })
458            .collect::<Result<Vec<RecordMember>, _>>()?;
459
460        Ok(RecordDatatype {
461            kind: RecordKind::Other,
462            members,
463        })
464    }
465
466    fn validate_union(
467        &self,
468        syntax: &UnionSyntax,
469        span: wast::Span,
470    ) -> Result<Variant, ValidationError> {
471        let (tag_repr, names) = self.union_tag_repr(&syntax.tag, span)?;
472
473        if let Some(names) = &names {
474            if names.len() != syntax.fields.len() {
475                return Err(ValidationError::UnionSizeMismatch {
476                    expected: names.len(),
477                    found: syntax.fields.len(),
478                    location: self.location(span),
479                });
480            }
481        }
482
483        let cases = syntax
484            .fields
485            .iter()
486            .enumerate()
487            .map(|(i, case)| {
488                Ok(Case {
489                    name: match &names {
490                        Some(names) => names[i].clone(),
491                        None => Id::new(i.to_string()),
492                    },
493                    tref: Some(self.validate_datatype(&case.item, false, span)?),
494                    docs: case.comments.docs(),
495                })
496            })
497            .collect::<Result<Vec<_>, _>>()?;
498        Ok(Variant { tag_repr, cases })
499    }
500
501    fn validate_variant(
502        &self,
503        syntax: &VariantSyntax,
504        span: wast::Span,
505    ) -> Result<Variant, ValidationError> {
506        let (tag_repr, names) = self.union_tag_repr(&syntax.tag, span)?;
507
508        if let Some(names) = &names {
509            if names.len() != syntax.cases.len() {
510                return Err(ValidationError::UnionSizeMismatch {
511                    expected: names.len(),
512                    found: syntax.cases.len(),
513                    location: self.location(span),
514                });
515            }
516        }
517
518        let mut name_set = names
519            .as_ref()
520            .map(|names| names.iter().collect::<HashSet<_>>());
521
522        let mut cases = syntax
523            .cases
524            .iter()
525            .map(|case| {
526                let name = Id::new(case.item.name.name());
527                if let Some(names) = &mut name_set {
528                    if !names.remove(&name) {
529                        return Err(ValidationError::InvalidUnionField {
530                            name: name.as_str().to_string(),
531                            location: self.location(case.item.name.span()),
532                            reason: format!("does not correspond to variant in tag `tag`"),
533                        });
534                    }
535                }
536                Ok(Case {
537                    name: Id::new(case.item.name.name()),
538                    tref: match &case.item.ty {
539                        Some(ty) => {
540                            Some(self.validate_datatype(ty, false, case.item.name.span())?)
541                        }
542                        None => None,
543                    },
544                    docs: case.comments.docs(),
545                })
546            })
547            .collect::<Result<Vec<_>, _>>()?;
548
549        // If we have an explicit tag with an enum then that's instructing us to
550        // reorder cases based on the order of the enum itself, so do that here.
551        if let Some(names) = names {
552            let name_pos = names
553                .iter()
554                .enumerate()
555                .map(|(i, name)| (name, i))
556                .collect::<HashMap<_, _>>();
557            cases.sort_by_key(|c| name_pos[&&c.name]);
558        }
559
560        Ok(Variant { tag_repr, cases })
561    }
562
563    fn union_tag_repr(
564        &self,
565        tag: &Option<Box<TypedefSyntax<'_>>>,
566        span: wast::Span,
567    ) -> Result<(IntRepr, Option<Vec<Id>>), ValidationError> {
568        let ty = match tag {
569            Some(tag) => self.validate_datatype(tag, false, span)?,
570            None => return Ok((IntRepr::U32, None)),
571        };
572        match &**ty.type_() {
573            Type::Variant(e) => {
574                let mut names = Vec::new();
575                for c in e.cases.iter() {
576                    if c.tref.is_some() {
577                        return Err(ValidationError::InvalidUnionTag {
578                            location: self.location(span),
579                            reason: format!("all variant cases should have empty payloads"),
580                        });
581                    }
582                    names.push(c.name.clone());
583                }
584                return Ok((e.tag_repr, Some(names)));
585            }
586            Type::Builtin(BuiltinType::U8 { .. }) => return Ok((IntRepr::U8, None)),
587            Type::Builtin(BuiltinType::U16) => return Ok((IntRepr::U16, None)),
588            Type::Builtin(BuiltinType::U32 { .. }) => return Ok((IntRepr::U32, None)),
589            Type::Builtin(BuiltinType::U64) => return Ok((IntRepr::U64, None)),
590            _ => {}
591        }
592
593        Err(ValidationError::WrongKindName {
594            name: "tag".to_string(),
595            location: self.location(span),
596            expected: "enum or builtin",
597            got: ty.type_().kind(),
598        })
599    }
600
601    fn validate_handle(
602        &self,
603        _syntax: &HandleSyntax,
604        _span: wast::Span,
605    ) -> Result<HandleDatatype, ValidationError> {
606        Ok(HandleDatatype {})
607    }
608
609    fn validate_int_repr(
610        &self,
611        type_: &BuiltinType,
612        span: wast::Span,
613    ) -> Result<IntRepr, ValidationError> {
614        match type_ {
615            BuiltinType::U8 { .. } => Ok(IntRepr::U8),
616            BuiltinType::U16 => Ok(IntRepr::U16),
617            BuiltinType::U32 { .. } => Ok(IntRepr::U32),
618            BuiltinType::U64 => Ok(IntRepr::U64),
619            _ => Err(ValidationError::InvalidRepr {
620                repr: type_.clone(),
621                location: self.location(span),
622            }),
623        }
624    }
625}
626
627struct ModuleValidation<'a> {
628    doc: &'a DocValidationScope<'a>,
629    scope: IdentValidation,
630    pub entries: HashMap<Id, ModuleEntry>,
631}
632
633impl<'a> ModuleValidation<'a> {
634    fn new(doc: &'a DocValidationScope<'a>) -> Self {
635        Self {
636            doc,
637            scope: IdentValidation::new(),
638            entries: HashMap::new(),
639        }
640    }
641
642    fn validate_decl(
643        &mut self,
644        decl: &Documented<ModuleDeclSyntax>,
645    ) -> Result<ModuleDefinition, ValidationError> {
646        match &decl.item {
647            ModuleDeclSyntax::Import(syntax) => {
648                let loc = self.doc.location(syntax.name_loc);
649                let name = self.scope.introduce(syntax.name, loc)?;
650                let variant = match syntax.type_ {
651                    ImportTypeSyntax::Memory => ModuleImportVariant::Memory,
652                };
653                let rc_import = Rc::new(ModuleImport {
654                    name: name.clone(),
655                    variant,
656                    docs: decl.comments.docs(),
657                });
658                self.entries
659                    .insert(name, ModuleEntry::Import(Rc::downgrade(&rc_import)));
660                Ok(ModuleDefinition::Import(rc_import))
661            }
662            ModuleDeclSyntax::Func(syntax) => {
663                let loc = self.doc.location(syntax.export_loc);
664                let name = self.scope.introduce(syntax.export, loc)?;
665                let mut argnames = IdentValidation::new();
666                let params = syntax
667                    .params
668                    .iter()
669                    .map(|f| {
670                        Ok(InterfaceFuncParam {
671                            name: argnames.introduce(
672                                f.item.name.name(),
673                                self.doc.location(f.item.name.span()),
674                            )?,
675                            tref: self.doc.validate_datatype(
676                                &f.item.type_,
677                                false,
678                                f.item.name.span(),
679                            )?,
680                            docs: f.comments.docs(),
681                        })
682                    })
683                    .collect::<Result<Vec<_>, _>>()?;
684                let results = syntax
685                    .results
686                    .iter()
687                    .map(|f| {
688                        let tref =
689                            self.doc
690                                .validate_datatype(&f.item.type_, false, f.item.name.span())?;
691                        Ok(InterfaceFuncParam {
692                            name: argnames.introduce(
693                                f.item.name.name(),
694                                self.doc.location(f.item.name.span()),
695                            )?,
696                            tref,
697                            docs: f.comments.docs(),
698                        })
699                    })
700                    .collect::<Result<Vec<_>, _>>()?;
701                let noreturn = syntax.noreturn;
702                let abi = Abi::Preview1;
703                abi.validate(&params, &results)
704                    .map_err(|reason| ValidationError::Abi {
705                        reason,
706                        location: self.doc.location(syntax.export_loc),
707                    })?;
708                let rc_func = Rc::new(InterfaceFunc {
709                    abi,
710                    name: name.clone(),
711                    params,
712                    results,
713                    noreturn,
714                    docs: decl.comments.docs(),
715                });
716                self.entries
717                    .insert(name, ModuleEntry::Func(Rc::downgrade(&rc_func)));
718                Ok(ModuleDefinition::Func(rc_func))
719            }
720        }
721    }
722}