Weird scoping rules with "import ."

79 views
Skip to first unread message

Benoit Sigoure

unread,
Jun 13, 2018, 12:55:13 AM6/13/18
to golang-nuts
Hi there,
I generally recommend against using "import ." however today someone pointed out an odd and surprising behavior:

——— mod1/foo.go ———
package mod1

type Foo interface {
Foo() int
}

——— mod2/bar.go ———
package mod2

import "mod1"

func Foo() mod1.Foo {
return nil
}

——— mod2/bad.go ———
package mod2

import . "mod1"

var _ Foo


This code fails to compile with:
mod2/bar.go:5:12: Foo redeclared in this block
previous declaration during import "mod1"

This is surprisingly because I didn't expect the import in bad.go to have an impact on bar.go.  Upon looking at the spec, that seems to be intended however:
If an explicit period (.) appears instead of a name, all the package’s exported identifiers declared in that package’s package block will be declared in the importing source file’s file block and must be accessed without a qualifier.
2. The scope of an identifier denoting a constant, type, variable, or function (but not method) declared at top level (outside any function) is the package block.

So in effect, the import . "mod1" takes all of the exported identifiers declared in mod1 and re-declares them in file block of bad.go, thereby contaminating the package block of mod2.  Except, not quite.  Because if I introduce a third package that attempts to use the re-declared identifier through mod2, it doesn't work:

——— mod3/wtf.go ———
package mod3

import "mod2"

func Bar() mod2.Foo {
return nil
}

Here the code fails to compile:
mod3/foo.go:5:12: undefined: mod2.Foo

So then the identifiers brought in by the dot-import weren't truly re-declared in the file block, otherwise they would be exported in the package block of mod2, no?  I'm probably misunderstanding something in this obscure corner case, so if any Go spec lawyers could shed some light on this, I'd appreciate it :)

[from what I can tell, this issue has been raised only once on the ML back in 2014, albeit for a different question]

--
Benoit "tsuna" Sigoure

Axel Wagner

unread,
Jun 13, 2018, 7:00:16 AM6/13/18
to tsun...@gmail.com, golang-nuts
Disclaimer: I think you just shouldn't use dot-imports and I think they are not very widely used, so this might just be an oversight indeed.
Disclaimer 2: IANALL (I'm not a language lawyer). I might overlook something or get mixed up myself :)

From the spec:
> No identifier may be declared twice in the same block, and no identifier may be declared in both the file and package block.

and:
> The scope of an identifier denoting a constant, type, variable, or function (but not method) declared at top level (outside any function) is the package block.

> An identifier is exported if both:
> 1. the first character of the identifier's name is a Unicode upper case letter (Unicode class "Lu"); and
> 2. the identifier is declared in the package block or it is a field name or method name.

So:

1. The dot-import declares the identifier Foo in the file-block
2. Writing down a declaration for Foo in a different file declares that in the package block, violating the "no identifier may be declared in both the file and package block" rule.
3. You can't use the imported Foo, because it's declared in the file-block, not the package-block and thus not exported.

But this still seems a bit wonky. I read this as the word "declared" being kind of overloaded, both as "you've written down an explicit declaration" and "an abstract concept of where an identifier lives, that is also fulfilled by imports implicitly". The latter might be more appropriately worded as "scoped"? I.e. maybe the exporting rule should be rephrased as "the scope of the identifier is the package block…"? And maybe the scope-rule rephrased as "the scope of an identifier from a TopLevelDecl that is not a MethodDecl is the package block" (which I believe is the intention of the current wording)?

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Ian Lance Taylor

unread,
Jun 13, 2018, 8:49:15 AM6/13/18
to Benoit Sigoure, golang-nuts
Yes. It has to work that way, because otherwise Foo would be
ambiguous in bad.go. Does it mean mod1.Foo or mod2.Foo? One could
argue that it must mean mod1.Foo, but then the name "Foo" means
different things in different files in the same package. That is a
recipe for confusion.


> Except, not quite. Because if I
> introduce a third package that attempts to use the re-declared identifier
> through mod2, it doesn't work:
>
> ——— mod3/wtf.go ———
> package mod3
>
> import "mod2"
>
> func Bar() mod2.Foo {
> return nil
> }
>
> Here the code fails to compile:
> mod3/foo.go:5:12: undefined: mod2.Foo
>
> So then the identifiers brought in by the dot-import weren't truly
> re-declared in the file block, otherwise they would be exported in the
> package block of mod2, no? I'm probably misunderstanding something in this
> obscure corner case, so if any Go spec lawyers could shed some light on
> this, I'd appreciate it :)

I think you're right that the spec is missing a few words here.
Certainly there is no intent to re-export the identifiers imported
using `import .`, but the spec doesn't seem to state that clearly.

Ian
Reply all
Reply to author
Forward
0 new messages