dotnet / dotnet/csharplang

C# Spec: Constraints checking in late bound invocations

Open
#300 1 comment 0 reactions 0 assignees View on GitHub
Spec
Dominant language
C#
Stars
12.7k
Forks
1.1k
Avg merge
11h 1m
Merged PRs (30d)
3

Description

I am moving this bug report from my email box (which I'm trying to clean up) to this bug.

Mads' analysis (the last of this thread, on top) is not correct. This is not an error because the simple name does not refer to an instance member. It refers to a method group. So I believe there remains a spec bug.

> From: Mads Torgersen
> Sent: Friday, January 18, 2013 12:08 PM
> To: Tomas Matousek; Neal Gafter
> Subject: RE: Constraints checking in late bound invocations
>
> Tomas and I just talked about this and agreed it would be best if this was a compile time error.
> From a spec point of view I am trying to figure out what this means. There’s a check in 7.5.4 for when the candidates are known at compile time, and it requires a) partial type inference and b) partial applicability to succeed. We thought the check should maybe be added there.
> I’ve since read up on the spec, and it turns out that this is in fact already supposed to be an error. The reference to “F” itself is faulty, according to 7.6.2 on Simple Names:
> · If [ it’s a local … ]
> · If [ it’s a type parameter of a method … ]
> · Otherwise, for each instance type T (§10.3.1), starting with the instance type of the immediately enclosing type declaration and continuing with the instance type of each enclosing class or struct declaration (if any):
> o If [ it’s a type parameter of a type … ]
> o Otherwise, if a member lookup (§7.4) of I in T with K type arguments produces a match:
> § If T is the instance type of the immediately enclosing class or struct type and the lookup identifies one or more methods, [ … ]
> § Otherwise, if T is the instance type of the immediately enclosing class or struct type, if the lookup identifies an instance member, and if the reference occurs within the block of an instance constructor, an instance method, or an instance accessor, [ … ]
> § Otherwise, the result is the same as a member access (§7.6.4) of the form T.I or T.I. In this case, it is a binding-time error for the simple-name to refer to an instance member.
> · Otherwise, [ search the namespaces … ]
> Since these don’t apply, we find ourselves in this situation. The simple name is not well formed, and should be rejected even before we realize that it is being applied to something dynamic. Us allowing this in Dev11 is simply a bug.
> Make sense?
> Mads

---

> From: Tomas Matousek
> Sent: Thursday, January 17, 2013 12:59 PM
> To: Mads Torgersen; Neal Gafter
> Subject: RE: Constraints checking in late bound invocations
>
> Yeah, I had to handle this case explicitly. The bound method group has no receiver. So I had to synthesize “this” bound reference that is passed to the call site. The runtime binder is correct – it doesn’t know there was a method group. It just invokes method “F” on type D.
> So maybe the bug is that we should have reported an error that you can’t call F of an outer class (as we do in non-dynamic case). That would indeed be a breaking change.
> Tomas

---

> From: Mads Torgersen
> Sent: Thursday, January 17, 2013 12:22 PM
> To: Tomas Matousek; Neal Gafter
> Subject: RE: Constraints checking in late bound invocations
>
> Arrgh! This is a bug in something. Either the compiler passes the wrong thing to the runtime binder for the receiver of “F”, or the runtime binder itself somehow does the wrong thing. Or something.
> Mads

---

> From: Tomas Matousek
> Sent: Thursday, January 17, 2013 10:01 AM
> To: Mads Torgersen; Neal Gafter
> Subject: RE: Constraints checking in late bound invocations
>
> Probably a corner case, but interesting one J. In the following code we never call C.F even though F binds to the method group C.F.
> But D.TryInvokeMember is invoked instead and there is no runtime error.
> using System;
> using System.Dynamic;
> public class C
> {
> public T F(bool u) where T : C { return default(T); }
> public T F(int u) where T : C { return default(T); }
> public class D : DynamicObject
> {
> public override bool TryInvokeMember(
> InvokeMemberBinder binder,
> Object[] args,
> out Object result)
> {
> result = "hello from " + binder.Name;
> return true;
> }
> public void M()
> {
> dynamic db = "bar";
> Console.WriteLine(F(db));
> }
> }
> public static void Main()
> {
> new D().M();
> }
> }
> Tomas

---

> From: Mads Torgersen
> Sent: Thursday, January 17, 2013 8:40 AM
> To: Tomas Matousek; Neal Gafter
> Subject: RE: Constraints checking in late bound invocations
>
> According to section 7.5.4 in the spec, case 3 is wrong. It says that “if no candidate passes this test, a compile-time error occurs”. As 2 shows, the native compiler correctly applies the test when there’s one candidate, but apparently not reliably when there’s two!
> It seems to have something to do with the specific way in which the candidates are failing the 7.5.4 test. This program does correctly give an error on the line of the call:
> class D
> {
> public void F(int x, int y) { }
> public void F(int x, bool y) { }
> void M()
> {
> dynamic db = null;
> F(db, "");
> }
> }
> Fixing the bug to report the error at compile time would be breaking, of course, but only slightly: it moves a runtime error (which you may avoid if the code path is not reached) to compile time. I vote that we do it.
> Mads

---

> From: Tomas Matousek
> Sent: Wednesday, January 16, 2013 5:48 PM
> To: Neal Gafter; Mads Torgersen
> Subject: Constraints checking in late bound invocations
>
> Case 1)
> class C
> {
> void M()
> {
> dynamic di = null;
> dynamic r1 = F(db);
> }
> }
> The compiler reports an error CS0103: The name 'F' does not exist in the current context.
> The method group F doesn’t have any candidates. Good.
> Case 2)
> class C
> {
> public T F(bool u) where T : C { return default(T); }
> void M()
> {
> dynamic db = null;
> dynamic r1 = F(db);
> }
> }
> The compiler reports error CS0311: The type 'dynamic' cannot be used as type parameter 'T' in the generic type or method 'C.F(bool)'. There is no implicit reference conversion from 'dynamic' to 'C'.
> This could be explained by the fact that we have no candidates matching the generic arguments – the constraint isn’t satisfied.
> Case 3)
> class C
> {
> public T F(bool u) where T : C { return default(T); }
> public T F(int u) where T : C { return default(T); }
> void M()
> {
> dynamic db = null;
> dynamic r1 = F(db);
> }
> }
> The Dev11 compiler doesn’t report an error. Compilation succeeds. But we still shouldn’t have any candidates if we are checking constraints!
> Either 2) is wrong and shouldn’t report an error because we ignore the constraints and leave their checking to the runtime binder or 3) is wrong because we should report an error that there are no matching F’s.
> Tomas

---

Migrated from TFS/DevDiv 1043008
Migrated from https://github.com/dotnet/roslyn/issues/4685

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by comparing the quoted C# specification sections 7.5.4 and 7.6.2 with the three dynamic-invocation examples in the issue. Reproduce the differing compiler behavior and determine the expected compile-time handling of the generic constraints and method group; done means the specification bug and intended behavior are resolved consistently.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.