diff --git a/src/Test/TestCases.Workflows/TestCases.Workflows.csproj b/src/Test/TestCases.Workflows/TestCases.Workflows.csproj index 8b1704b8..621b8ce7 100644 --- a/src/Test/TestCases.Workflows/TestCases.Workflows.csproj +++ b/src/Test/TestCases.Workflows/TestCases.Workflows.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Test/TestCases.Workflows/TestUtils/Project.cs b/src/Test/TestCases.Workflows/TestUtils/Project.cs index 961aeacc..0b7de450 100644 --- a/src/Test/TestCases.Workflows/TestUtils/Project.cs +++ b/src/Test/TestCases.Workflows/TestUtils/Project.cs @@ -2,34 +2,54 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.VisualBasic; using System; using System.Activities; using System.Activities.ExpressionParser; +using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Loader; using System.Threading.Tasks; +using TestCases.Workflows.WF4Samples; namespace TestCases.Workflows.TestUtils { internal class Project { - static readonly MefHostServices HostServices = MefHostServices.Create(new[]{ "Microsoft.CodeAnalysis.Workspaces", + static readonly MefHostServices HostServicesCS = MefHostServices.Create(new[]{ "Microsoft.CodeAnalysis.Workspaces", "Microsoft.CodeAnalysis.CSharp.Workspaces", "Microsoft.CodeAnalysis.Features", "Microsoft.CodeAnalysis.CSharp.Features" } .Select(Assembly.Load)); - private readonly AdhocWorkspace _workspace = new(HostServices); + static readonly MefHostServices HostServicesVB = MefHostServices.Create(new[]{ "Microsoft.CodeAnalysis.Workspaces", + "Microsoft.CodeAnalysis.VisualBasic.Workspaces", "Microsoft.CodeAnalysis.Features", "Microsoft.CodeAnalysis.VisualBasic.Features" } + .Select(Assembly.Load)); + private readonly Dictionary _workspaces = new() + { + { Language.CSharp, new AdhocWorkspace(HostServicesCS) }, + { Language.VisualBasic, new AdhocWorkspace(HostServicesVB) } + }; private readonly MetadataReference[] _references; public Project(MetadataReference[] references) => _references = references; - public async Task Compile(string classCode, string className) + public async Task Compile(string classCode, string className, Language language) { - _workspace.ClearSolution(); - CSharpCompilationOptions compilationOptions = new(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release); - var scriptProjectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), className, className, LanguageNames.CSharp) + if (_workspaces.TryGetValue(language, out var workspace) == false) + { + throw new NotSupportedException(nameof(language)); + } + workspace.ClearSolution(); + + CompilationOptions compilationOptions = language == Language.CSharp + ? new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release) + : new VisualBasicCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release); + + var scriptProjectInfo = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), className, className, + language == Language.CSharp ? LanguageNames.CSharp : LanguageNames.VisualBasic) .WithMetadataReferences(_references) .WithCompilationOptions(compilationOptions); - var scriptProject = _workspace.AddProject(scriptProjectInfo); - _workspace.AddDocument(scriptProject.Id, className, SourceText.From(classCode)); - var compilation = await _workspace.CurrentSolution.Projects.First().GetCompilationAsync(); + + var scriptProject = workspace.AddProject(scriptProjectInfo); + workspace.AddDocument(scriptProject.Id, className, SourceText.From(classCode)); + var compilation = await workspace.CurrentSolution.Projects.First().GetCompilationAsync(); //using var output = File.OpenWrite("Output.dll"); var results = ScriptingAotCompiler.BuildAssembly(compilation, className, AssemblyLoadContext.Default/*, output*/); if (results.HasErrors) diff --git a/src/Test/TestCases.Workflows/TestXamls/AssignWithExpressionNothing.xaml b/src/Test/TestCases.Workflows/TestXamls/AssignWithExpressionNothing.xaml new file mode 100644 index 00000000..232fc7e3 --- /dev/null +++ b/src/Test/TestCases.Workflows/TestXamls/AssignWithExpressionNothing.xaml @@ -0,0 +1,75 @@ + + + + Microsoft.VisualBasic + System + System.Activities + System.Activities.Statements + System.Collections + System.Collections.Generic + System.Collections.ObjectModel + System.Linq + + + + + Microsoft.VisualBasic + mscorlib + System + System.Activities + System.ComponentModel.TypeConverter + System.Core + System.Data + System.Data.Common + System.Data.DataSetExtensions + System.Drawing + System.Drawing.Common + System.Drawing.Primitives + System.Linq + System.ObjectModel + System.Private.CoreLib + System.Xaml + System.Xml + System.Xml.Linq + System.Reflection.DispatchProxy + System.Reflection.TypeExtensions + System.Reflection.Metadata + System.Security.Permissions + System.Configuration.ConfigurationManager + System.ComponentModel + System.Memory + System.Private.Uri + System.Private.ServiceModel + System.Collections + System.Collections.NonGeneric + System.Linq.Expressions + System.Private.DataContractSerialization + System.Runtime.Serialization.Formatters + System.Runtime.Serialization.Primitives + + + + + + + + + + [myVar] + + + + [Nothing] + + + + \ No newline at end of file diff --git a/src/Test/TestCases.Workflows/TestXamls/AssignWithLiteral.xaml b/src/Test/TestCases.Workflows/TestXamls/AssignWithLiteral.xaml new file mode 100644 index 00000000..18344bbe --- /dev/null +++ b/src/Test/TestCases.Workflows/TestXamls/AssignWithLiteral.xaml @@ -0,0 +1,77 @@ + + + + Microsoft.VisualBasic + System + System.Activities + System.Activities.Statements + System.Collections + System.Collections.Generic + System.Collections.ObjectModel + System.Linq + + + + + Microsoft.VisualBasic + mscorlib + System + System.Activities + System.ComponentModel.TypeConverter + System.Core + System.Data + System.Data.Common + System.Data.DataSetExtensions + System.Drawing + System.Drawing.Common + System.Drawing.Primitives + System.Linq + System.ObjectModel + System.Private.CoreLib + System.Xaml + System.Xml + System.Xml.Linq + System.Reflection.DispatchProxy + System.Reflection.TypeExtensions + System.Reflection.Metadata + System.Security.Permissions + System.Configuration.ConfigurationManager + System.ComponentModel + System.Memory + System.Private.Uri + System.Private.ServiceModel + System.Collections + System.Collections.NonGeneric + System.Linq.Expressions + System.Private.DataContractSerialization + System.Runtime.Serialization.Formatters + System.Runtime.Serialization.Primitives + + + + + + + + + + [myVar] + + + + + + + + + + \ No newline at end of file diff --git a/src/Test/TestCases.Workflows/TestXamls/TestHelper.cs b/src/Test/TestCases.Workflows/TestXamls/TestHelper.cs index 7d1a8098..813e130b 100644 --- a/src/Test/TestCases.Workflows/TestXamls/TestHelper.cs +++ b/src/Test/TestCases.Workflows/TestXamls/TestHelper.cs @@ -46,6 +46,8 @@ public enum TestXamls SpecialCharacters, ValueSpecialCharacterCSharp, ValueSpecialCharacterVb, - ImproveAssignabilityOutArgumentActivity + ImproveAssignabilityOutArgumentActivity, + AssignWithLiteral, + AssignWithExpressionNothing } } diff --git a/src/Test/TestCases.Workflows/WF4Samples/Expressions.cs b/src/Test/TestCases.Workflows/WF4Samples/Expressions.cs index 6899bf16..6e23ffe1 100644 --- a/src/Test/TestCases.Workflows/WF4Samples/Expressions.cs +++ b/src/Test/TestCases.Workflows/WF4Samples/Expressions.cs @@ -21,6 +21,12 @@ namespace TestCases.Workflows.WF4Samples { using StringDictionary = Dictionary; + internal enum Language + { + VisualBasic, + CSharp + }; + public abstract class ExpressionsBaseCommon { protected abstract bool CompileExpressions { get; } @@ -171,19 +177,32 @@ public void CSharpCalculationGenerated() public async Task WorkflowWithReadonlyValueTypeVar() { var activity = TestHelper.GetActivityFromXamlResource(TestXamls.WorkflowWithReadonlyValueTypeVar); - var compiledExpressionRoot = await GenerateAndLoadCompiledExpressionRoot(activity); + var compiledExpressionRoot = await GenerateAndLoadCompiledExpressionRoot(activity, "TestXamls_WorkflowWithReadonlyValueTypeVar", Language.CSharp); CompiledExpressionInvoker.SetCompiledExpressionRootForImplementation(activity, compiledExpressionRoot); TestHelper.InvokeWorkflow(activity).ShouldBe(string.Empty); } - private static async Task GenerateAndLoadCompiledExpressionRoot(Activity activity) + [Fact] + public async Task Assign_Value_RequiresCompilation() + { + var assignWithLiteral = TestHelper.GetActivityFromXamlResource(TestXamls.AssignWithLiteral); + var compiledExpressionRoot = await GenerateAndLoadCompiledExpressionRoot(assignWithLiteral, "TestXamls_Assign", Language.VisualBasic); + + var assignWithExpressionNothing = TestHelper.GetActivityFromXamlResource(TestXamls.AssignWithExpressionNothing); + CompiledExpressionInvoker.SetCompiledExpressionRootForImplementation(assignWithExpressionNothing, compiledExpressionRoot); + + var ex = Assert.Throws(() => TestHelper.InvokeWorkflow(assignWithExpressionNothing)); + ex.Message.ShouldBe(@"My Fancy Assign: Value Expression Activity type 'VisualBasicValue`1 (Nothing)' requires compilation in order to run. Please ensure that the workflow has been compiled."); + } + private static async Task GenerateAndLoadCompiledExpressionRoot(Activity activity, string className, + Language language) { using var expressionsStringWriter = new StringWriter(); - var activityName = $"{TestXamls.WorkflowWithReadonlyValueTypeVar}_CompiledExpressionRoot"; + var activityName = $"{className}_CompiledExpressionRoot"; TextExpressionCompilerSettings settings = new() { Activity = activity, - Language = "C#", + Language = language == Language.VisualBasic ? "VB" : "C#", ActivityName = activityName, RootNamespace = null, GenerateAsPartialClass = false, @@ -194,7 +213,7 @@ private static async Task GenerateAndLoadCompiledExpres var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.IsDynamic).Select(References.GetReference).ToArray(); var project = new Project(assemblies); - var compiledExpressionsClassType = await project.Compile(expressionsStringWriter.ToString(), activityName); + var compiledExpressionsClassType = await project.Compile(expressionsStringWriter.ToString(), activityName, language); return (ICompiledExpressionRoot)Activator.CreateInstance(compiledExpressionsClassType, activity); } [Fact] diff --git a/src/UiPath.Workflow.Runtime/ActivityInstance.cs b/src/UiPath.Workflow.Runtime/ActivityInstance.cs index cfed354f..e7b5e039 100644 --- a/src/UiPath.Workflow.Runtime/ActivityInstance.cs +++ b/src/UiPath.Workflow.Runtime/ActivityInstance.cs @@ -8,6 +8,8 @@ namespace System.Activities; using Internals; using Runtime; +using System.Activities.Expressions; +using System.Text; using Tracking; #if DYNAMICUPDATE @@ -857,10 +859,19 @@ internal bool ResolveArguments(ActivityExecutor executor, IDictionary dynamicUpdateVariableIndexes, bool forImplementation) diff --git a/src/UiPath.Workflow.Runtime/Expressions/CompiledExpressionInvoker.cs b/src/UiPath.Workflow.Runtime/Expressions/CompiledExpressionInvoker.cs index 873b7168..83ae0d57 100644 --- a/src/UiPath.Workflow.Runtime/Expressions/CompiledExpressionInvoker.cs +++ b/src/UiPath.Workflow.Runtime/Expressions/CompiledExpressionInvoker.cs @@ -9,6 +9,8 @@ namespace System.Activities.Expressions; public class CompiledExpressionInvoker { + internal static readonly string TextExpressionMetadataRequiresCompilationKey = nameof(TextExpressionMetadataRequiresCompilationKey); + private static readonly AttachableMemberIdentifier compiledExpressionRootProperty = new(typeof(CompiledExpressionInvoker), "CompiledExpressionRoot"); private static readonly AttachableMemberIdentifier compiledExpressionRootForImplementationProperty = @@ -57,7 +59,9 @@ public object InvokeExpression(ActivityContext activityContext) { if (!TryGetCurrentCompiledExpressionRoot(activityContext, out _compiledRoot, out _expressionId)) { - throw FxTrace.Exception.AsError(new NotSupportedException(SR.TextExpressionMetadataRequiresCompilation(_expressionActivity.GetType().Name))); + var exception = new NotSupportedException(SR.TextExpressionMetadataRequiresCompilation($"{_expressionActivity.GetType().Name} ({_textExpression.ExpressionText ?? string.Empty})")); + exception.Data[TextExpressionMetadataRequiresCompilationKey] = true; + throw FxTrace.Exception.AsError(exception); } } }