-
Notifications
You must be signed in to change notification settings - Fork 261
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
feat!: New behavior for opened import where module contains homonymous top-level declaration #2355
Conversation
For `import opened M` where `M` is a module that declares a top-level `M`, let `M` in the importer refer to that top-level `M` instead of referring to the module `M`. Fixes dafny-lang#1996
# Conflicts: # RELEASE_NOTES.md
The resolution change introduced by the previous commit silently changed the To illustrate the technique, consider the following example:
This program printed 1 in previous versions of Dafny. It would print 2 with the To do so, we keep track of shadowing in a separate table:
This table is used in
The check is performed in
Two subtle additional difficulties:
|
The resolution change introduced by the previous commit silently changes the meaning of existing Dafny programs. This commit does its best to detect these cases and raise resolution errors instead. To illustrate the technique, consider the following example: module Option { static const a := 1 datatype Option = … { static const a := 2 } } module X { import opened Option method M() { print Option.A; } } This program printed 1 in previous versions of Dafny. It would print 2 with the new resolution strategy introduced in this commit. To avoid that, the program is rejected instead. To do so, we keep track of shadowing in a separate table: * Source/DafnyCore/AST/TopLevelDeclarations.cs (ModuleSignature): Add a new field `ShadowedImportedModules`. * Source/DafnyCore/Resolver.cs (ResolveOpenedImports): Keep track of shadowed modules. This table is used in `ResolveDotSuffix`: * Source/DafnyCore/Resolver.cs (ResolveNameSegment): Return name of shadowed module, if any, as an out parameter (ResolveDotSuffix): Before returning, make sure that the same name could not have been resolved in the parent module. The check is performed in `CheckForAmbiguityInShadowedImportedModule`, which runs a simplified version of resolution of a name in a module: * Source/DafnyCore/Resolver.cs (CheckForAmbiguityInShadowedImportedModule): Error out if the shadowed module contains a conflicting name (a name that would previously would have been instead of the one that the new resolution algorithm picks). (NameConflictsWithModuleContents): Determine whether a conflicting name exists. The check is an over-approximation: it disallows certain cases in which the old program would not have compiled due to ambiguity, for example. Two subtle additional difficulties: - Constructors are visible in a datatype and in its parent module. This means that without special care we would disallow expressions like `Option.Some`. - Code that was previously ambiguous now compiles unambiguously. One specific example is datatype constructors: previously, if two datatypes in a module defined same-named constructors, references to `Module.Constructor` were ambiguous. Now, if one of the datatype has the same name as the module, then such references are legal and unambiguously resolve to the constructors of that datatype.
519af0f
to
f3427f6
Compare
I opened issue 2757, assigned to me, to document this change in the RM. |
Source/DafnyCore/Resolver.cs
Outdated
/// <summary> | ||
/// Check whether the name we just resolved may have been resolved differently if we didn't allow member `M.M` of | ||
/// module `M` to shadow `M` when the user writes `import opened M`. Raising an error in that case allowed us to | ||
/// change the bahvior of `import opened` without silently changing the meaning of existing programs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bahvior
-> behavior
# Conflicts: # RELEASE_NOTES.md
reporter.Error(MessageSource.Resolver, tok, | ||
"Reference to member '{0}' is ambiguous: name '{1}' shadows an import-opened module of the same name, and " | ||
+ "both have a member '{0}'. To solve this issue, give a different name to the imported module using " | ||
+ "`import open XYZ = ...` instead of `import open ...`.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The two instances of 'open' should be 'opened'
This PR changes the behavior of
import opened M = M
(and its shorthandimport opened M
) for a module that contains a top-level declarationM
. To motivate and describe the change, it's helpful to start with what Dafny did previously.Opened import
Consider a "library" module
and a "client" module
The
import opened
declaration addsM
as a local name inClient
, and thisM
refers to the module being imported. In addition, since the import isopened
, the top-level names declares in moduleM
are spilled into the moduleClient
. This means that the client can refer to the constant as eitherX
orM.X
.There are some restrictions:
X
, then thatX
takes precedence over the import-openedX
. This means that the client has to writeM.X
to refer to that constant.X
, but the client also doesimport opened N
, whereN
is a module that declares anX
, then the nameX
is ambiguous in the client. This is allowed by Dafny, but the client module cannot write justX
, but has to use the qualified nameM.X
orN.X
to indicate whichX
it wants to refer to.Previous behavior
Suppose the library module contains a top-level declaration with the same name as the module:
A client that does
import opened M
introduces the local nameM
, and this name takes precedence over the import-opened nameM
. Thus, in the client module,M
refers to the imported module, and the only way to refer to the type inside that module is to writeM.M
.Even though this gives rise to simple, consistent rules, it sometimes causes programs to have to invent more names. For example, for a library module that primarily exports one type, say
Option
, it would be natural to use the same name (Option
) for the name of the module. However, that means clients have to writeOption.Option
to refer to the type, even if the module is imported asopened
.New behavior
To better serve the special case of naming a module and its primary top-level declaration the same, this PR introduces an exception to the standard rules given above. The exception is that if a client does
import opened M
where moduleM
contains a top-level declarationM
, then the client, in effect, gets a local nameM
that refers to theM
in moduleM
.For example, a library module
can be used as follows:
Note, however, that this means the import-opened module cannot be referred to directly (unless it is renamed). For example, the only way the client module can refer to another member in the imported module, say
Certainly
, is to not use any qualification. That is,Option.Certainly
would give an error (sinceOption
refers to the type andCertainly
is not a member of that type), and the client instead must write justCertainly
(which works only if no other name in the client takes precedence and there is no ambiguity).Since
import opened M
makesM
a local name--either to the imported module, as before, or to a top-level declaration in the imported module--it does not introduce any ambiguity with other import-opened symbols. For example, in the exampleRenaming imports
Dafny allows an import declaration to introduce a local name that is different from the name of the module being imported. This is done as
(with or without the
opened
modifier). By default,import opened M
just meansimport opened M = M
(again, with or without theopened
modifier). The new behavior applies only if the local name is the same as the name of the module being imported. For example, this PR does not change the behavior forimport opened LocalNameForM = M
.TODO: The description here should be copied into the reference manual.
Fixes #1996
By submitting this pull request, I confirm that my contribution is made under the terms of the MIT license.