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

"is not a class type" error for given for intersection type #14175

Closed
user753 opened this issue Dec 26, 2021 · 10 comments · Fixed by #19635
Closed

"is not a class type" error for given for intersection type #14175

user753 opened this issue Dec 26, 2021 · 10 comments · Fixed by #19635
Assignees
Labels
area:typer better-errors Issues concerned with improving confusing/unhelpful diagnostic messages exp:novice itype:enhancement
Milestone

Comments

@user753
Copy link

user753 commented Dec 26, 2021

Compiler version

3.1.0

Minimized example

trait Foo:
  def foo: Int

trait Bar:
  def bar: Int  

given (Foo & Bar) with
  def foo = 1
  def bar = 2

Output

compilation error

Foo & Bar is not a class type

Expectation

I think something like

Intersection type cannot be used with given

would be clearer

@som-snytt
Copy link
Contributor

Or, say, "Did you mean...?" and tell us what to write.

One imagines a session like the following.

As a footnote, error recovery on missing with is a bit wonky.

scala> def f(using x: AutoCloseable & Runnable) = try x.run() finally x.close()
def f(using x: AutoCloseable & Runnable): Unit

scala> f
-- Error:
1 |f
  | ^
  | no implicit argument of type AutoCloseable & Runnable was found for parameter x of method f

scala> given AutoCloseable & Runnable with { def close() = () }
-- Error:
1 |given AutoCloseable & Runnable with { def close() = () }
  |                    ^
  |                    'with' expected, but identifier found
-- Error:
1 |given AutoCloseable & Runnable with { def close() = () }
  |                                                        ^
  |                                                        '{' expected, but eof found

scala> given (AutoCloseable & Runnable) with { def close() = () }
-- Error:
1 |given (AutoCloseable & Runnable) with { def close() = () }
  |      ^^^^^^^^^^^^^^^^^^^^^^^^^^
  |      AutoCloseable & Runnable is not a class type

scala> given AutoCloseable with Runnable with { def close() = () ; def run() = () }
// defined object given_AutoCloseable_Runnable

scala> given AutoCloseable { def close() = () }
-- Error:
1 |given AutoCloseable { def close() = () }
  |                    ^
  |                    'with' expected, but '{' found
-- Error:
1 |given AutoCloseable { def close() = () }
  |                                        ^
  |                                        '{' expected, but eof found

@user753
Copy link
Author

user753 commented Dec 26, 2021

So It's possible to do this

given Foo with Bar with 
  def foo = 1
  def bar = 2

So I am not sure what is the problem with given Foo & Bar?

@som-snytt
Copy link
Contributor

Probably because you have to construct an instance. A & B is not the same as A with B because of linearization, where order matters.

scala> trait T { def f: Int = 42 }
// defined trait T

scala> trait U { def f: Int = 27 }
// defined trait U

scala> class C extends T, U { override def f = super.f } ; C().f
// defined class C
val res0: Int = 27

scala> class C extends U, T { override def f = super.f } ; C().f
// defined class C
val res1: Int = 42

That reminds me, I don't remember where I noticed withless syntax.

@user753
Copy link
Author

user753 commented Dec 26, 2021

A & B is not the same as A with B because of linearization, where order matters.

Yes, but given Foo with Bar can be used for using Foo & Bar

given Foo with Bar with 
  def foo = 1
  def bar = 2

def fooBar(using fb: Foo & Bar) = fb.foo + fb.bar  

println(fooBar) // 3

@som-snytt
Copy link
Contributor

I meant to say that & is commutative but with is not. Foo with Bar is both Foo & Bar and Bar & Foo, but not the same as Bar with Foo; that is my guess why with is required here.

I forgot to add that comma is not a synonym for with in the type of a given:

scala> given AutoCloseable, Runnable with { def close() = () ; def run() = () }
-- Error:
1 |given AutoCloseable, Runnable with { def close() = () ; def run() = () }
  |                   ^
  |                   'with' expected, but ',' found
-- Error:
1 |given AutoCloseable, Runnable with { def close() = () ; def run() = () }
  |                                                                        ^
  |                                                                        '{' expected, but eof found

While browsing the repo, I saw an old comment about disallowing with in anonymous classes, so you could only have one parent, IIRC. So I don't know what their intentions are.

@bishabosha
Copy link
Member

The difference here is that intersection types can only be the types of return values, or parameters - a given <type> with ... is similar to a class declaration, so can not use an intersection type. If you would like an intersection type then you need an alias given : given <type> = ...

@ckipp01 ckipp01 added the stat:needs triage Every issue needs to have an "area" and "itype" label label May 15, 2023
@nicolasstucki nicolasstucki added area:typer better-errors Issues concerned with improving confusing/unhelpful diagnostic messages itype:enhancement and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Jan 10, 2024
@nicolasstucki
Copy link
Contributor

We sould probably add a -explain to NotClassType in messages.scala.

@mbovel mbovel added exp:novice Spree Suitable for a future Spree labels Jan 31, 2024
@scala-center-bot
Copy link

This issue was picked for the Issue Spree No. 42 of February 6th, 2024. @SethTisue, @spavikevik, @iusildra, @aherlihy will be working on it. If you have any insight into the issue or guidance on how to fix it, please leave it here.

@SethTisue
Copy link
Member

"Class type" is spec language that is not familiar to most Scala programmers. So we must either avoid it, or explain it.

@SethTisue
Copy link
Member

We have been discussing whether it's enough to change and/or add an "-explain" text to the general error message, or whether it's worth trying to make it specific to the given case. None of us feel strongly that it's worth doing something specific for given.

It would be nice to avoid the term "class type", but we can't think of a concise alternative, so I guess we'll just add an "explain" text.

We weren't sure initially how many other situations this error might come up in. It appears to us that the three main non-class types are intersection types, union types, and refinement types. (In Scala 2, only two error messages refer to "class type", and both appear intended to only exclude refinement types.)

aherlihy added a commit to aherlihy/scala3 that referenced this issue Feb 6, 2024
aherlihy added a commit to aherlihy/scala3 that referenced this issue Feb 6, 2024
SethTisue pushed a commit to aherlihy/scala3 that referenced this issue Feb 19, 2024
@SethTisue SethTisue self-assigned this Feb 19, 2024
@SethTisue SethTisue removed the Spree Suitable for a future Spree label Feb 19, 2024
@Kordyjan Kordyjan added this to the 3.4.2 milestone Mar 28, 2024
@Kordyjan Kordyjan modified the milestones: 3.4.2, 3.5.0 May 10, 2024
@Kordyjan Kordyjan added this to the 3.4.2 milestone May 10, 2024
WojciechMazur pushed a commit that referenced this issue Jul 1, 2024
WojciechMazur pushed a commit that referenced this issue Jul 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:typer better-errors Issues concerned with improving confusing/unhelpful diagnostic messages exp:novice itype:enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants