slicec 0.4.0

The Slice parser and other core components for Slice compilers.
Documentation
// Copyright (c) ZeroC, Inc.

use crate::diagnostics::{Diagnostic, Diagnostics, Error};
use crate::grammar::*;

pub fn validate_members(members: Vec<&impl Member>, diagnostics: &mut Diagnostics) {
    tags_have_optional_types(members.clone(), diagnostics);
    tags_are_unique(members.clone(), diagnostics);
}

/// Validates that the tags are unique.
fn tags_are_unique(members: Vec<&impl Member>, diagnostics: &mut Diagnostics) {
    // The tagged members must be sorted by value first as we are using windowing to check the
    // n + 1 tagged member against the n tagged member. If the tags are sorted by value then
    // the windowing will reveal any duplicate tags.
    let mut sorted_tagged_members = members.into_iter().filter(|m| m.is_tagged()).collect::<Vec<_>>();
    sorted_tagged_members.sort_by_key(|member| member.tag().expect("tagged member has no tag!"));
    sorted_tagged_members.windows(2).for_each(|window| {
        if window[0].tag() == window[1].tag() {
            Diagnostic::from_error(Error::CannotHaveDuplicateTag {
                identifier: window[1].identifier().to_owned(),
            })
            .set_span(window[1].span())
            .add_note(
                format!(
                    "The tag '{}' is already being used by member '{}'",
                    window[0].tag().unwrap(),
                    window[0].identifier(),
                ),
                Some(window[0].span()),
            )
            .push_into(diagnostics);
        };
    });
}

/// Validate that the type of the tagged member is optional.
fn tags_have_optional_types(members: Vec<&impl Member>, diagnostics: &mut Diagnostics) {
    let tagged_members = members.into_iter().filter(|member| member.is_tagged());

    // Validate that tagged members are optional.
    for member in tagged_members {
        if !member.data_type().is_optional {
            Diagnostic::from_error(Error::TaggedMemberMustBeOptional {
                identifier: member.identifier().to_owned(),
            })
            .set_span(member.span())
            .push_into(diagnostics);
        }
    }
}