From f73849b498816500a837b9a1e0411078ab246a08 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 7 Feb 2025 09:44:56 -0800 Subject: [PATCH 1/4] Support compilation for before statements --- bbq/compiler/desugar.go | 453 +++++++++++++++++++++++++++---- bbq/compiler/native_functions.go | 1 + bbq/vm/test/vm_test.go | 194 +++++++++++++ bbq/vm/value_int.go | 15 + 4 files changed, 615 insertions(+), 48 deletions(-) diff --git a/bbq/compiler/desugar.go b/bbq/compiler/desugar.go index 789050950..2a60933fd 100644 --- a/bbq/compiler/desugar.go +++ b/bbq/compiler/desugar.go @@ -50,8 +50,10 @@ type Desugar struct { } type inheritedFunction struct { - interfaceType *sema.InterfaceType - functionDecl *ast.FunctionDeclaration + interfaceType *sema.InterfaceType + functionDecl *ast.FunctionDeclaration + rewrittenBeforeStatements []ast.Statement + elaboration *sema.Elaboration } var _ ast.DeclarationVisitor[ast.Declaration] = &Desugar{} @@ -109,7 +111,7 @@ func (d *Desugar) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration) funcBlock, declaration.ParameterList, ) - postConditions := d.desugarPostConditions( + postConditions, beforeStatements := d.desugarPostConditions( funcName, funcBlock, declaration.ParameterList, @@ -118,6 +120,8 @@ func (d *Desugar) VisitFunctionDeclaration(declaration *ast.FunctionDeclaration) modifiedStatements := make([]ast.Statement, 0) modifiedStatements = append(modifiedStatements, preConditions...) + modifiedStatements = append(modifiedStatements, beforeStatements...) + if funcBlock.HasStatements() { pos := funcBlock.Block.StartPos @@ -405,7 +409,7 @@ func (d *Desugar) tempResultIdentifierExpr(pos ast.Position) *ast.IdentifierExpr func (d *Desugar) desugarPreConditions( enclosingFuncName string, funcBlock *ast.FunctionBlock, - list *ast.ParameterList, + parameterList *ast.ParameterList, ) []ast.Statement { desugaredConditions := make([]ast.Statement, 0) @@ -429,6 +433,7 @@ func (d *Desugar) desugarPreConditions( kind, inheritedFunc, nil, // No result variable for pre-conditions + nil, // No before functions for pre-conditions pos, ) desugaredConditions = append(desugaredConditions, invocation) @@ -459,7 +464,8 @@ func (d *Desugar) desugarPreConditions( conditions, funcBlock, desugaredConditions, - list, + parameterList, + nil, ) return nil @@ -468,10 +474,10 @@ func (d *Desugar) desugarPreConditions( func (d *Desugar) desugarPostConditions( enclosingFuncName string, funcBlock *ast.FunctionBlock, - list *ast.ParameterList, -) []ast.Statement { + parameterList *ast.ParameterList, +) (desugaredConditions []ast.Statement, beforeStatements []ast.Statement) { - desugaredConditions := make([]ast.Statement, 0) + desugaredConditions = make([]ast.Statement, 0) pos := ast.EmptyPosition kind := ast.ConditionKindPost @@ -480,11 +486,13 @@ func (d *Desugar) desugarPostConditions( conditions = funcBlock.PostConditions } - // Desugar self-defined post-conditions + // Desugar locally-defined post-conditions if conditions != nil { postConditionsRewrite := d.elaboration.PostConditionsRewrite(conditions) conditionsList := postConditionsRewrite.RewrittenPostConditions + beforeStatements = postConditionsRewrite.BeforeStatements + for _, condition := range conditionsList { desugaredCondition := d.desugarCondition(condition) desugaredConditions = append(desugaredConditions, desugaredCondition) @@ -494,6 +502,8 @@ func (d *Desugar) desugarPostConditions( // Desugar inherited post-conditions inheritedFuncs, ok := d.inheritedFuncsWithConditions[enclosingFuncName] if ok && len(inheritedFuncs) > 0 { + beforeFuncResults := map[*ast.VariableDeclaration]string{} + // Must be added in reverse order. for i := len(inheritedFuncs) - 1; i >= 0; i-- { inheritedFunc := inheritedFuncs[i] @@ -504,6 +514,25 @@ func (d *Desugar) desugarPostConditions( resultVarType, resultVarExist := d.elaboration.ResultVariableType(inheritedFunc.functionDecl.FunctionBlock) + // If the inherited function has before-statements, then add an invocation + // to call the generated before-statements-function of the interface. + // + // IMPORTANT: inherited before-functions must be visited before the + // inherited post-conditions, because the results of inherited before-functions + // are being passed as arguments to the inherited post conditions. + for _, statement := range inheritedFunc.rewrittenBeforeStatements { + beforeVarDecl := d.desugarInheritedBeforeStatement( + enclosingFuncName, + statement, + inheritedFunc, + resultVarType, + pos, + beforeFuncResults, + ) + + beforeStatements = append(beforeStatements, beforeVarDecl) + } + // If the inherited function has post-conditions, then add an invocation // to call the generated post-condition-function of the interface. @@ -513,6 +542,7 @@ func (d *Desugar) desugarPostConditions( kind, inheritedFunc, resultVarType, + beforeFuncResults, pos, ) desugaredConditions = append(desugaredConditions, invocation) @@ -526,21 +556,86 @@ func (d *Desugar) desugarPostConditions( // If this is a method of a concrete-type then return with the updated statements, // and continue desugaring the rest. if d.canInlineConditions(funcBlock, conditions) { - return desugaredConditions + return desugaredConditions, beforeStatements } // Otherwise, i.e: if this is an interface function with only post-conditions, // (thus not a default function), then generate a separate function for the conditions. + d.generateConditionsFunction( enclosingFuncName, kind, conditions, funcBlock, desugaredConditions, - list, + parameterList, + beforeStatements, ) - return nil + return nil, nil +} + +func (d *Desugar) desugarInheritedBeforeStatement( + enclosingFuncName string, + statement ast.Statement, + inheritedFunc *inheritedFunction, + resultVarType sema.Type, + pos ast.Position, + beforeFuncResults map[*ast.VariableDeclaration]string, +) *ast.VariableDeclaration { + + varDecl, ok := statement.(*ast.VariableDeclaration) + if !ok { + panic(errors.NewUnreachableError()) + } + + // Call to the inherited synthetic function, to get the before-statement results + beforeFuncInvocation := d.inheritedBeforeFunctionInvocation( + enclosingFuncName, + varDecl.Identifier.Identifier, + inheritedFunc, + resultVarType, + pos, + ) + + // Store the result in a variable. + beforeVarDecl := ast.NewVariableDeclaration( + d.memoryGauge, + varDecl.Access, + varDecl.IsConstant, + d.beforeFunctionResultVarName(beforeFuncResults, varDecl), + varDecl.TypeAnnotation, + beforeFuncInvocation, + ast.NewTransfer( + d.memoryGauge, + ast.TransferOperationCopy, + pos, + ), + pos, + nil, + nil, + "", + ) + + beforeFuncResults[varDecl] = beforeVarDecl.Identifier.Identifier + + beforeVarDeclTypes := inheritedFunc.elaboration.VariableDeclarationTypes(varDecl) + d.elaboration.SetVariableDeclarationTypes( + beforeVarDecl, + beforeVarDeclTypes, + ) + return beforeVarDecl +} + +func (d *Desugar) beforeFunctionResultVarName(beforeFuncResults map[*ast.VariableDeclaration]string, varDecl *ast.VariableDeclaration) ast.Identifier { + offset := len(beforeFuncResults) + varName := fmt.Sprintf("$_before%d", offset) + + return ast.NewIdentifier( + nil, + varName, + ast.EmptyPosition, + ) } func (d *Desugar) canInlineConditions(funcBlock *ast.FunctionBlock, conditions *ast.Conditions) bool { @@ -559,6 +654,7 @@ func (d *Desugar) generateConditionsFunction( functionBlock *ast.FunctionBlock, desugaredConditions []ast.Statement, parameterList *ast.ParameterList, + beforeStatements []ast.Statement, ) { pos := conditions.StartPos @@ -584,40 +680,50 @@ func (d *Desugar) generateConditionsFunction( params := make([]*ast.Parameter, len(parameterList.Parameters)) copy(params, parameterList.Parameters) - // For post conditions, also add `result` as a parameter, if needed. - _, needResultVar := d.elaboration.ResultVariableType(functionBlock) - if kind == ast.ConditionKindPost && needResultVar { - params = append(params, ast.NewParameter( - d.memoryGauge, - sema.ArgumentLabelNotRequired, - ast.NewIdentifier( - d.memoryGauge, - resultVariableName, - pos, - ), + if kind == ast.ConditionKindPost { + + // If there are before statements, then add their results + // to be passed in as parameters. + for _, statement := range beforeStatements { + varDecl, isVarDecl := statement.(*ast.VariableDeclaration) + if !isVarDecl { + panic(errors.NewUnreachableError()) + } + + d.generateBeforeFunction( + enclosingFuncName, + functionBlock, + parameterList, + varDecl, + ) - ast.NewTypeAnnotation( + // Add parameter to pass in 'before' values. + params = append(params, ast.NewParameter( d.memoryGauge, - false, + sema.ArgumentLabelNotRequired, + varDecl.Identifier, - // TODO: Pass the proper type here. It can be looked-up from the elaboration (see above). - // However, will have to convert the `sema.Type` to `ast.Type`. - // Currently this `ast.Type` is not needed for the compiler, hence passing a dummy type. - ast.NewNominalType( + ast.NewTypeAnnotation( d.memoryGauge, - ast.NewIdentifier( - d.memoryGauge, - sema.AnyStructType.Name, - pos, - ), - nil, + false, + + // TODO: Pass the proper type here. It can be looked-up from the elaboration (see above). + // However, will have to convert the `sema.Type` to `ast.Type`. + // Currently this `ast.Type` is not needed for the compiler, hence passing a dummy type. + d.anyStructType(pos), + parameterList.StartPos, ), + + nil, parameterList.StartPos, - ), + )) + } - nil, - parameterList.StartPos, - )) + // For post conditions, also add `result` as a parameter, if needed. + _, needResultVar := d.elaboration.ResultVariableType(functionBlock) + if needResultVar { + params = append(params, d.resultVarParameter(pos)) + } } conditionFunc := ast.NewFunctionDeclaration( @@ -656,11 +762,131 @@ func (d *Desugar) generateConditionsFunction( d.modifiedDeclarations = append(d.modifiedDeclarations, conditionFunc) } +// Generates a separate function for the before statements. +func (d *Desugar) generateBeforeFunction( + enclosingFuncName string, + functionBlock *ast.FunctionBlock, + parameterList *ast.ParameterList, + beforeVarDecl *ast.VariableDeclaration, +) { + pos := beforeVarDecl.StartPos + + astRange := functionBlock.PostConditions.Range + + beforeValueReturnStmt := ast.NewReturnStatement(d.memoryGauge, beforeVarDecl.Value, astRange) + + beforeVarDeclTypes := d.elaboration.VariableDeclarationTypes(beforeVarDecl) + + d.elaboration.SetReturnStatementTypes( + beforeValueReturnStmt, + sema.ReturnStatementTypes{ + ValueType: beforeVarDeclTypes.TargetType, + ReturnType: beforeVarDeclTypes.TargetType, + }, + ) + + modifiedFuncBlock := ast.NewFunctionBlock( + d.memoryGauge, + ast.NewBlock( + d.memoryGauge, + []ast.Statement{ + beforeValueReturnStmt, + }, + astRange, + ), + nil, + nil, + ) + + params := make([]*ast.Parameter, len(parameterList.Parameters)) + copy(params, parameterList.Parameters) + + // For post conditions, also add `result` as a parameter, if needed. + _, needResultVar := d.elaboration.ResultVariableType(functionBlock) + if needResultVar { + params = append(params, d.resultVarParameter(pos)) + } + + beforeFunction := ast.NewFunctionDeclaration( + d.memoryGauge, + ast.AccessAll, + ast.FunctionPurityView, + false, + false, + generatedBeforeFuncIdentifier( + d.enclosingInterfaceType, + enclosingFuncName, + beforeVarDecl.Identifier.Identifier, + pos, + ), + nil, + + ast.NewParameterList( + d.memoryGauge, + params, + parameterList.Range, + ), + + ast.NewTypeAnnotation( + d.memoryGauge, + false, + d.anyStructType(pos), + pos, + ), + modifiedFuncBlock, + pos, + "", + ) + + // TODO: Is the generated function needed to be desugared? + + d.modifiedDeclarations = append(d.modifiedDeclarations, beforeFunction) +} + +func (d *Desugar) resultVarParameter(pos ast.Position) *ast.Parameter { + return ast.NewParameter( + d.memoryGauge, + sema.ArgumentLabelNotRequired, + ast.NewIdentifier( + d.memoryGauge, + resultVariableName, + pos, + ), + + ast.NewTypeAnnotation( + d.memoryGauge, + false, + + // TODO: Pass the proper type here. It can be looked-up from the elaboration (see above). + // However, will have to convert the `sema.Type` to `ast.Type`. + // Currently this `ast.Type` is not needed for the compiler, hence passing a dummy type. + d.anyStructType(pos), + pos, + ), + + nil, + pos, + ) +} + +func (d *Desugar) anyStructType(pos ast.Position) *ast.NominalType { + return ast.NewNominalType( + d.memoryGauge, + ast.NewIdentifier( + d.memoryGauge, + sema.AnyStructType.Name, + pos, + ), + nil, + ) +} + func (d *Desugar) inheritedConditionInvocation( funcName string, kind ast.ConditionKind, inheritedFunc *inheritedFunction, resultVarType sema.Type, + beforeFuncResults map[*ast.VariableDeclaration]string, pos ast.Position, ) ast.Statement { @@ -670,7 +896,9 @@ func (d *Desugar) inheritedConditionInvocation( kind, ) - params := inheritedFunc.functionDecl.ParameterList.Parameters + inheritedFuncDecl := inheritedFunc.functionDecl + + params := inheritedFuncDecl.ParameterList.Parameters semaParams := make([]sema.Parameter, 0, len(params)) for _, param := range params { paramTypeAnnotation := d.checker.ConvertTypeAnnotation(param.TypeAnnotation) @@ -682,12 +910,38 @@ func (d *Desugar) inheritedConditionInvocation( }) } - if resultVarType != nil && kind == ast.ConditionKindPost { - semaParams = append(semaParams, sema.Parameter{ - TypeAnnotation: sema.NewTypeAnnotation(resultVarType), - Label: sema.ArgumentLabelNotRequired, - Identifier: resultVariableName, - }) + var beforeValues []ast.Expression + + if kind == ast.ConditionKindPost { + // Add 'before' function results as arguments. + for _, statement := range inheritedFunc.rewrittenBeforeStatements { + varDecl, ok := statement.(*ast.VariableDeclaration) + if !ok { + panic(errors.NewUnreachableError()) + } + + varDeclTypes := inheritedFunc.elaboration.VariableDeclarationTypes(varDecl) + + beforeFunctionResultVarName, ok := beforeFuncResults[varDecl] + if !ok { + panic(errors.NewUnreachableError()) + } + + semaParams = append(semaParams, sema.Parameter{ + TypeAnnotation: sema.NewTypeAnnotation(varDeclTypes.TargetType), + Label: sema.ArgumentLabelNotRequired, + Identifier: beforeFunctionResultVarName, + }) + } + + // Add 'result' variable as an argument. + if resultVarType != nil { + semaParams = append(semaParams, sema.Parameter{ + TypeAnnotation: sema.NewTypeAnnotation(resultVarType), + Label: sema.ArgumentLabelNotRequired, + Identifier: resultVariableName, + }) + } } funcType := sema.NewSimpleFunctionType( @@ -710,11 +964,72 @@ func (d *Desugar) inheritedConditionInvocation( pos, conditionsFuncName, member, + beforeValues, ) return ast.NewExpressionStatement(d.memoryGauge, invocation) } +func (d *Desugar) inheritedBeforeFunctionInvocation( + funcName string, + beforeVarName string, + inheritedFunc *inheritedFunction, + resultVarType sema.Type, + pos ast.Position, +) *ast.InvocationExpression { + + beforeFuncName := generatedBeforeFuncName( + inheritedFunc.interfaceType, + funcName, + beforeVarName, + ) + + inheritedFuncDecl := inheritedFunc.functionDecl + + params := inheritedFuncDecl.ParameterList.Parameters + semaParams := make([]sema.Parameter, 0, len(params)) + for _, param := range params { + paramTypeAnnotation := d.checker.ConvertTypeAnnotation(param.TypeAnnotation) + + semaParams = append(semaParams, sema.Parameter{ + TypeAnnotation: paramTypeAnnotation, + Label: param.Label, + Identifier: param.Identifier.Identifier, + }) + } + + if resultVarType != nil { + semaParams = append(semaParams, sema.Parameter{ + TypeAnnotation: sema.NewTypeAnnotation(resultVarType), + Label: sema.ArgumentLabelNotRequired, + Identifier: resultVariableName, + }) + } + + funcType := sema.NewSimpleFunctionType( + sema.FunctionPurityView, + semaParams, + sema.VoidTypeAnnotation, + ) + + member := sema.NewPublicFunctionMember( + d.memoryGauge, + inheritedFunc.interfaceType, + beforeFuncName, + funcType, + "", + ) + + return d.interfaceDelegationMethodCall( + inheritedFunc.interfaceType, + funcType, + pos, + beforeFuncName, + member, + nil, + ) +} + func generatedConditionsFuncName(interfaceType *sema.InterfaceType, funcName string, kind ast.ConditionKind) string { return fmt.Sprintf("$%s.%s.%sConditions", interfaceType.Identifier, funcName, kind.Keyword()) } @@ -732,6 +1047,27 @@ func generatedConditionsFuncIdentifier( ) } +func generatedBeforeFuncName( + interfaceType *sema.InterfaceType, + funcName string, + beforeVarName string, +) string { + return fmt.Sprintf("$%s.%s.%s", interfaceType.Identifier, funcName, beforeVarName) +} + +func generatedBeforeFuncIdentifier( + interfaceType *sema.InterfaceType, + funcName string, + beforeVarName string, + pos ast.Position, +) ast.Identifier { + return ast.NewIdentifier( + nil, + generatedBeforeFuncName(interfaceType, funcName, beforeVarName), + pos, + ) +} + func astVoidType(pos ast.Position) ast.Type { return ast.NewNominalType( nil, @@ -909,9 +1245,19 @@ func (d *Desugar) inheritedFunctionsWithConditions(compositeType sema.Conforming continue } funcs := inheritedFunctions[name] + + postConditions := functionDecl.FunctionBlock.PostConditions + var rewrittenBeforeStatements []ast.Statement + if postConditions != nil { + postConditionsRewrite := d.elaboration.PostConditionsRewrite(postConditions) + rewrittenBeforeStatements = postConditionsRewrite.BeforeStatements + } + funcs = append(funcs, &inheritedFunction{ - interfaceType: interfaceType, - functionDecl: functionDecl, + interfaceType: interfaceType, + functionDecl: functionDecl, + rewrittenBeforeStatements: rewrittenBeforeStatements, + elaboration: elaboration, }) inheritedFunctions[name] = funcs } @@ -984,6 +1330,7 @@ func (d *Desugar) inheritedDefaultFunctions( pos, funcName, member, + nil, ) funcReturnType := inheritedFuncType.ReturnTypeAnnotation.Type @@ -1069,6 +1416,7 @@ func (d *Desugar) interfaceDelegationMethodCall( pos ast.Position, functionName string, member *sema.Member, + extraArguments []ast.Expression, ) *ast.InvocationExpression { arguments := make([]*ast.Argument, 0) @@ -1112,6 +1460,15 @@ func (d *Desugar) interfaceDelegationMethodCall( arguments = append(arguments, arg) } + for _, argument := range extraArguments { + arg := ast.NewUnlabeledArgument( + d.memoryGauge, + argument, + ) + + arguments = append(arguments, arg) + } + // `FooInterface.defaultFunc(a1, a2)` // // However, when generating code, we need to load "self" as the receiver, diff --git a/bbq/compiler/native_functions.go b/bbq/compiler/native_functions.go index 9cc2a9101..d9e889299 100644 --- a/bbq/compiler/native_functions.go +++ b/bbq/compiler/native_functions.go @@ -44,6 +44,7 @@ func NativeFunctions() map[string]*global { var builtinTypes = []sema.Type{ sema.StringType, sema.AccountType, + sema.IntType, &sema.CapabilityType{}, } diff --git a/bbq/vm/test/vm_test.go b/bbq/vm/test/vm_test.go index ebcb17060..52006ddef 100644 --- a/bbq/vm/test/vm_test.go +++ b/bbq/vm/test/vm_test.go @@ -3644,4 +3644,198 @@ func TestDefaultFunctionsWithConditions(t *testing.T) { }, logs, ) }) + +} + +func TestBeforeFunctionInPostConditions(t *testing.T) { + + t.Parallel() + + t.Run("condition in same type", func(t *testing.T) { + t.Parallel() + + storage := interpreter.NewInMemoryStorage(nil) + + activation := sema.NewVariableActivation(sema.BaseValueActivation) + activation.DeclareValue(stdlib.PanicFunction) + activation.DeclareValue(stdlib.NewStandardLibraryStaticFunction( + "log", + sema.NewSimpleFunctionType( + sema.FunctionPurityView, + []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "value", + TypeAnnotation: sema.AnyStructTypeAnnotation, + }, + }, + sema.VoidTypeAnnotation, + ), + "", + nil, + )) + + var logs []string + vmConfig := &vm.Config{ + Storage: storage, + AccountHandler: &testAccountHandler{}, + NativeFunctionsProvider: func() map[string]vm.Value { + funcs := vm.NativeFunctions() + funcs[commons.LogFunctionName] = vm.NativeFunctionValue{ + ParameterCount: len(stdlib.LogFunctionType.Parameters), + Function: func(config *vm.Config, typeArguments []interpreter.StaticType, arguments ...vm.Value) vm.Value { + logs = append(logs, arguments[0].String()) + return vm.VoidValue{} + }, + } + + return funcs + }, + } + + _, err := compileAndInvokeWithOptions(t, ` + struct Test { + var i: Int + + init() { + self.i = 2 + } + + fun test() { + post { + print(before(self.i).toString()) + print(self.i.toString()) + } + self.i = 5 + } + } + + access(all) view fun print(_ msg: String): Bool { + log(msg) + return true + } + + fun main() { + Test().test() + } + `, + "main", + CompilerAndVMOptions{ + VMConfig: vmConfig, + ParseAndCheckOptions: &ParseAndCheckOptions{ + Config: &sema.Config{ + LocationHandler: singleIdentifierLocationResolver(t), + BaseValueActivationHandler: func(location common.Location) *sema.VariableActivation { + return activation + }, + }, + }, + }, + ) + + require.NoError(t, err) + require.Equal( + t, + []string{ + "2", + "5", + }, logs, + ) + }) + + t.Run("inherited condition", func(t *testing.T) { + t.Parallel() + + storage := interpreter.NewInMemoryStorage(nil) + + activation := sema.NewVariableActivation(sema.BaseValueActivation) + activation.DeclareValue(stdlib.PanicFunction) + activation.DeclareValue(stdlib.NewStandardLibraryStaticFunction( + "log", + sema.NewSimpleFunctionType( + sema.FunctionPurityView, + []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "value", + TypeAnnotation: sema.AnyStructTypeAnnotation, + }, + }, + sema.VoidTypeAnnotation, + ), + "", + nil, + )) + + var logs []string + vmConfig := &vm.Config{ + Storage: storage, + AccountHandler: &testAccountHandler{}, + NativeFunctionsProvider: func() map[string]vm.Value { + funcs := vm.NativeFunctions() + funcs[commons.LogFunctionName] = vm.NativeFunctionValue{ + ParameterCount: len(stdlib.LogFunctionType.Parameters), + Function: func(config *vm.Config, typeArguments []interpreter.StaticType, arguments ...vm.Value) vm.Value { + logs = append(logs, arguments[0].String()) + return vm.VoidValue{} + }, + } + + return funcs + }, + } + + _, err := compileAndInvokeWithOptions(t, ` + struct interface Foo { + var i: Int + + fun test() { + post { + print(before(self.i).toString()) + print(self.i.toString()) + } + self.i = 5 + } + } + + struct Test: Foo { + var i: Int + + init() { + self.i = 2 + } + } + + access(all) view fun print(_ msg: String): Bool { + log(msg) + return true + } + + fun main() { + Test().test() + } + `, + "main", + CompilerAndVMOptions{ + VMConfig: vmConfig, + ParseAndCheckOptions: &ParseAndCheckOptions{ + Config: &sema.Config{ + LocationHandler: singleIdentifierLocationResolver(t), + BaseValueActivationHandler: func(location common.Location) *sema.VariableActivation { + return activation + }, + }, + }, + }, + ) + + require.NoError(t, err) + require.Equal( + t, + []string{ + "2", + "5", + }, logs, + ) + }) } diff --git a/bbq/vm/value_int.go b/bbq/vm/value_int.go index 99d1b552b..f92aa618c 100644 --- a/bbq/vm/value_int.go +++ b/bbq/vm/value_int.go @@ -24,6 +24,7 @@ import ( "github.com/onflow/atree" "github.com/onflow/cadence/interpreter" + "github.com/onflow/cadence/sema" ) type IntValue struct { @@ -135,3 +136,17 @@ func (v IntValue) GreaterEqual(other ComparableValue) BoolValue { } return v.SmallInt >= otherInt.SmallInt } + +// members + +func init() { + typeName := interpreter.PrimitiveStaticTypeInt.String() + + RegisterTypeBoundFunction(typeName, sema.ToStringFunctionName, NativeFunctionValue{ + ParameterCount: len(sema.ToStringFunctionType.Parameters), + Function: func(config *Config, typeArguments []StaticType, value ...Value) Value { + number := value[0].(IntValue) + return NewStringValue(number.String()) + }, + }) +} From ba728ea817b448e7e52e3d6394be0c16bd53e442 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 7 Feb 2025 10:55:35 -0800 Subject: [PATCH 2/4] Add more tests --- bbq/vm/test/vm_test.go | 106 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/bbq/vm/test/vm_test.go b/bbq/vm/test/vm_test.go index 52006ddef..1c7b35818 100644 --- a/bbq/vm/test/vm_test.go +++ b/bbq/vm/test/vm_test.go @@ -3838,4 +3838,110 @@ func TestBeforeFunctionInPostConditions(t *testing.T) { }, logs, ) }) + + t.Run("multiple inherited conditions", func(t *testing.T) { + t.Parallel() + + storage := interpreter.NewInMemoryStorage(nil) + + activation := sema.NewVariableActivation(sema.BaseValueActivation) + activation.DeclareValue(stdlib.PanicFunction) + activation.DeclareValue(stdlib.NewStandardLibraryStaticFunction( + "log", + sema.NewSimpleFunctionType( + sema.FunctionPurityView, + []sema.Parameter{ + { + Label: sema.ArgumentLabelNotRequired, + Identifier: "value", + TypeAnnotation: sema.AnyStructTypeAnnotation, + }, + }, + sema.VoidTypeAnnotation, + ), + "", + nil, + )) + + var logs []string + vmConfig := &vm.Config{ + Storage: storage, + AccountHandler: &testAccountHandler{}, + NativeFunctionsProvider: func() map[string]vm.Value { + funcs := vm.NativeFunctions() + funcs[commons.LogFunctionName] = vm.NativeFunctionValue{ + ParameterCount: len(stdlib.LogFunctionType.Parameters), + Function: func(config *vm.Config, typeArguments []interpreter.StaticType, arguments ...vm.Value) vm.Value { + logs = append(logs, arguments[0].String()) + return vm.VoidValue{} + }, + } + + return funcs + }, + } + + _, err := compileAndInvokeWithOptions(t, ` + struct interface Foo { + var i: Int + + fun test() { + post { + print(before(self.i).toString()) + print(before(self.i + 1).toString()) + print(self.i.toString()) + } + self.i = 8 + } + } + + struct interface Bar: Foo { + var i: Int + + fun test() { + post { + print(before(self.i + 3).toString()) + } + } + } + + + struct Test: Bar { + var i: Int + + init() { + self.i = 2 + } + } + + access(all) view fun print(_ msg: String): Bool { + log(msg) + return true + } + + fun main() { + Test().test() + } + `, + "main", + CompilerAndVMOptions{ + VMConfig: vmConfig, + ParseAndCheckOptions: &ParseAndCheckOptions{ + Config: &sema.Config{ + LocationHandler: singleIdentifierLocationResolver(t), + BaseValueActivationHandler: func(location common.Location) *sema.VariableActivation { + return activation + }, + }, + }, + }, + ) + + require.NoError(t, err) + require.Equal( + t, + []string{"2", "3", "8", "5"}, + logs, + ) + }) } From 8d37b88dae5fbd328645c30e54ad846a0cce2c57 Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Fri, 7 Feb 2025 15:10:33 -0800 Subject: [PATCH 3/4] Get parameter types from the elaboration, rather than converting from AST --- bbq/compiler/desugar.go | 46 ++++++++++++++--------------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/bbq/compiler/desugar.go b/bbq/compiler/desugar.go index 2a60933fd..ab525fd7d 100644 --- a/bbq/compiler/desugar.go +++ b/bbq/compiler/desugar.go @@ -56,6 +56,10 @@ type inheritedFunction struct { elaboration *sema.Elaboration } +func (f inheritedFunction) functionType() *sema.FunctionType { + return f.elaboration.FunctionDeclarationFunctionType(f.functionDecl) +} + var _ ast.DeclarationVisitor[ast.Declaration] = &Desugar{} func NewDesugar( @@ -896,19 +900,9 @@ func (d *Desugar) inheritedConditionInvocation( kind, ) - inheritedFuncDecl := inheritedFunc.functionDecl - - params := inheritedFuncDecl.ParameterList.Parameters - semaParams := make([]sema.Parameter, 0, len(params)) - for _, param := range params { - paramTypeAnnotation := d.checker.ConvertTypeAnnotation(param.TypeAnnotation) - - semaParams = append(semaParams, sema.Parameter{ - TypeAnnotation: paramTypeAnnotation, - Label: param.Label, - Identifier: param.Identifier.Identifier, - }) - } + inheritedFuncType := inheritedFunc.functionType() + parameters := make([]sema.Parameter, len(inheritedFuncType.Parameters)) + copy(parameters, inheritedFuncType.Parameters) var beforeValues []ast.Expression @@ -927,7 +921,7 @@ func (d *Desugar) inheritedConditionInvocation( panic(errors.NewUnreachableError()) } - semaParams = append(semaParams, sema.Parameter{ + parameters = append(parameters, sema.Parameter{ TypeAnnotation: sema.NewTypeAnnotation(varDeclTypes.TargetType), Label: sema.ArgumentLabelNotRequired, Identifier: beforeFunctionResultVarName, @@ -936,7 +930,7 @@ func (d *Desugar) inheritedConditionInvocation( // Add 'result' variable as an argument. if resultVarType != nil { - semaParams = append(semaParams, sema.Parameter{ + parameters = append(parameters, sema.Parameter{ TypeAnnotation: sema.NewTypeAnnotation(resultVarType), Label: sema.ArgumentLabelNotRequired, Identifier: resultVariableName, @@ -946,7 +940,7 @@ func (d *Desugar) inheritedConditionInvocation( funcType := sema.NewSimpleFunctionType( sema.FunctionPurityView, - semaParams, + parameters, sema.VoidTypeAnnotation, ) @@ -984,22 +978,12 @@ func (d *Desugar) inheritedBeforeFunctionInvocation( beforeVarName, ) - inheritedFuncDecl := inheritedFunc.functionDecl - - params := inheritedFuncDecl.ParameterList.Parameters - semaParams := make([]sema.Parameter, 0, len(params)) - for _, param := range params { - paramTypeAnnotation := d.checker.ConvertTypeAnnotation(param.TypeAnnotation) - - semaParams = append(semaParams, sema.Parameter{ - TypeAnnotation: paramTypeAnnotation, - Label: param.Label, - Identifier: param.Identifier.Identifier, - }) - } + inheritedFuncType := inheritedFunc.functionType() + parameters := make([]sema.Parameter, len(inheritedFuncType.Parameters)) + copy(parameters, inheritedFuncType.Parameters) if resultVarType != nil { - semaParams = append(semaParams, sema.Parameter{ + parameters = append(parameters, sema.Parameter{ TypeAnnotation: sema.NewTypeAnnotation(resultVarType), Label: sema.ArgumentLabelNotRequired, Identifier: resultVariableName, @@ -1008,7 +992,7 @@ func (d *Desugar) inheritedBeforeFunctionInvocation( funcType := sema.NewSimpleFunctionType( sema.FunctionPurityView, - semaParams, + parameters, sema.VoidTypeAnnotation, ) From 638f177128724948f8fbf612e0114fa5e17fbc3c Mon Sep 17 00:00:00 2001 From: Supun Setunga Date: Tue, 11 Feb 2025 12:08:23 -0800 Subject: [PATCH 4/4] Fix before function compilation: no need of result variable --- bbq/compiler/desugar.go | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/bbq/compiler/desugar.go b/bbq/compiler/desugar.go index ab525fd7d..74650e63d 100644 --- a/bbq/compiler/desugar.go +++ b/bbq/compiler/desugar.go @@ -516,7 +516,8 @@ func (d *Desugar) desugarPostConditions( continue } - resultVarType, resultVarExist := d.elaboration.ResultVariableType(inheritedFunc.functionDecl.FunctionBlock) + // Result variable must be looked-up in the corresponding elaboration. + resultVarType, resultVarExist := inheritedFunc.elaboration.ResultVariableType(inheritedFunc.functionDecl.FunctionBlock) // If the inherited function has before-statements, then add an invocation // to call the generated before-statements-function of the interface. @@ -529,7 +530,6 @@ func (d *Desugar) desugarPostConditions( enclosingFuncName, statement, inheritedFunc, - resultVarType, pos, beforeFuncResults, ) @@ -583,7 +583,6 @@ func (d *Desugar) desugarInheritedBeforeStatement( enclosingFuncName string, statement ast.Statement, inheritedFunc *inheritedFunction, - resultVarType sema.Type, pos ast.Position, beforeFuncResults map[*ast.VariableDeclaration]string, ) *ast.VariableDeclaration { @@ -598,7 +597,6 @@ func (d *Desugar) desugarInheritedBeforeStatement( enclosingFuncName, varDecl.Identifier.Identifier, inheritedFunc, - resultVarType, pos, ) @@ -805,12 +803,6 @@ func (d *Desugar) generateBeforeFunction( params := make([]*ast.Parameter, len(parameterList.Parameters)) copy(params, parameterList.Parameters) - // For post conditions, also add `result` as a parameter, if needed. - _, needResultVar := d.elaboration.ResultVariableType(functionBlock) - if needResultVar { - params = append(params, d.resultVarParameter(pos)) - } - beforeFunction := ast.NewFunctionDeclaration( d.memoryGauge, ast.AccessAll, @@ -968,7 +960,6 @@ func (d *Desugar) inheritedBeforeFunctionInvocation( funcName string, beforeVarName string, inheritedFunc *inheritedFunction, - resultVarType sema.Type, pos ast.Position, ) *ast.InvocationExpression { @@ -982,14 +973,6 @@ func (d *Desugar) inheritedBeforeFunctionInvocation( parameters := make([]sema.Parameter, len(inheritedFuncType.Parameters)) copy(parameters, inheritedFuncType.Parameters) - if resultVarType != nil { - parameters = append(parameters, sema.Parameter{ - TypeAnnotation: sema.NewTypeAnnotation(resultVarType), - Label: sema.ArgumentLabelNotRequired, - Identifier: resultVariableName, - }) - } - funcType := sema.NewSimpleFunctionType( sema.FunctionPurityView, parameters, @@ -1233,7 +1216,7 @@ func (d *Desugar) inheritedFunctionsWithConditions(compositeType sema.Conforming postConditions := functionDecl.FunctionBlock.PostConditions var rewrittenBeforeStatements []ast.Statement if postConditions != nil { - postConditionsRewrite := d.elaboration.PostConditionsRewrite(postConditions) + postConditionsRewrite := elaboration.PostConditionsRewrite(postConditions) rewrittenBeforeStatements = postConditionsRewrite.BeforeStatements }