Changeset - b0a907fd8513
[Not reviewed]
0 3 0
MH - 4 years ago 2021-04-02 14:35:43
contact@maxhenger.nl
Add some tests for failing imports
3 files changed with 108 insertions and 12 deletions:
0 comments (0 inline, 0 general)
src/protocol/lexer.rs
Show inline comments
 
@@ -479,74 +479,77 @@ impl Lexer<'_> {
 
        while self.has_string(b"::") {
 
            self.consume_string(b"::")?;
 
            self.consume_ident()?;
 
        }
 

	
 
        Ok(())
 
    }
 

	
 
    fn consume_namespaced_identifier2(&mut self, h: &mut Heap) -> Result<NamespacedIdentifier2, ParseError2> {
 
        if self.has_reserved() {
 
            return Err(self.error_at_pos("Encountered reserved keyword"));
 
        }
 

	
 
        // Consumes a part of the namespaced identifier, returns a boolean
 
        // indicating whether polymorphic arguments were specified.
 
        // TODO: Continue here: if we fail to properly parse the polymorphic
 
        //  arguments, assume we have reached the end of the namespaced 
 
        //  identifier and are instead dealing with a less-than operator. Ugly?
 
        //  Yes. Needs tokenizer? Yes. 
 
        fn consume_part(
 
            l: &mut Lexer, h: &mut Heap, ident: &mut NamespacedIdentifier2,
 
            backup_pos: &mut InputPosition
 
        ) -> Result<(), ParseError2> {
 
            // Consume identifier
 
            if !ident.value.is_empty() {
 
                ident.value.extend(b"::");
 
            }
 
            let ident_start = ident.value.len();
 
            ident.value.extend(l.consume_ident()?);
 
            ident.parts.push(NamespacedIdentifierPart::Identifier{
 
                start: ident_start as u16,
 
                end: ident.value.len() as u16
 
            });
 

	
 
            // Maybe consume polymorphic args.
 
            *backup_pos = l.source.pos();
 
            l.consume_whitespace(false)?;
 
            let had_poly_args = match l.consume_polymorphic_args(h, true)? {
 
            match l.consume_polymorphic_args(h, true)? {
 
                Some(args) => {
 
                    let poly_start = ident.poly_args.len();
 
                    ident.poly_args.extend(args);
 

	
 
                    ident.parts.push(NamespacedIdentifierPart::PolyArgs{
 
                        start: poly_start as u16,
 
                        end: ident.poly_args.len() as u16,
 
                    });
 

	
 
                    *backup_pos = l.source.pos();
 
                },
 
                None => {}
 
            };
 

	
 
            Ok(had_poly_args)
 
            Ok(())
 
        }
 

	
 
        let mut ident = NamespacedIdentifier2{
 
            position: self.source.pos(),
 
            value: Vec::new(),
 
            poly_args: Vec::new(),
 
            parts: Vec::new(),
 
        };
 

	
 
        // Keep consume parts separted by "::". We don't consume the trailing
 
        // whitespace, hence we keep a backup position at the end of the last
 
        // valid part of the namespaced identifier (i.e. the last ident, or the
 
        // last encountered polymorphic arguments).
 
        let mut backup_pos = self.source.pos();
 
        consume_part(self, h, &mut ident, &mut backup_pos)?;
 
        self.consume_whitespace(false)?;
 
        while self.has_string(b"::") {
 
            self.consume_string(b"::")?;
 
            self.consume_whitespace(false)?;
 
            consume_part(self, h, &mut ident, &mut backup_pos)?;
 
            self.consume_whitespace(false)?;
 
        }
 

	
 
        self.source.seek(backup_pos);
src/protocol/parser/utils.rs
Show inline comments
 
use crate::protocol::ast::*;
 
    use crate::protocol::ast::*;
 
use crate::protocol::inputsource::*;
 
use super::symbol_table::*;
 
use super::type_table::*;
 

	
 
/// Utility result type.
 
pub(crate) enum FindTypeResult<'t, 'i> {
 
    // Found the type exactly
 
    Found((&'t DefinedType, NamespacedIdentifier2Iter<'i>)),
 
    // Could not match symbol
 
    SymbolNotFound{ident_pos: InputPosition},
 
    // Matched part of the namespaced identifier, but not completely
 
    SymbolPartial{ident_pos: InputPosition, symbol_pos: InputPosition, ident_iter: NamespacedIdentifier2Iter<'i>},
 
    SymbolPartial{ident_pos: InputPosition, ident_iter: NamespacedIdentifier2Iter<'i>},
 
    // Symbol matched, but points to a namespace/module instead of a type
 
    SymbolNamespace{ident_pos: InputPosition, symbol_pos: InputPosition},
 
}
 

	
 
// TODO: @cleanup Find other uses of this pattern
 
impl<'t, 'i> FindTypeResult<'t, 'i> {
 
    /// Utility function to transform the `FindTypeResult` into a `Result` where
 
    /// `Ok` contains the resolved type, and `Err` contains a `ParseError` which
 
    /// can be readily returned. This is the most common use.
 
    pub(crate) fn as_parse_error(self, module_source: &InputSource) -> Result<(&'t DefinedType, NamespacedIdentifier2Iter<'i>), ParseError2> {
 
        match self {
 
            FindTypeResult::Found(defined_type) => Ok(defined_type),
 
            FindTypeResult::SymbolNotFound{ident_pos} => {
 
                Err(ParseError2::new_error(
 
                    module_source, ident_pos,
 
                    "Could not resolve this identifier to a symbol"
 
                ))
 
            },
 
            FindTypeResult::SymbolPartial{ident_pos, symbol_pos, ident_iter} => {
 
            FindTypeResult::SymbolPartial{ident_pos, ident_iter} => {
 
                Err(ParseError2::new_error(
 
                    module_source, ident_pos, 
 
                    "Could not fully resolve this identifier to a symbol"
 
                ).with_postfixed_info(
 
                    module_source, symbol_pos, 
 
                    &format!(
 
                        "The partial identifier '{}' was matched to this symbol",
 
                        String::from_utf8_lossy(ident_iter.returned_section()),
 
                        "Could not fully resolve this identifier to a symbol, was only able to match '{}'",
 
                        &String::from_utf8_lossy(ident_iter.returned_section())
 
                    )
 
                ))
 
            },
 
            FindTypeResult::SymbolNamespace{ident_pos, symbol_pos} => {
 
                Err(ParseError2::new_error(
 
                    module_source, ident_pos,
 
                    "This identifier was resolved to a namespace instead of a type"
 
                ).with_postfixed_info(
 
                    module_source, symbol_pos,
 
                    "This is the referenced namespace"
 
                ))
 
            }
 
        }
 
    }
 
}
 

	
 
/// Attempt to find the type pointer to by a (root, identifier) combination. The
 
/// type must match exactly (no parts in the namespace iterator remaining) and
 
/// must be a type, not a namespace. 
 
pub(crate) fn find_type_definition<'t, 'i>(
 
    symbols: &SymbolTable, types: &'t TypeTable, 
 
    root_id: RootId, identifier: &'i NamespacedIdentifier2
 
) -> FindTypeResult<'t, 'i> {
 
    // Lookup symbol
 
    let (symbol, ident_iter) = symbols.resolve_namespaced_identifier(root_id, identifier);
 
    if symbol.is_none() { 
 
        return FindTypeResult::SymbolNotFound{ident_pos: identifier.position};
 
    }
 
    
 
    // Make sure we resolved it exactly
 
    let symbol = symbol.unwrap();
 
    if ident_iter.num_remaining() != 0 { 
 
        return FindTypeResult::SymbolPartial{
 
            ident_pos: identifier.position, 
 
            symbol_pos: symbol.position, 
 
            ident_pos: identifier.position,
 
            ident_iter
 
        };
 
    }
 

	
 
    match symbol.symbol {
 
        Symbol::Namespace(_) => {
 
            FindTypeResult::SymbolNamespace{
 
                ident_pos: identifier.position, 
 
                symbol_pos: symbol.position
 
            }
 
        },
 
        Symbol::Definition((_, definition_id)) => {
 
            // If this function is called correctly, then we should always be
 
            // able to match the definition's ID to an entry in the type table.
 
            let definition = types.get_base_definition(&definition_id);
 
            debug_assert!(definition.is_some());
 
            FindTypeResult::Found((definition.unwrap(), ident_iter))
 
        }
 
    }
 
}
 

	
 
pub(crate) enum MatchPolymorphResult<'t> {
 
    Matching,
 
    InferAll(usize),
src/protocol/tests/parser_imports.rs
Show inline comments
 
@@ -126,28 +126,125 @@ fn test_multi_symbol_import() {
 
            return Foo{in_bar:0}.in_bar + Bar{in_foo:0}.in_foo;    
 
        }")
 
        .compile()
 
        .expect_ok();
 

	
 
    // TODO: Re-enable once std lib is properly implemented
 
    // Tester::new("import all")
 
    //     .with_source("
 
    //     #module external
 
    //     struct Foo { byte f };
 
    //     struct Bar { byte b };
 
    //     ")
 
    //     .with_source("
 
    //     import external::*;
 
    //     byte caller() {
 
    //         auto f = Foo{f:0};
 
    //         auto b = Bar{b:0};
 
    //         return f.f + b.b;
 
    //     }
 
    //     ")
 
    //     .compile()
 
    //     .expect_ok();
 
}
 

	
 
#[test]
 
fn test_illegal_import_use() {
 
    Tester::new("unexpected polymorphic args")
 
        .with_source("
 
        #module external
 
        struct Foo { byte f }
 
        ")
 
        .with_source("
 
        import external;
 
        byte caller() {
 
            auto foo = external::Foo<int>{ f: 0 };
 
            return foo.f;
 
        }
 
        ")
 
        .compile()
 
        .expect_err()
 
        .error(|e| { e
 
            .assert_msg_has(0, "the type Foo is not polymorphic");
 
        });
 

	
 
    Tester::new("mismatched polymorphic args")
 
        .with_source("
 
        #module external
 
        struct Foo<T>{ T f }
 
        ")
 
        .with_source("
 
        import external;
 
        byte caller() {
 
            auto foo = external::Foo<byte, int>{ f: 0 };
 
            return foo.f;
 
        }")
 
        .compile()
 
        .expect_err()
 
        .error(|e| { e
 
            .assert_msg_has(0, "expected 1 polymorphic")
 
            .assert_msg_has(0, "2 were specified");
 
        });
 

	
 
    Tester::new("module as type")
 
        .with_source("
 
        #module external
 
        ")
 
        .with_source("
 
        import external;
 
        byte caller() {
 
            auto foo = external{ f: 0 };
 
            return 0;
 
        }
 
        ")
 
        .compile()
 
        .expect_err()
 
        .error(|e| { e
 
            .assert_msg_has(0, "resolved to a namespace");
 
        });
 

	
 
    Tester::new("more namespaces than needed, not polymorphic")
 
        .with_source("
 
        #module external
 
        struct Foo { byte f }
 
        ")
 
        .with_source("
 
        import external;
 
        byte caller() {
 
            auto foo = external::Foo::f{ f: 0 };
 
            return 0;
 
        }")
 
        .compile()
 
        .expect_err()
 
        .error(|e| { e
 
            .assert_msg_has(0, "not fully resolve this identifier")
 
            .assert_msg_has(0, "able to match 'external::Foo'");
 
        });
 

	
 
    Tester::new("import from another import")
 
        .with_source("
 
        #module mod1
 
        struct Foo { byte f }
 
        ")
 
        .with_source("
 
        #module mod2
 
        import mod1::Foo;
 
        struct Bar { Foo f }
 
        ")
 
        .with_source("
 
        import mod2;
 
        byte caller() {
 
            auto bar = mod2::Bar{ f: mod2::Foo{ f: 0 } };
 
            return var.f.f;
 
        }")
 
        .compile()
 
        .expect_err()
 
        .error(|e| { e
 
            .assert_msg_has(0, "Could not resolve this identifier")
 
            .assert_occurs_at(0, "mod2::Foo");
 
        });
 
}
 

	
 
// TODO: Test incorrect imports:
 
//  1. importing a module
 
//  2. import something a module imports
 
//  3. import something that doesn't exist in a module
 
\ No newline at end of file
0 comments (0 inline, 0 general)