Esempio n. 1
0
// 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()
	}
}
Esempio n. 2
0
// 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()
}
Esempio n. 3
0
// 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()
}
Esempio n. 4
0
// 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
}
Esempio n. 5
0
// 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
}
Esempio n. 6
0
// 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()
}
Esempio n. 7
0
// 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())
		}
	}
}