dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
18.96k stars 4.02k forks source link

Add Script property to InteractiveScriptGlobals and CommandLineScriptGlobals that returns a ScriptContext object #5979

Open tmat opened 8 years ago

tmat commented 8 years ago
public class CommandLineScriptGlobals 
{
   public ScriptContext Script { get; }

   public CommandLineScriptGlobals(..., ScriptContext context)
}

public class InteractiveScriptGlobals 
{
   public ScriptContext Script { get; }

   public InteractiveScriptGlobals(..., ScriptContext context)
}

public class ScriptContext 
{
   public string GetPath([CallerFilePath]string path = null) { get; }
   public string GetDirectory([CallerFilePath]string path = null) { get; }

   public ScriptContext(string path)
}

The Script property would thus be available to scripts:

foreach (var arg in Script.Args) 
   Console.Writeline(File.ReadAllText(Path.Combine(Script.GetDirectory(), arg)));
...

In future we can add more features to this context:

class ScriptContext 
{
    Task<object> EvalAsync(string code, object globals);
}

F# has a similar concept: "fsi" object:

image

tmat commented 8 years ago

@ManishJayaswal @dotnet/interactive @mattwar

tmat commented 8 years ago

@MadsTorgersen Pointed out that Args are likely to be accessed frequently, so we might want to keep it directly on the Globals object. It would also avoid the breaking change.

khellang commented 8 years ago

Here are some things that people were interested in and we (almost) added to scriptcs a couple of years back; https://github.com/scriptcs/scriptcs/pull/404 :smile:

tmat commented 8 years ago

@khellang Thanks for the pointer. I'd be very reluctant to add any Assembly information. That just exposes implementation details of the host and asks for trouble. What was the scenario that it was enabling?

khellang commented 8 years ago

It's from https://github.com/scriptcs/scriptcs/issues/244. It's basically to be able to do assembly scanning etc.

tmat commented 8 years ago

I'd need to see more detailed reasoning than "I need it for Nancy".

khellang commented 8 years ago

Currently I'm doing Assembly.GetCallingAssembly(). Whilst this works, it feels fragile and it's not discoverable.

A lot of existing APIs expect you to pass it an assembly, for whatever reason. It would be nice to have a convenient way of accessing it. Is an Assembly method the recommended way of doing it?

tmat commented 8 years ago

It depends on what such APIs expect to find in the assembly. Could you give examples of the APIs? We can then recommend how to use them.

It could be that these APIs are not well designed and can't be reliably used in scripts (are these APIs usable in IronPython, or other dynamic languages?). You can perhaps come up with a workaround for each API, but exposing assembly as a generally applicable solution would be wrong. It would give the impression that it works in general, but it won't.

khellang commented 8 years ago

Well, the most common example I can think of is scanning for types, e.g. to

  1. Discover web framework controllers/modules. (Granted, these usually work in an AppDomain scope, but still)
  2. Register things in an IoC container. http://docs.autofac.org/en/latest/register/scanning.html#scanning-for-types
  3. Gather entity configurations (with EF). https://msdn.microsoft.com/en-us/library/system.data.entity.modelconfiguration.configuration.configurationregistrar.addfromassembly(v=vs.113).aspx

There are loads and loads of these examples. I guess you could always do something like typeof(T).Assembly, but still, it would be nice to have access to the current submission assembly somewhere :smile:

glennblock commented 8 years ago

As @khellang said this is pretty common. One use case I have used this for is controllers for Web API where the controllers themselves are authored within a script. The assembly is passed to an Assembly Resolver which reflects through the in-memory compiled assembly to get the controllers. Another common example would be a unit-test runner that executes tests within a script.

tmat commented 8 years ago

It seems to me that most of these scenarios would be addressed by providing IEnumerable<Type> Script.GetDeclaredTypes(). Correct?

khellang commented 8 years ago

It seems to me that most of these scenarios would be addressed by providing IEnumerable Script.GetDeclaredTypes(). Correct?

Yes. That would cover a lot of scenarios :smile:

glennblock commented 8 years ago

Yes that should work, if one needs the assembly they can grab it from the type.

On Thu, Oct 15, 2015 at 10:57 AM Kristian Hellang notifications@github.com wrote:

It seems to me that most of these scenarios would be addressed by providing IEnumerable Script.GetDeclaredTypes(). Correct?

Yes. That would cover a lot of scenarios [image: :smile:]

— Reply to this email directly or view it on GitHub https://github.com/dotnet/roslyn/issues/5979#issuecomment-148472770.

glennblock commented 8 years ago

Thanks @tmat On Thu, Oct 15, 2015 at 11:32 AM Glenn Block glenn.block@gmail.com wrote:

Yes that should work, if one needs the assembly they can grab it from the type.

On Thu, Oct 15, 2015 at 10:57 AM Kristian Hellang < notifications@github.com> wrote:

It seems to me that most of these scenarios would be addressed by providing IEnumerable Script.GetDeclaredTypes(). Correct?

Yes. That would cover a lot of scenarios [image: :smile:]

— Reply to this email directly or view it on GitHub https://github.com/dotnet/roslyn/issues/5979#issuecomment-148472770.

tmat commented 8 years ago

@glennblock The reason why I don't want to expose assembly directly is because scripts may be compiled into multiple assemblies (e.g. in REPL) and we don't want different behavior between REPL and script files.

GetDeclaredTypes() would also handle type hiding in REPL.

> class C { public int X; }
> class C { public int Y; }
> new C()
C { Y = 0 }

GetDeclaredTypes() would return the last defined C, not the previous ones.

glennblock commented 8 years ago

Nice, thanks for the clarification. Most frameworks that I know of that accept assemblies also accept type collections so this should work. On Thu, Oct 15, 2015 at 11:42 AM Tomáš Matoušek notifications@github.com wrote:

@glennblock https://github.com/glennblock The reason why I don't want to expose assembly directly is because scripts may be compiled into multiple assemblies (e.g. in REPL) and we don't want different behavior between REPL and scripts.

GetDeclaredTypes() will also handle type hiding in REPL.

class C { public int X; } class C { public int Y; } new C() C { X = 0, Y = 0 }

GetDeclaredTypes() will return the last defined C, not the previous ones.

— Reply to this email directly or view it on GitHub https://github.com/dotnet/roslyn/issues/5979#issuecomment-148484331.

ManishJayaswal commented 8 years ago

@tmat moving this to 1.2 milestone