Bug 42436 - Android C# project with F# Android library won't build, problem with generated resource file
Summary: Android C# project with F# Android library won't build, problem with generate...
Status: VERIFIED FIXED
Alias: None
Product: Android
Classification: Xamarin
Component: MSBuild ()
Version: 7.0 (C8)
Hardware: PC Mac OS
: High normal
Target Milestone: 7.1 (C9)
Assignee: Jonathan Pryor
URL:
Depends on:
Blocks:
 
Reported: 2016-07-08 18:32 UTC by James Moore
Modified: 2016-09-30 11:20 UTC (History)
8 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
VERIFIED FIXED

Description James Moore 2016-07-08 18:32:05 UTC
Create an Android C# project.  Add an Android F# library to the solution.  Build.  Get error:

/Users/james/Projects/Testing16/Testing16/Resources/Resource.designer.cs(55,55): Error CS1646: Keyword, identifier, or string expected after verbatim specifier: @ (CS1646) (Testing16)

Screencast at https://knowledge.autodesk.com/community/screencast/87f967dc-d8cf-4b48-9b9c-eed1f1cdcfad
Comment 1 James Moore 2016-07-08 18:32:34 UTC
=== Xamarin Studio Professional ===

Version 6.1 (build 4963)
Installation UUID: 6dc077b1-7f04-4c1c-b481-1dcbb9a8b0a1
Runtime:
	Mono 4.4.0 (mono-4.4.0-branch/fcf7a6d) (64-bit)
	GTK+ 2.24.23 (Raleigh theme)

	Package version: 404000148

=== NuGet ===

Version: 3.4.3.0

=== Xamarin.Profiler ===

Not Installed

=== Apple Developer Tools ===

Xcode 7.3.1 (10188.1)
Build 7D1014

=== Xamarin.Mac ===

Version: 2.9.2.81 (Visual Studio Professional)

=== Xamarin.Android ===

Version: 6.1.99.224 (Visual Studio Professional)
Android SDK: /Users/james/Library/Developer/Xamarin/android-sdk-mac_x86
	Supported Android versions:
		4.0.3  (API level 15)
		4.1    (API level 16)
		4.2    (API level 17)
		4.3    (API level 18)
		4.4    (API level 19)
		4.4.87 (API level 20)
		5.0    (API level 21)
		5.1    (API level 22)
		6.0    (API level 23)
		6.0.99 (API level 24)

SDK Tools Version: 25.1.7
SDK Platform Tools Version: 24
SDK Build Tools Version: 24

Java SDK: /Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home
java version "1.8.0_77"
Java(TM) SE Runtime Environment (build 1.8.0_77-b03)
Java HotSpot(TM) 64-Bit Server VM (build 25.77-b03, mixed mode)

Android Designer EPL code available here:
https://github.com/xamarin/AndroidDesigner.EPL

=== Xamarin Android Player ===

Not Installed

=== Xamarin.iOS ===

Version: 9.9.0.288 (Visual Studio Professional)
Hash: ef65ca5
Branch: master
Build date: 2016-06-07 21:27:36-0400

=== Build Information ===

Release ID: 601004963
Git revision: b37c52e0249c009e308eb144957ac97d1003ec0e
Build date: 2016-06-10 12:00:02-04
Xamarin addins: db4bae9b11a32d16ee009695cd1bfa36455187b0
Build lane: monodevelop-lion-cycle8-preview2

=== Operating System ===

Mac OS X 10.11.5
Darwin retina.restphone.com 15.5.0 Darwin Kernel Version 15.5.0
    Tue Apr 19 18:36:36 PDT 2016
    root:xnu-3248.50.21~8/RELEASE_X86_64 x86_64
Comment 2 Greg Munn 2016-07-08 20:02:14 UTC
I was able to reproduce this with xbuild.
Comment 3 Jonathan Pryor 2016-07-09 00:29:49 UTC
Thank you for the bug report.

This is interesting because it *invalidates* my previous looks into Bug #24709 and Bug #39520, where I thought the presence of `@` in the field names was an FSharp.CodeDom.Compiler bug:

>     global.LibFSherp.Resource_String.library_name@ <- Resource_Resource_String.library_name@

That's just wonderful for my ego. Wait, that's backwards.

~~~

Background: During the build process several things happen:

1. All files with a Build action of `@(AndroidResource)` are copied into an intermediate `res` directory and processed with the Android SDK `aapt` tool to generate an `R.java` file.

2. This `R.java` file is processed through a glorious process of Regular Expressions and System.CodeDom to create Resource.Designer.cs.

https://github.com/xamarin/xamarin-android/blob/3885fcc/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs#L91-L93
https://github.com/xamarin/xamarin-android/blob/3885fcc/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs

However, that's not all it does. Android Library resource ID values aren't constant, they're read/write, and their values need to be updated by the Application during startup so that the Library's `library_name` resource ID corresponds to the Application's `library_name` resource ID, so that the App may override its value.

All referenced projects are processed here:

https://github.com/xamarin/xamarin-android/blob/3885fcc/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs#L124-L125
https://github.com/xamarin/xamarin-android/blob/3885fcc/src/Xamarin.Android.Build.Tasks/Utilities/ResourceDesignerImportGenerator.cs

The actual compiler error is due to the interaction of two things:

1. ResourceDesignerImportGenerator.cs assumes something akin to C# Resource.Designer.cs output for referenced assemblies, in which static read/write *fields* are present:

  partial class Resource {
    partial class String {
      public static int library_name = 2130837504

      static String()
      {
        global::Android.Runtime.ResourceIdManager.UpdateIdValues();
      }
    }
  }

A static constructor is also assumed so that when the Library assembly uses `Resource.String.library_name`, we ensure that `library_name` has the correct App-provided value.

Note that ResourceDesignerImportGenerator.cs assumes *fields*, and uses CodeFieldReferenceExpression:

https://github.com/xamarin/xamarin-android/blob/3885fcc/src/Xamarin.Android.Build.Tasks/Utilities/ResourceDesignerImportGenerator.cs#L54-L59

2. The F# compiler *doesn't use fields like this*. The generated F# code is:

    type
      Resource() =
    and
      Resource_String() = 
        static val mutable private library_name:int

To my non-F# eyes, that looks like a field. It isn't. Disassemble the F# library project, and I see:

  .class public ... Resource_String
  {
    .field  assembly static  int32 library_name@

    .method assembly static specialname default
      int32 get_library_name () ...

    .method assembly static specialname default
      void set_library_name(int32 value) ...

    .property int32 library_name () {
      .set default void set_library_name (int32 value)
      .get default int32 get_library_name ()
    }
  }

~~~

Xamarin.Android is looking for a *field* named `library_name`. For whatever reason -- and I have no idea what that reason is -- CodeDom is resolving CodeFieldReferenceExpression(typeof(Resource_String), "library_name") to `Resource_String.library_name@`, INCLUDING THE `@`.

THIS is why the generated Resource.Designer.cs contains a *trailing* `@`: because the F# assembly has a "similar" field name that ends with `@`, and that field name is emitted by the C# CodeDom compiler.
Comment 4 Jonathan Pryor 2016-07-09 00:39:36 UTC
At present, I have no idea what the correct fix is, and the previous analysis raises as many questions as it answers:

Did the F# compiler always "mangle" names in this way? If so, why didn't this happen before? Did this start with a specific F# compiler version? Which version? If this was due to an F# compiler change, why did this change?

Then there's the question of how to fix it.

In order for ResourceDesignerImportGenerator.cs to work with the F# library, we'd either need to convince the F# compiler to emit fields again (is that possible?), or update ResourceDesignerImportGenerator.cs to use *properties* (I hope). In order for properties to work, we'd have to update C# CodeDom emission so that properties are actually usable, which in turn may mean moving the initialization statement "out of line," into the constructor.

Which diverges the difference between App Resource.Designer.cs files -- which use `const int` -- from Library Resource.Designer.cs files, unless the C#6 CodeDom support permits C#6 property initializers. (Does it? I don't currently know.)

  partial class Resource {
    partial class String {
      // Would be desired, but I don't know if this is possible
      public static int library_name {get; set;} = 2130837504;
    }
  }

Additionally, requiring such a change would very likely break all existing Android Library assemblies, as they're using fields, and I don't immediately see a way to use *either* a property or a field, depending on what the type actually provides.

Such breakage would be Bad™.
Comment 5 Jonathan Pryor 2016-07-09 00:45:56 UTC
Finally, the analysis in Comment #3 is likely incomplete. Consider the generated App Resource.Designer.cs for this repro:

  public partial class Resource
  {
    public static void UpdateIdValues()
    {
      global::FSharpLib.Resource_String.library_name@ =
        global::Scratch.Bxc42436.Resource_Resource_String.library_name@;
    }
  }

The target field being `Resource_String.library_name@` makes sense, given the IL.

But why does the *source* value end with `@`? Plus, it references a type which doesn't exist, `Resource_Resource_String`.

I suspect that there's a separate but related bug here:

https://github.com/xamarin/xamarin-android/blob/3885fcc/src/Xamarin.Android.Build.Tasks/Utilities/ResourceDesignerImportGenerator.cs#L49-L50

I believe it's assuming that "type nesting" for the source field's type will match that of the destination field's type, which is incorrect when the source and destination fields are in different language assemblies (F# and C#).
Comment 6 Jonathan Pryor 2016-07-09 00:48:17 UTC
To answer Comment #4 wrt CodeDom, CodeMemberProperty doesn't appear to have a way to set an initialization expression; compare to CodeMemberField.InitExpression. Thus, even if C#6 supports "inline" property initializers, CodeDom doesn't.

Pity.
Comment 7 James Moore 2016-07-09 02:43:47 UTC
====
    type
      Resource() =
    and
      Resource_String() = 
        static val mutable private library_name:int

To my non-F# eyes, that looks like a field.
====

I _think_ what you might want is this:

type
  Resource() = class end
and
  Resource_String() =
    static let mutable library_name: int = 0

Should end up with

  .class nested public auto ansi serializable Resource_String
        extends [mscorlib]System.Object
  {
.....

    .field  assembly static  int32 library_name
Comment 8 Jonathan Pryor 2016-07-09 15:48:48 UTC
@James: The thing to keep in mind is that we don't *directly* emit that F# code. We use System.CodeDom to create the code, and the F# CodeDom provider emits the code.

The CodeDom provider thus has leeway to completely ignore whatever we tell it, e.g. if some hypothetical language didn't have "fields" it could emit properties for CodeMemberField.

At present, I don't know how -- or if it's even possible -- to convince the F# CodeDom provider to use `let` instead of `val`. This bears looking into, as `val` seems to "name mangle" the field and emit a property, while `let` doesn't.
Comment 9 Jason Imison 2016-07-11 20:44:24 UTC
public static fields were removed from F# 2.0. 

See here for further information - http://www.ianvoyce.com/index.php/2010/10/01/public-static-fields-gone-from-f-2-0/
Comment 10 Jason Imison 2016-07-12 07:08:43 UTC
type
  Resource() = class end
and
  Resource_String() =
    static let mutable library_name: int = 0
    [<Microsoft.FSharp.Core.DefaultValueAttribute(false)>]
    static val mutable private other_name:int

compiles to

    .field  assembly static  int32 library_name
    .field  assembly static  int32 init@4
    .field  assembly static  int32 other_name@
    .custom instance void class [FSharp.Core]Microsoft.FSharp.Core.DefaultValueAttribute::'.ctor'(bool) =  (01 00 00 00 00 ) // .....

Note that library_name isn't public. other_name@ has a public property wrapper.

Don has given me write access to CodeDom, so I should be able to change the outputted code to be `static let` instead of `static val` but is this going to be enough? It doesn't look like it. @Jon - you still need the fields to be public?
Comment 11 Jason Imison 2016-07-12 07:22:29 UTC
Sorry, other_name@ has a _private_ property wrapper.
Comment 12 Jonathan Pryor 2016-07-12 16:50:05 UTC
> @Jon - you still need the fields to be public?

Yes, because if this type is in a Library project, the App project needs to be able to update the field values.

...which isn't possible, because F# doesn't want public static fields.

What it sounds like is we need:

1. A new F#-compatible design for providing and updating Resource id values.

2. Some way via CodeDom of "probing" which Resource id handling version a Library assembly provides so that the App glue code can Do The Right Thing™.

Unfortunately, I'm not sure what *either* of these would look like. (2) might be "puntable" by *not* using CodeDom, but instead checking for a (new) assembly-level attribute which specifies which Resource ID implementation scheme is in use...

I don't know enough about F#'s constraints to even begin to ponder (1). Suggestions welcome.
Comment 13 Jason Imison 2016-07-12 20:41:33 UTC
public static fields are the only thing that we can't generate from F#.

This probably means that we need to use either properties or singleton instance fields.

Another solution might be to leave the codedom in C# and reference the genned file from F#. Not sure how well that would work in practice.
Comment 14 Jonathan Pryor 2016-07-12 20:45:01 UTC
@Jason: My understanding is that for F# to interact with C# code, the C# code needs to be in a separate assembly, and I'm not sure how to get a .fsproj to *implicitly create* a C# .dll that *isn't* part of a .csproj...

...though I wonder if we could somehow abuse .NET modules? (...and down an inane rabbit hole I fall...)

I'll need to think about using properties.
Comment 15 Jason Imison 2016-07-12 20:48:30 UTC
Yeah, the only way we can reference a C# file is via a csproj.

I also thought about inline IL, which while possible, isn't officially supported.
Comment 16 Jason Imison 2016-07-12 20:51:24 UTC
I had a look at how F# modules look from C#.

    module mymodule=
        let myfield=1

looks like 

	[CompilationMapping (SourceConstructFlags.Module)]
	public static class mymodule
	{
		public static int myfield {
			[DebuggerNonUserCode, CompilerGenerated]
			get {
				return 1;
			}
		}
	}

when decompiled as C#
Comment 17 Miguel de Icaza [MSFT] 2016-08-08 13:47:26 UTC
Team, question: do we need to use CodeDom to generate the F# bindings?   Or the C# bindings for that matter?

Perhaps we can just generate the code manually.

It does not look like we use too much CodeDom on here to begin with, and when we do, we end up doing some manual F# fixups.   

Perhaps we just handled F# separately for what looks like a trivial amount of code.
Comment 18 Jason Imison 2016-08-10 23:27:55 UTC
@miguel CodeDom isn't the issue here. The issue is that F# doesn't support public static fields, so it makes no difference if we use CodeDOM or not.
Comment 19 Jason Imison 2016-09-30 08:18:07 UTC
Fixed in master XS (XamarinStudio-6.2.0.655)
Comment 20 Naqeeb 2016-09-30 11:20:17 UTC
Reproduce Status:
I have checked this issue with XamarinStudio-6.2.0.644_e0b5ce401fc1300733467c2ce6d432dfa519fa5a and able to reproduce successfully. Here is the screencast for the same: http://www.screencast.com/t/9CkCUGFf


Verified Status:
I have checked this issue with XamarinStudio-6.2.0.655_6e6c8a4f700e488cc91e9526b48fba713fecbb4e and observed that it is working fine. Here is the screencast for the same: http://www.screencast.com/t/jy8dqyA0rZDb

Environment info: https://gist.github.com/NaqeebAnsari/dee0be7bee94fa7d3d812de5b606081c

Hence closing this issue.