Skip to content

Conversation

@som-snytt
Copy link
Contributor

@som-snytt som-snytt commented Jun 10, 2018

Take -Yimports:x,y,z to mean x,y,z are root imports
of the form:

import x._ { import y._ { import z._ { code } } }

and where the import from a module can be disabled
with usual idiom, an import at the package level.

in detail:

The "root" imports which constitute the context for Scala source files can be managed explicitly.

The default root context imports symbols from java.lang, scala, and scala.Predef.

These imports can be turned off with -Yno-imports, or just the Predef with -Yno-predef.

A new option, -Yimports, allows these root imports to be specified explicitly as fully-qualified names of packages and objects. Order is significant, because later imports shadow earlier ones, as though they were in nested scopes.

Currently, root imports from Predef can be disabled in a source file by introducing an explicit import from Predef. Typically, import Predef.{any2stringadd => _, _}. Now, any root import from a predef object can be disabled using the same idiom. (There are some restrictions due to the way a source file is processed: generally, the import should be at the package level and appear at the top of the file. Also, a package object is not subject to this idiom.)

@scala-jenkins scala-jenkins added this to the 2.13.0-M5 milestone Jun 10, 2018
@som-snytt som-snytt closed this Jun 10, 2018
@som-snytt som-snytt reopened this Jun 11, 2018
@som-snytt som-snytt force-pushed the issue/yimports-2.13 branch from d333519 to fcaf156 Compare June 11, 2018 01:54
@som-snytt som-snytt requested a review from lrytz June 11, 2018 01:56
@som-snytt
Copy link
Contributor Author

som-snytt commented Jun 11, 2018

@lrytz Here is -Yimports which instead of trying to scan for the "disabling import", just waits for importSig to notice an import selecting from the module to disable. It means that source code order now matters; the import can't sit at the bottom of a package with impunity. In other words, it reduces the effect of action at a distance.

It occurs to me that maybe "disabling" should be scoped in the normal way, like a form of shadowing, where only the innermost import context from a root import is active. I didn't attempt that because the behavior is very different.

This PR currently removes scanning for the predef module in this unit; I'll restore that, possibly using the same mechanism: when you notice you're typechecking your predef, disable the import.

I may add a lint flag to warn when a predef is disabled; since there can be many -Yimports, it's easier to mistakenly have an explicit import that does the wrong thing.

Edit: I guess there is no test for not compiling Predef with import Predef._ enabled. It strikes me that it's sufficient to warn when encountering the predef and disabling the import; the warning would say to either disable the -Yimport or import Predef.{_ => _}; I wonder if my imports PR supports that syntax? Doesn't everyone clamor for it? or clamber for it?

Copy link
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really good!

For the "new trick" of filtering disabling module imports, could you

  • explain the advantage (that it can be done symbolically?) (in the commit message)
  • explain when imports are completed (in the commit message) and show in examples (and tests) in which cases filtering doesn't work. mention the limitations in -Yimports:help.

The tests should also show that -Yimports are not relative, always _root_... - that could also go into to the -Yimports:help.


```scala
import java.lang._
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not valid Scala, unfortunately... I don't see how it could be expressed in source code. Maybe we can just explain that this is conceptual, not actual code.

We could add a note that this makes Predef.String work (right?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added more words on the other PR.

.withPostSetHook(bs => if (bs) imports.value = Nil)
val nopredef = BooleanSetting ("-Yno-predef", "Compile without importing Predef.")
.withPostSetHook(bs => if (bs && !noimports) imports.value = "java.lang" :: "scala" :: Nil)
val imports = MultiStringSetting("-Yimports", "import", "Custom preamble imports, default is java.lang, scala, scala.Predef.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe remove the spaces in default is `java.lang,scala,scala.Predef`

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would also be nice to define the helpText, it can be quite long (optInlineFrom uses it for example) and cover all the details (eg about neutralizing module imports).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a high bar. Or maybe a high bar stool.

else imp.importedSymbol(name, requireExplicit, record) filter (s => isAccessible(s, imp.qual.tpe, superAccess = false))

private def isExcludedRootImport(imp: ImportInfo): Boolean =
imp.isRootImport && excludedRootImportsCached.get(unit).map(_.contains(imp.qual.symbol)).getOrElse(false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.map(f).getOrElse(false) could be .exists(f)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had that during one version while trying to get everything working... :)

// Top-level definition whose leading imports include Predef.
def isLeadingPredefImport(defn: Tree): Boolean = defn match {
case PackageDef(_, defs1) => defs1 exists isLeadingPredefImport
case Import(expr, _) => isReferenceToPredef(expr)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems isReferenceToPredef and isReferenceToPredef can be removed too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, not long ago I was still special-casing it for efficiency.

@lrytz
Copy link
Member

lrytz commented Jun 13, 2018

What are your thoughts about the cost/benefit of supporting module (and package) members in the -Yimports list?

Does -Yimports: work to disable root imports?

@som-snytt
Copy link
Contributor Author

When I reviewed Andy Scott, I remembered the feature of just making them look like regular imports. I did at least want to support members in the sense of members of stable member MyObj.stableM.

I thought I'd return again to whether -Yimports:MyObj.stableM._ and supporting rename syntax etc, gives up the simplicity. It's true that root imports are "just an import."

@som-snytt som-snytt added the WIP label Jun 13, 2018
@som-snytt som-snytt force-pushed the issue/yimports-2.13 branch from fcaf156 to dad17b0 Compare June 19, 2018 04:12
@som-snytt som-snytt removed the WIP label Jun 19, 2018
@som-snytt
Copy link
Contributor Author

More tests to come. More option syntax, something like -Yimports:+MyPredef and -Yimports:-scala.Predef. Not sure why the values must be absolute instead of relative to previous contexts, which is more obvious and sane.

@som-snytt som-snytt force-pushed the issue/yimports-2.13 branch 2 times, most recently from 5f9514d to 32e7276 Compare June 19, 2018 04:44
@som-snytt
Copy link
Contributor Author

I think -Ypreamble "import junk._" or -Ypreamble @mypreamble could be a separate effort.

There, a preamble would simply prepend to the compilation unit, without the special weirdness of root imports. A preamble is just some statements.

I hope -Yimports lands without preambleness.

@som-snytt
Copy link
Contributor Author

som-snytt commented Jun 21, 2018

How about a preamble, -Ypreamble "package com.snytt". That could not work transparently because of relative packaging. Besides imports, what do people most want from a preamble?

Edit: I guess this is another reason everyone wants top-level defs. -Ypreamble "def debug(s: String) = println(s)" or -Ypreamble "@elidable debug(s: String) = ()".

Take `-Yimports:x,y,z` to mean `x,y,z` are root imports
of the form:
```
import x._ { import y._ { import z._ { code } } }
```
and where the import from a module (non-package) can
be disabled with usual idiom, an import at the package level.

The disabling import is registered when the import
is completed, so it must precede all usages.
@som-snytt som-snytt force-pushed the issue/yimports-2.13 branch from 32e7276 to 4971e90 Compare July 27, 2018 21:20
@som-snytt som-snytt force-pushed the issue/yimports-2.13 branch from 38dea40 to 15b1222 Compare July 28, 2018 01:42
@SethTisue SethTisue modified the milestones: 2.13.0-M5, 2.13.0-RC1 Aug 7, 2018
@adriaanm adriaanm modified the milestones: 2.13.0-RC1, 2.13.0-M5 Aug 7, 2018
@adriaanm adriaanm self-assigned this Aug 7, 2018
Copy link
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!! Leaving open for @adriaanm's 👀

@adriaanm
Copy link
Contributor

adriaanm commented Aug 8, 2018

My 👀 had been lurking approvingly. :shipit:

@adriaanm adriaanm merged commit 27335e1 into scala:2.13.x Aug 8, 2018
@lrytz
Copy link
Member

lrytz commented Aug 8, 2018

Wohoo!! Thanks @som-snytt

@som-snytt
Copy link
Contributor Author

Thanks, what could go wrong?

@som-snytt som-snytt deleted the issue/yimports-2.13 branch August 8, 2018 17:01
@SethTisue SethTisue added the release-notes worth highlighting in next release notes label Aug 8, 2018
@som-snytt
Copy link
Contributor Author

som-snytt commented Aug 8, 2018

@SethTisue I'll contribute reconsider some verbiage:

The "root" imports which constitute the context for Scala source files can be managed explicitly.

The default root context imports symbols from java.lang, scala, and scala.Predef.

These imports can be turned off with -Yno-imports, or just the Predef with -Yno-predef.

A new option, -Yimports, allows these root imports to be specified explicitly as fully-qualified names of packages and objects. Order is significant, because later imports shadow earlier ones, as though they were in nested scopes.

Currently, root imports from Predef can be disabled in a source file by introducing an explicit import from Predef. Typically, import Predef.{any2stringadd => _, _}. Now, any root import from a predef object can be disabled using the same idiom. (There are some restrictions due to the way a source file is processed: generally, the import should be at the package level and appear at the top of the file. Also, a package object is not subject to this idiom.)

@edmundnoble
Copy link
Contributor

edmundnoble commented Aug 14, 2018

This is actually really not useful in practice. I don't want -Yimports:java.lang.String to import the members from java.lang.String at all; I want it to just import java.lang.String. If I wanted import java.lang.String._, doesn't it stand to reason that I would type -Yimports:java.lang.String._? I assumed this existed to complement -Yno-imports, but with this working the way it does, I have no use for it at all.

The part that really hurts is, it seems like it would be absolutely trivial to fix this issue. Is there any chance of a PR fixing it getting in before this is completely unchangeable?

@SethTisue
Copy link
Member

there’s still time to change this. it’s just a -Y flag so there aren’t hard compatibility guarantees even after 2.13.0 is out, but obviously it would be better to get any changes in before that.

@SethTisue
Copy link
Member

(perhaps this is one where a thread on https://contributors.scala-lang.org might spark some design discussion even from folks who don’t follow the scala/scala PR queue)

@som-snytt
Copy link
Contributor Author

@edmundnoble I think you're asking for -Ypreamble. The difference in usability is that a preamble is just something stuck onto the front of your source text. A root context is more like what you get with nested package clauses, package rootA ; package rootB ; mycode where members of the roots are visible to mycode, with usual shadowing. That's different from import rootA._. (Root contexts have lower name binding precedence, too.) I'll try to propose a -Ypreamble that satisfies what I think you're asking for.

@som-snytt
Copy link
Contributor Author

@edmundnoble Also please note I'm exercising everything I learned in Sunday school to respond to your comment that, "This is actually really not useful in practice." I took that to mean simply that I didn't address your use case. But I hope that -Ypreamble does cover it.

@fommil
Copy link
Contributor

fommil commented Aug 14, 2018

Personally I think this is pretty useful, but yeah I agree that something more explicit would be even better. Thanks @som-snytt !

Had I more than (checks watch) 3 hours left doing scala, I'd be excited about typing this...

-Yno-imports -Yno-predef -Yimports:fommil.prelude

@som-snytt
Copy link
Contributor Author

-Yno-imports -Yno-predef are aliases for -Yimports so you can save precious time and omit them in the presence of -Yimports. There was talk on the ticket about getting rid of those options, but I think they are a handy shorthand. I'd be happy to entertain suggestions for a better name than the misnomer -Yimports. -Yroot-contexts lacks something. Naming is so hard, it becomes tractable only through crowd-sourcing.

@fommil
Copy link
Contributor

fommil commented Aug 14, 2018

@som-snytt -Yimports should be -Yprelude in my opinion (or -Ypredef). imports is definitely a misnomer.

@som-snytt
Copy link
Contributor Author

@fommil Instead of crowd-sourced, fommil-sourced. I'll try out -Yprelude. There is a distinction between "predef" objects and packages in the prelude. I hope prelude isn't too close to preamble. In one, you're just ambling, in the other, you're just playing around, ludus.

@som-snytt
Copy link
Contributor Author

I had a branch if I can find it that goes the other way to support Andy Scott's syntax. (As in my previous comment #6764 (comment))

I liked this PR because it is not finicky: you define packages and Predef objects to wildcard import from.

The follow-up was just to take regular import syntax, -Yimports:mypredef._, and treat them like regular imports (so rooted names are not required). I wanted to avoid having two options (for packages versus predef things you can opt out of in source code). I still have lingering doubts about opting out of your own predef, but the feature can still be supported on the current basis, that the qualifier of an import is not a package.

My concern about "import syntax" is that it could wind up very confusing, and it increases the surface area outside the source text. The dubious notion is that I need -Yimports:my.special.Thing because I don't want to create an object that aliases it because we don't export.

Another difference is that an explicit import has higher precedence, so it's harder to reason about -Yimports:my.Thing,my.stuff._ where the stuff package might include a Thing. (That would be ambiguous.) I haven't exercised these edge cases yet. In the current PR, everything is wildcard import, so there is no ability to induce ambiguity with the option.

Using import syntax seems familiar, but it's also true that everyone hates import syntax and it needs to be simplified. So this PR is simple, and import syntax would be finicky.

If I can find the branch, though, I'll propose the finicky PR.

@agilesteel
Copy link

This might be the wrong place to ask, but is dotty going to support this? As of right now I'm getting bad option '-Yimports and that's on 0.27.0-RC1.

@SethTisue
Copy link
Member

@benhutchison
Copy link
Contributor

I agree with @edmundnoble observation that this feature, by limiting to coarse-grained, whole-package imports, ruled out too large a portion of the use cases where it could be valuable, eg:

  • Importing most of the existing Predef, but renaming or hiding a few members.
  • Importing specific classes or members from outside the std prelude packages

At the time, there was talk of a -Ypreamble that would have these capabilities, and thus provide a way for user projects to use a fully customized prelude.

However, I note that, somewhat contradictorily, when users then asked for custom prelude at scala/bug#9341 and scala/scala-dev#474, they were told "You've already got it with -Yimports".

I myself would use the ability to set a fully custom prelude at the level of a SBT project, including fine-grained addition, hiding and renaming of members, and in Scala3, control of given imports. It would ideally be supported across Scala 2 & 3, but 3 alone would at least provide a beacon to navigate towards.

@som-snytt
Copy link
Contributor Author

People are still confused whether Seq means collection.Seq or immutable.Seq.

To ask for a -Yarbitrary-mapping-of-names requires a specification.

It's not obvious to me that an opinionated Scala 3 wants people plugging in arbitrary namespaces at all.

Maybe there is a difference of understanding about the phrase "custom Predef"? You can say -YMyPredef. That is a custom predef. That does not "customize" the existing scala.Predef. Per scala/bug#9341

Sorry but the feature is custom root contexts. I'd suggest further commentary on a different Scala 3 ticket. I understand that "I myself would use" is a use case for a different feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-notes worth highlighting in next release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants