Skip to content
Philip Herron edited this page May 20, 2021 · 1 revision

Mod and visibility

In order to support module blocks there are 4 (in order) things to implement:

  • Mod block HIR lowering
  • Name resolution of mod blocks
  • Type resolution of mod blocks
  • lower HIR to GENERIC for mod blocks

HIR Lowering

The first testcase to implement for mod blocks should be:

mod foomod {
    struct Foo {
     f: i32,
     g: u32,
    }
}

This test case means we don't care about any name/type resoution we just care that we can parse this correctly and use -frust-dump-all to check that we emit the HIR tree's for the mod block.

You should be able to add new Analysis::NodeMapping to the mod block and the HIR::Items within the block should already have this field.

In order to implement the HIR lowering for mod you will need to add a new visitor into:

rust/hir/rust-ast-lower-item.h such as ASTLoweringItem::visit(AST::ModuleBodied *mod) { }

We turn this into HIR::ModuleBodied but you will need to add the mappings see: rust/hir/tree/rust-hir-item.h

  ModuleBodied (Analysis::NodeMapping mappings, Identifier name, Location locus,
		std::vector<std::unique_ptr<Item> > items
		= std::vector<std::unique_ptr<Item> > (),
		Visibility visibility = Visibility::create_error (),
		std::vector<Attribute> inner_attrs = std::vector<Attribute> (),
		std::vector<Attribute> outer_attrs = std::vector<Attribute> ())

The fields should map over fairly sensibly but I would avoid implementing the Visibility portion for this for now. When adding the mappings for a module we will need to ensrue we a this to the mappings with a generated DefId which signifies a toplevel defintion within a crate in rust:

void insert_defid_mapping (DefId id, HIR::Item *item);
HIR::Item *lookup_defid (DefId id);

See the lowering of other things within rust/hir/rust-ast-lower-item.h.

Name resolution

This next portion should cover a test case like:

mod foomod {
    struct Foo {
     f: i32,
     g: u32,
    }
}

fn test() -> foomod::Foo {
    foomod::Foo{
        f:1,
        g:3,
    }
}

The name resolver uses the notion of CanonicalPaths see:

  1. https://doc.rust-lang.org/reference/paths.html#canonical-paths
  2. https://github.com/Rust-GCC/gccrs/blob/ca206dd3bbe8b6063216a466cd9a21fa44af3172/gcc/rust/resolve/rust-ast-resolve.cc#L556

Name resoltution toplevel

So for the ModuleBodied example here the name resolver should be creating a Canonical Path of foomod::Foo the code for this should be simple enough by adding a visitor to: rust/resolve/rust-ast-resolve-toplevel.h

void visit(AST::ModuleBodied& module) override {
    auto path
      = prefix.append (<module_name>);
    resolver->get_name_scope ().insert (
      path, node_id, module_location, false,
      [&] (const CanonicalPath &, NodeId, Location locus) -> void {
          // error handling for duplicate names
      });
    resolver->insert_new_definition (module.get_node_id (),
				     Definition{module.get_node_id (),
						module.get_node_id ()});
                        
   // foreach module item you need to resolve the toplevel scope so things can be forward declared
   // so blocks can referencec items which are declared after the fact.
   for (auto &item : module.items())
       ResolveTopLevel::go (item, path)
}

Name resolution for path expressions

As it was mentioned we will try to resolve the path fully if possible but sometimes it may not be possible such as generic items within impl blocks.

The code to update/change is within: https://github.com/Rust-GCC/gccrs/blob/ca206dd3bbe8b6063216a466cd9a21fa44af3172/gcc/rust/resolve/rust-ast-resolve.cc#L464

The resolver here keeps trying to build up Canonical paths for map lookups to resolve the name. It might need some changes here to handle modules but it might just work. If it doesn't the goal is for example a path A::B::C this name resolver must at least resolve A::B and can leave C to be resolved in type resolution.

Extra information about why we use root paths

The TLDR is that its not nessecarily always possible to resolve the path of something fully but we should be able to always resolve the root path of an expression.

struct Foo<A> (A);

impl Foo<isize> {
    fn test() -> ...
}

impl Foo<f32> {
    fn test() -> ...
}

fn main() {
    let a:i32 = Foo::test();
}

In this case the compiler can resolve the root Path Foo but it cannot resolve Foo::test since this depends on type resolution which can always leads to multiple candidates for a path see: https://github.com/Rust-GCC/gccrs/blob/ca206dd3bbe8b6063216a466cd9a21fa44af3172/gcc/rust/typecheck/rust-hir-path-probe.h#L36

We proess the impls for the type of the root path in this case Foo which lets us resolve to two candidates for Foo::::test and Foo::::test but we are trying to resolve via inference which leads to multiple candidates.

The other thing to remember about is overlapping items: https://github.com/Rust-GCC/gccrs/blob/ca206dd3bbe8b6063216a466cd9a21fa44af3172/gcc/rust/typecheck/rust-hir-inherent-impl-overlap.h#L28

This is for the case where:

struct Foo<A> {
    a: A,
}

impl Foo<isize> {
    fn bar(self) -> isize { // { dg-error "duplicate definitions with name bar" }
        self.a
    }
}

impl Foo<char> {
    fn bar(self) -> char { // { dg-error "duplicate definitions with name bar" }
        self.a
    }
}

impl<T> Foo<T> {
    fn bar(self) -> T {
        self.a
    }
}

fn main() {
    let a = Foo { a: 123 };
    a.bar();
}

This is valid rust code and should pass the name resoltuion but because of the final generic impl block it is possible that this impl block overlaps with the other impl blocks which means it will never be possible to resolve the path.

All of this in theory shouldn't affect the implementation of modules but the notion of resloving the root path will require changes.

Type Resolution

Firstly you need to add a visitor for rust/typecheck/rust-hir-type-check-toplevel.h to visit ModuleBodied and iterate all the items and call TypeCheckToplevel::Resolve(). This is responsible for adding in the type information for all those items. So they can be looked up later on.

See: https://github.com/Rust-GCC/gccrs/blob/ca206dd3bbe8b6063216a466cd9a21fa44af3172/gcc/rust/typecheck/rust-hir-type-check-expr.h#L865

The code here might just work for the simple paths of foomod::Foo but for impl blocks/generics i imagine there will be required changes within resolving the root path of A::B to then leave the path probe to look for C. But i imagine there might be changes to the path probe there as well. It might require us to add CanonicalPaths to the TyTy::ADT and TyTy::FnTypes.