Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow declaration shadowing but disallow ambiguous identifier references #678

Closed
AndreaOrru opened this issue Jan 9, 2018 · 7 comments
Closed
Labels
accepted This proposal is planned. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Milestone

Comments

@AndreaOrru
Copy link
Contributor

This works and compiles as expected:

const X = struct {
    const Y = struct {
        pub fn init() -> Y {
            return Y {};
        }
    };

    pub fn init() -> X {
        return X {};
    }
};

pub fn main() -> %void {
    const x = X.init();
    const y = X.Y.init();
}

However, if the inner init is defined as a pub const, it fails:

const X = struct {
    const Y = struct {
        pub const init = init2;

        fn init2() -> Y {
            return Y {};
        }
    };

    pub fn init() -> X {
        return X {};
    }
};

pub fn main() -> %void {
    const x = X.init();
    const y = X.Y.init();
}
/home/andrea/Programming/test2.zig:3:13: error: redefinition of 'init'
        pub const init = init2;
            ^
/home/andrea/Programming/test2.zig:10:9: note: previous definition is here
    pub fn init() -> X {
@andrewrk andrewrk added this to the 0.2.0 milestone Jan 10, 2018
@thejoshwolfe
Copy link
Sponsor Contributor

Proposal to fix this:

Name shadowing is allowed in certain special cases (see list below). When a name is declared that shadows another name, then referring to that name unqualified is a compile error.

So for your example, both ways of writing the code would be equivalent, but in either case, you cannot refer to the unqualified name init inside Y. Every time you would want to refer to init inside Y, you would need to say either Y.init or X.init. However, inside X outside Y, you can refer to init, and it means X.init.

List of cases where shadowing is allowed:

  • Struct members shadowing members of other structs (the OP example).

There may be more cases to add to the list as well, but we want to keep this list as short as possible. See #683.

@andrewrk andrewrk added accepted This proposal is planned. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. labels Jan 11, 2018
@andrewrk andrewrk modified the milestones: 0.2.0, 0.3.0 Feb 28, 2018
@andrewrk andrewrk modified the milestones: 0.3.0, 0.4.0 Jul 18, 2018
@andrewrk andrewrk modified the milestones: 0.4.0, 0.5.0 Mar 20, 2019
@daurnimator
Copy link
Collaborator

daurnimator commented Jul 24, 2019

This is related to the issue of being unable to create a method named after a builtin type. e.g.

        pub fn @"bool"() bool {

fails with

error: declaration shadows primitive type 'bool'

One potential solution is to allow the definition, but once there is a conflict: enforce that all accesses go via a qualified scope e.g. @This().bool.

A different solution might be along the lines of #705 (comment)

@andrewrk andrewrk modified the milestones: 0.5.0, 0.6.0 Sep 2, 2019
@andrewrk andrewrk modified the milestones: 0.6.0, 0.7.0 Feb 10, 2020
@hbobenicio
Copy link

hbobenicio commented Sep 24, 2020

Here is another simple example, without functions, just with nested structs:

zig version
0.6.0+58ee5f4e6

const Foo = struct {
    const Bar = struct {
        name: []const u8 = "Foo Bar",
    };
};

const Bar = struct {
    name: []const u8 = "Just Bar",
};

pub fn main() void {
    const foo_bar = Foo.Bar{};
    const bar = Bar{};
}

which fails with:

./src/main.zig:2:5: error: redefinition of 'Bar'
    const Bar = struct {
    ^
./src/main.zig:7:1: note: previous definition is here
const Bar = struct {
^
./src/main.zig:12:24: note: referenced here
    const foo_bar = Foo.Bar{};

But this will work:

const Foo = struct {
    const Bar = struct {
        name: []const u8 = "Foo Bar",
    };
};

const Caz = struct {
    const Bar = struct {
        name: []const u8 = "Just Bar",
    };
};

pub fn main() void {
    const foo_bar = Foo.Bar{};
    const caz_bar = Caz.Bar{};
}

Note also that if you separate "Foo.Bar" to "foobar.zig" and "Bar" to "bar.zig", and import them, it will work as expected.
So, the name conflicts between types that are nested inside structs only happen on the same module/file.

How should nested structs behave?

@ghost

This comment has been minimized.

@andrewrk andrewrk changed the title nested structs shadowing problems allow declaration shadowing but disallow ambiguous identifier references May 13, 2021
@andrewrk
Copy link
Member

Notes about how this will play in relationship to usingnamespace:

  • Multiple usingnamespace are allowed to cause name conflicts in a given namespace, however the same rule applies: an identifier must unambiguously reference a declaration, otherwise a compile error occurs.
usingnamespace struct {
    pub const a: i32 = 1234;
    pub const b: i32 = 99;
};
usingnamespace struct {
    pub const a: i32 = 5678;
};
test {
    _ = a; // compile error
}

If the test is replaced with only a reference to b then it becomes ok.

test {
    _ = b; // ok
}
  • If two pub declarations have the same name, a compile error occurs:
pub usingnamespace struct {
    pub const a: i32 = 1234;
};
pub usingnamespace struct {
    pub const a: i32 = 5678;
};

Even without a test, this is a compile error, because two pub declarations have the same name.

  • If only one of them is pub, it is OK:
pub usingnamespace struct {
    pub const a: i32 = 1234;
};
usingnamespace struct {
    pub const a: i32 = 5678;
};

However a reference within the same file would be ambiguous:

const This = @This();
test {
    _ = This.a; // error, ambiguous reference
}

Additionally, a usingnamespace decl shadowing a local decl is a compile error:

usingnamespace struct {
    pub const a: i32 = 5678;
};
const a: i32 = 1234; // error, name conflict

@daurnimator
Copy link
Collaborator

* If two pub declarations have the same name, a compile error occurs:
pub usingnamespace struct {
    pub const a: i32 = 1234;
};
pub usingnamespace struct {
    pub const a: i32 = 5678;
};

did you mean for one of these to not be pub?

@andrewrk andrewrk modified the milestones: 0.8.0, 0.9.0 May 19, 2021
@Hadron67
Copy link
Contributor

I really love this proposal. The use case I encountered is interface implementations, where both outer and inner structs implements the same interface:

field1: i32,
field2: []const u8,
...
compound: struct {
    field3: i32,
    ...
    pub fn jsonStringify(...) !void { ... }
},

pub fn jsonStringify(...) !void {...}

Without this proposal, the workaround could be define the inner struct in a separate file, which is unnecessarily clumsy. The same is true for constant declarations, they might be part of the interface too (e.g. #8987).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted This proposal is planned. proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

6 participants