// scopeAssignedValue scopes a named assigned value exported into the context. func (sb *scopeBuilder) scopeAssignedValue(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // If the assigned value's name is _, then it is anonymous scope. if node.Get(parser.NodeNamedValueName) == ANONYMOUS_REFERENCE { return newScope().ForAnonymousScope(sb.sg.tdg).GetScope() } // If the assigned value is under a rejection, then it is always an error (but nullable, as it // may not be present always). if _, ok := node.TryGetIncomingNode(parser.NodeAssignedRejection); ok { return newScope().Valid().Assignable(sb.sg.tdg.ErrorTypeReference().AsNullable()).GetScope() } // Otherwise, the value is the assignment of the parent statement's expression. parentNode := node.GetIncomingNode(parser.NodeAssignedDestination) switch parentNode.Kind() { case parser.NodeTypeResolveStatement: // The assigned value exported by a resolve statement has the type of its expression. exprScope := sb.getScope(parentNode.GetNode(parser.NodeResolveStatementSource), context) if !exprScope.GetIsValid() { return newScope().Invalid().GetScope() } // If the parent node has a rejection, then the expression may be null. exprType := exprScope.ResolvedTypeRef(sb.sg.tdg) if _, ok := parentNode.TryGetNode(parser.NodeAssignedRejection); ok { exprType = exprType.AsNullable() } return newScope().Valid().Assignable(exprType).GetScope() default: panic(fmt.Sprintf("Unknown node exporting an assigned value: %v", parentNode.Kind())) return newScope().Invalid().GetScope() } }
// scopeKeywordNotExpression scopes a keyword not expression in the SRG. func (sb *scopeBuilder) scopeKeywordNotExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Check for 'is not null' case. parentExpr, hasParentExpr := node.TryGetIncomingNode(parser.NodeBinaryExpressionRightExpr) if hasParentExpr && parentExpr.Kind() == parser.NodeIsComparisonExpression { if node.GetNode(parser.NodeUnaryExpressionChildExpr).Kind() != parser.NodeNullLiteralExpression { sb.decorateWithError(node, "Expression under an 'is not' must be 'null'") return newScope().Invalid().GetScope() } return newScope().Valid().GetScope() } // Check for normal bool-eable. return sb.scopeUnaryExpression(node, "bool", parser.NodeUnaryExpressionChildExpr, context).GetScope() }
// scopeIdentifierExpression scopes an identifier expression in the SRG. func (sb *scopeBuilder) scopeIdentifierExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { name := node.Get(parser.NodeIdentifierExpressionName) if name == ANONYMOUS_REFERENCE { // Make sure this node is under an assignment of some kind. var found = false for _, predicate := range ALLOWED_ANONYMOUS { if _, ok := node.TryGetIncomingNode(predicate); ok { found = true break } } if !found { sb.decorateWithError(node, "Anonymous identifier '_' cannot be used as a value") return newScope().Invalid().GetScope() } return newScope().ForAnonymousScope(sb.sg.tdg).GetScope() } // Lookup the name given, starting at the location of the current node. namedScope, rerr := sb.lookupNamedScope(name, node) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().GetScope() } // Ensure that the named scope has a valid type. if !namedScope.IsValid(context) { return newScope().Invalid().GetScope() } // Warn if we are accessing an assignable value under an async function, as it will be executing // in a different context. if namedScope.IsAssignable() && namedScope.UnderModule() { srgImpl, found := context.getParentContainer(sb.sg.srg) if found && srgImpl.ContainingMember().IsAsyncFunction() { sb.decorateWithWarning(node, "%v '%v' is defined outside the async function and will therefore be unique for each call to this function", namedScope.Title(), name) } } context.staticDependencyCollector.checkNamedScopeForDependency(namedScope) return newScope().ForNamedScope(namedScope, context).GetScope() }
// buildNamedAccess builds the CodeDOM for a member access expression. func (db *domBuilder) buildNamedAccess(node compilergraph.GraphNode, name string, childExprNode *compilergraph.GraphNode) codedom.Expression { scope, _ := db.scopegraph.GetScope(node) namedReference, hasNamedReference := db.scopegraph.GetReferencedName(scope) // Reference to an unknown name or a nullable member access must be a dynamic access. if !hasNamedReference || node.Kind() == parser.NodeNullableMemberAccessExpression { return codedom.DynamicAccess(db.buildExpression(*childExprNode), name, node) } // Reference to a local name is a var or parameter. if namedReference.IsLocal() { return codedom.LocalReference(namedReference.Name(), node) } // Check for a reference to a type. if typeRef, isType := namedReference.Type(); isType { return codedom.StaticTypeReference(typeRef, node) } // Check for a reference to a member. if memberRef, isMember := namedReference.Member(); isMember { // If the member is under a child expression, build as an access expression. if childExprNode != nil { childExpr := db.buildExpression(*childExprNode) _, underFuncCall := node.TryGetIncomingNode(parser.NodeFunctionCallExpressionChildExpr) isAliasedFunctionReference := scope.ResolvedTypeRef(db.scopegraph.TypeGraph()).HasReferredType(db.scopegraph.TypeGraph().FunctionType()) if isAliasedFunctionReference && !underFuncCall { return codedom.DynamicAccess(childExpr, memberRef.Name(), node) } else { return codedom.MemberReference(childExpr, memberRef, node) } } else { // This is a direct access of a static member. Generate an access under the module. return codedom.StaticMemberReference(memberRef, db.scopegraph.TypeGraph().AnyTypeReference(), node) } } panic("Unknown kind of named access") return nil }
// findAddedNameInScope finds the {parameter, with, loop, var} node exposing the given name, if any. func (g *SRG) findAddedNameInScope(name string, node compilergraph.GraphNode) (compilergraph.GraphNode, bool) { nodeSource := node.Get(parser.NodePredicateSource) nodeStartIndex := node.GetValue(parser.NodePredicateStartRune).Int() // Note: This filter ensures that the name is accessible in the scope of the given node by checking that // the node adding the name contains the given node. containingFilter := func(q compilergraph.GraphQuery) compilergraph.Query { startRune := node.GetValue(parser.NodePredicateStartRune).Int() endRune := node.GetValue(parser.NodePredicateEndRune).Int() return q. In(parser.NodePredicateTypeMemberParameter, parser.NodeLambdaExpressionInferredParameter, parser.NodeLambdaExpressionParameter, parser.NodePredicateTypeMemberGeneric, parser.NodeStatementNamedValue, parser.NodeAssignedDestination, parser.NodeAssignedRejection, parser.NodePredicateChild, parser.NodeStatementBlockStatement). InIfKind(parser.NodeStatementBlockStatement, parser.NodeTypeResolveStatement). HasWhere(parser.NodePredicateStartRune, compilergraph.WhereLTE, startRune). HasWhere(parser.NodePredicateEndRune, compilergraph.WhereGTE, endRune) } nit := g.layer.StartQuery(name). In("named"). Has(parser.NodePredicateSource, nodeSource). IsKind(parser.NodeTypeParameter, parser.NodeTypeNamedValue, parser.NodeTypeAssignedValue, parser.NodeTypeVariableStatement, parser.NodeTypeLambdaParameter, parser.NodeTypeGeneric). FilterBy(containingFilter). BuildNodeIterator(parser.NodePredicateStartRune, parser.NodePredicateEndRune) // Sort the nodes found by location and choose the closest node. var results = make(scopeResultNodes, 0) for nit.Next() { node := nit.Node() startIndex := nit.GetPredicate(parser.NodePredicateStartRune).Int() // If the node is a variable statement or assigned value, we have do to additional checks // (since they are not block scoped but rather statement scoped). if node.Kind() == parser.NodeTypeVariableStatement || node.Kind() == parser.NodeTypeAssignedValue { endIndex := nit.GetPredicate(parser.NodePredicateEndRune).Int() if node.Kind() == parser.NodeTypeAssignedValue { if parentNode, ok := node.TryGetIncomingNode(parser.NodeAssignedDestination); ok { endIndex = parentNode.GetValue(parser.NodePredicateEndRune).Int() } else if parentNode, ok := node.TryGetIncomingNode(parser.NodeAssignedRejection); ok { endIndex = parentNode.GetValue(parser.NodePredicateEndRune).Int() } else { panic("Missing assigned parent") } } // Check that the startIndex of the variable statement is <= the startIndex of the parent node if startIndex > nodeStartIndex { continue } // Ensure that the scope starts after the end index of the variable. Otherwise, the variable // name could be used in its initializer expression (which is expressly disallowed). if nodeStartIndex <= endIndex { continue } } results = append(results, scopeResultNode{node, startIndex}) } if len(results) == 1 { // If there is a single result, return it. return results[0].node, true } else if len(results) > 1 { // Otherwise, sort the list by startIndex and choose the one closest to the scope node. sort.Sort(results) return results[0].node, true } return compilergraph.GraphNode{}, false }
// scopeFunctionCallExpression scopes a function call expression in the SRG. func (sb *scopeBuilder) scopeFunctionCallExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Scope the child expression. childExpr := node.GetNode(parser.NodeFunctionCallExpressionChildExpr) childScope := sb.getScope(childExpr, context) if !childScope.GetIsValid() { return newScope().Invalid().GetScope() } // Check if the child expression has a static scope. If so, this is a type conversion between // a nominal type and a base type. if childScope.GetKind() == proto.ScopeKind_STATIC { return sb.scopeTypeConversionExpression(node, context) } else if childScope.GetKind() == proto.ScopeKind_GENERIC { namedScopedRef, found := sb.getNamedScopeForScope(childScope) if found { sb.decorateWithError(node, "Cannot invoke function call on unclarified generic %s %s.", namedScopedRef.Title(), namedScopedRef.Name()) } else { sb.decorateWithError(node, "Cannot invoke function call on unclarified generic scope.") } return newScope().Invalid().GetScope() } namedNode, hasNamedNode := sb.getNamedScopeForScope(childScope) if hasNamedNode { context.staticDependencyCollector.registerNamedDependency(namedNode) } getDescription := func() string { if !hasNamedNode { return "" } return fmt.Sprintf("on %v %v ", namedNode.Title(), namedNode.Name()) } // Ensure the child expression has type function. childType := childScope.ResolvedTypeRef(sb.sg.tdg) if !childType.IsDirectReferenceTo(sb.sg.tdg.FunctionType()) { // If the child type is a function, but nullable, only allow it to be called if the type // is a result of a null access expression. This is a special case to allow writing code // such as `foo?.bar()` easier, without allowing for random `someNullableFunc()` to be // called. // // TODO: It might be a good idea to revisit this decision if we find `someNullableFunc()` to // be a useful pattern as well. if !childType.HasReferredType(sb.sg.tdg.FunctionType()) || childExpr.Kind() != parser.NodeNullableMemberAccessExpression { sb.decorateWithError(node, "Cannot invoke function call on non-function '%v'.", childType) return newScope().Invalid().GetScope() } } // Find the starting index of the nullable parameters. Once all parameters are nullable, // they are considered optional. var nonOptionalIndex = -1 childParameters := childType.Parameters() for parameterIndex, parameterType := range childParameters { if !parameterType.NullValueAllowed() { nonOptionalIndex = parameterIndex } } // Ensure that the parameters of the function call match those of the child type. var index = -1 ait := node.StartQuery(). Out(parser.NodeFunctionCallArgument). BuildNodeIterator() var isValid = true for ait.Next() { index = index + 1 // Resolve the scope of the argument. argumentScope := sb.getScope(ait.Node(), context) if !argumentScope.GetIsValid() { isValid = false nonOptionalIndex = index continue } if index < len(childParameters) { // Ensure the type of the argument matches the parameter. argumentType := argumentScope.ResolvedTypeRef(sb.sg.tdg) serr, exception := argumentType.CheckSubTypeOfWithExceptions(childParameters[index], typegraph.AllowNominalWrappedForData) if serr != nil { sb.decorateWithError(ait.Node(), "Parameter #%v %sexpects type %v: %v", index+1, getDescription(), childParameters[index], serr) isValid = false } // If a nominally-wrapped value was used in place of an argument that expects its data type, then // mark the expression as being a shortcut that needs unwrapping during generation. if exception == typegraph.AllowNominalWrappedForData { sb.decorateWithSecondaryLabel(ait.Node(), proto.ScopeLabel_NOMINALLY_SHORTCUT_EXPR) } } } if index < nonOptionalIndex { sb.decorateWithError(node, "Function call %sexpects %v non-optional arguments, found %v", getDescription(), nonOptionalIndex+1, index+1) return newScope().Invalid().GetScope() } if index >= len(childParameters) { sb.decorateWithError(node, "Function call %sexpects %v arguments, found %v", getDescription(), len(childParameters), index+1) return newScope().Invalid().GetScope() } var returnType = childType.Generics()[0] if childType.IsNullable() { returnType = returnType.AsNullable() } // Check for an awaitable return type. If found and this call is not under an assignment or // arrow, warn. if isValid && returnType.IsDirectReferenceTo(sb.sg.tdg.AwaitableType()) { if !returnType.Generics()[0].IsVoid() { if _, underStatement := node.TryGetIncomingNode(parser.NodeExpressionStatementExpression); underStatement { sb.decorateWithWarning(node, "Returned Awaitable resolves a value of type %v which is not handled", returnType.Generics()[0]) } } } // The function call returns the first generic of the function. return newScope().IsValid(isValid).Resolving(returnType).GetScope() }
// inferLambdaParameterTypes performs type inference to determine the types of the parameters of the // given lambda expression (if necessary). // // Forms supported for inference: // // var someVar = (a, b) => someExpr // someVar(1, 2) // // var<function<void>(...)> = (a, b) => someExpr // // ((a, b) => someExpr)(1, 2) func (sb *scopeBuilder) inferLambdaParameterTypes(node compilergraph.GraphNode, context scopeContext) { // If the lambda has no inferred parameters, nothing more to do. if _, ok := node.TryGetNode(parser.NodeLambdaExpressionInferredParameter); !ok { return } // Otherwise, collect the names and positions of the inferred parameters. pit := node.StartQuery(). Out(parser.NodeLambdaExpressionInferredParameter). BuildNodeIterator() var inferenceParameters = make([]compilergraph.GraphNode, 0) for pit.Next() { inferenceParameters = append(inferenceParameters, pit.Node()) } getInferredTypes := func() ([]typegraph.TypeReference, bool) { // Check if the lambda expression is under a function call expression. If so, we use the types of // the parameters. parentCall, hasParentCall := node.TryGetIncomingNode(parser.NodeFunctionCallExpressionChildExpr) if hasParentCall { return sb.getFunctionCallArgumentTypes(parentCall, context), true } // Check if the lambda expression is under a variable declaration. If so, we try to infer from // either its declared type or its use(s). parentVariable, hasParentVariable := node.TryGetIncomingNode(parser.NodeVariableStatementExpression) if !hasParentVariable { return make([]typegraph.TypeReference, 0), false } // Check if the parent variable has a declared type of function. If so, then we simply // use the declared parameter types. declaredType, hasDeclaredType := sb.getDeclaredVariableType(parentVariable) if hasDeclaredType && declaredType.IsDirectReferenceTo(sb.sg.tdg.FunctionType()) { return declaredType.Parameters(), true } // Otherwise, we find all references of the variable under the parent scope that are, // themselves, under a function call, and intersect the types of arguments found. parentVariableName := parentVariable.Get(parser.NodeVariableStatementName) parentBlock, hasParentBlock := parentVariable.TryGetIncomingNode(parser.NodeStatementBlockStatement) if !hasParentBlock { return make([]typegraph.TypeReference, 0), false } var inferredTypes = make([]typegraph.TypeReference, 0) rit := sb.sg.srg.FindReferencesInScope(parentVariableName, parentBlock) for rit.Next() { funcCall, hasFuncCall := rit.Node().TryGetIncomingNode(parser.NodeFunctionCallExpressionChildExpr) if !hasFuncCall { continue } inferredTypes = sb.sg.tdg.IntersectTypes(inferredTypes, sb.getFunctionCallArgumentTypes(funcCall, context)) } return inferredTypes, true } // Resolve the inferred types and decorate the parameters with them (if any). inferredTypes, hasInferredTypes := getInferredTypes() if hasInferredTypes { for index, inferenceParameter := range inferenceParameters { var inferredType = sb.sg.tdg.AnyTypeReference() if index < len(inferredTypes) { if !inferredTypes[index].IsVoid() { inferredType = inferredTypes[index] } } sb.inferredParameterTypes.Set(string(inferenceParameter.NodeId), inferredType) sb.modifier.Modify(inferenceParameter).DecorateWithTagged(NodePredicateInferredType, inferredType) } } else { for _, inferenceParameter := range inferenceParameters { sb.inferredParameterTypes.Set(string(inferenceParameter.NodeId), sb.sg.tdg.AnyTypeReference()) sb.modifier.Modify(inferenceParameter).DecorateWithTagged(NodePredicateInferredType, sb.sg.tdg.AnyTypeReference()) } } }