diff --git a/AresScript.Tests/Program.cs b/AresScript.Tests/Program.cs index a5b3b4a5..b3d74d8d 100644 --- a/AresScript.Tests/Program.cs +++ b/AresScript.Tests/Program.cs @@ -218,6 +218,31 @@ public Task SyntaxErrors_AreThrown() return Task.CompletedTask; } + [Test] + public async Task Validation_AllowsMemberAccess_OnUnknownFunctionParameter() + { + var script = """ + def meme(bepis): + bepis.GetTemperature() + """; + + await ValidateScriptAsync(script); + } + + [Test] + public async Task Validation_AssignsVariable_FromUserFunctionCall() + { + var script = """ + def main(): + return 1 + + bepis = main() + assert bepis == 1 + """; + + await ValidateScriptAsync(script); + } + [Test] public async Task Range_OneArg_GeneratesCorrectSequence() { diff --git a/AresScript/Interpreters/AresValidationInterpreter.cs b/AresScript/Interpreters/AresValidationInterpreter.cs index 34782e25..4653db70 100644 --- a/AresScript/Interpreters/AresValidationInterpreter.cs +++ b/AresScript/Interpreters/AresValidationInterpreter.cs @@ -121,7 +121,7 @@ public override async Task VisitFuncBlock(AresLangParser.FuncBlockContext contex { foreach(var parameter in _pendingFunctionParameters.Peek()) { - _environment.AssignVariable(parameter, AresValueHelper.CreateNull()); + _environment.AssignVariable(parameter, CreateUnknownValue()); } } @@ -461,7 +461,7 @@ public override async Task VisitLambdaExpr(AresLangParser.LambdaExprContext cont { foreach(var parameter in parameterNames) { - _environment.AssignVariable(parameter, AresValueHelper.CreateNull()); + _environment.AssignVariable(parameter, CreateUnknownValue()); } await Visit(body); @@ -495,6 +495,7 @@ public override Task VisitMemberAccess([NotNull] AresLangParser.MemberAccessCont { var ctxExpr = context.expression(); var id = context.ID().GetText(); + var receiverSchema = _typeInference.Visit(ctxExpr); if(!TryResolveValue(ctxExpr, out var value)) { throw new AresInterpreterException( @@ -513,6 +514,11 @@ public override Task VisitMemberAccess([NotNull] AresLangParser.MemberAccessCont ); } + if(receiverSchema.Type is AresDataType.Any or AresDataType.UnspecifiedType) + { + return Task.CompletedTask; + } + if(value.KindCase == AresValue.KindOneofCase.StructValue) { if(value.StructValue.Fields.ContainsKey(id)) @@ -533,6 +539,12 @@ public override Task VisitMemberAccess([NotNull] AresLangParser.MemberAccessCont ); } + private static AresValue CreateUnknownValue() + { + // A value with no active oneof kind maps to UnspecifiedType in schema inference. + return new AresValue(); + } + public override async Task VisitFunctionCall(AresLangParser.FunctionCallContext ctx) { var ctxExpr = ctx.expression(); @@ -1028,12 +1040,29 @@ private static int FindParameterIndex(IReadOnlyList parameters, string n case AresLangParser.FunctionCallContext functionCallContext: { var funcId = TryResolveFunctionId(functionCallContext.expression()); - if(funcId is not null && _environment.TryGetSystemFunction(funcId, out var systemFunction)) + if(funcId is null) + { + break; + } + + if(_environment.TryGetValue(funcId, out var aliasValue) && aliasValue.FunctionValue is not null) + { + funcId = aliasValue.FunctionValue.FunctionId; + } + + if(_environment.TryGetSystemFunction(funcId, out var systemFunction)) { var schema = systemFunction.OutputSchema; var dummyValue = InterpreterHelpers.CreateDummyValue(schema); return dummyValue; } + + if(_environment.TryGetUserFunction(funcId, out var _) || _environment.TryGetUserLambda(funcId, out var _)) + { + // We cannot know user-function return shape statically in this pass, but assignment target + // should still be introduced into scope for subsequent validation. + return CreateUnknownValue(); + } break; } } diff --git a/UI/Components/ScriptEditor.razor b/UI/Components/ScriptEditor.razor index 71f14983..a2199406 100644 --- a/UI/Components/ScriptEditor.razor +++ b/UI/Components/ScriptEditor.razor @@ -5,6 +5,7 @@ @code { + private const string DefaultScript = "# Welcome to ARES\ndef main():\n return True\n"; private StandaloneCodeEditor _editor; private IJSObjectReference? _setupModule; @@ -30,8 +31,21 @@ [Parameter] public IMonacoHoverProvider? HoverProvider { get; set; } + [Parameter] + public string? InitialScript { get; set; } + public Task GetScript() => _editor.GetValue(); + public async Task SetScript(string script) + { + if(_editor is null) + { + return; + } + + await _editor.SetValue(script ?? string.Empty); + } + protected override async Task OnAfterRenderAsync(bool firstRender) { if(firstRender) @@ -129,13 +143,14 @@ private StandaloneEditorConstructionOptions EditorConstructionOptions(StandaloneCodeEditor editor) { + var initialScript = string.IsNullOrWhiteSpace(InitialScript) ? DefaultScript : InitialScript; + return new StandaloneEditorConstructionOptions { Language = "ares", AutomaticLayout = true, SemanticHighlightingEnabled = true, - Value = "# Welcome to ARES\ndef main():\n return True\n" + Value = initialScript }; } } -