// scopeAssignStatement scopes a assign statement in the SRG. func (sb *scopeBuilder) scopeAssignStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // TODO: Handle tuple assignment once we figure out tuple types // Scope the name. nameScope := sb.getScope(node.GetNode(parser.NodeAssignStatementName), context.withAccess(scopeSetAccess)) // Scope the expression value. exprScope := sb.getScope(node.GetNode(parser.NodeAssignStatementValue), context) if !nameScope.GetIsValid() || !exprScope.GetIsValid() { return newScope().Invalid().GetScope() } // Check that we have a named item. namedScopedRef, found := sb.getNamedScopeForScope(nameScope) if !found { sb.decorateWithError(node, "Cannot assign to non-named value") return newScope().Invalid().GetScope() } // Check that the item is assignable. if !namedScopedRef.IsAssignable() { sb.decorateWithError(node, "Cannot assign to non-assignable %v %v", namedScopedRef.Title(), namedScopedRef.Name()) return newScope().Invalid().GetScope() } // Ensure that we can assign the expr value to the named scope. if serr := exprScope.ResolvedTypeRef(sb.sg.tdg).CheckSubTypeOf(nameScope.AssignableTypeRef(sb.sg.tdg)); serr != nil { sb.decorateWithError(node, "Cannot assign value to %v %v: %v", namedScopedRef.Title(), namedScopedRef.Name(), serr) return newScope().Invalid().GetScope() } return newScope().Valid().GetScope() }
// scopeUnaryExpression scopes a unary expression in the SRG. func (sb *scopeBuilder) scopeUnaryExpression(node compilergraph.GraphNode, opName string, predicate compilergraph.Predicate, context scopeContext) *scopeInfoBuilder { // Get the scope of the sub expression. childScope := sb.getScope(node.GetNode(predicate), context) // Ensure that the child scope is valid. if !childScope.GetIsValid() { return newScope().Invalid() } // Ensure that the operator exists under the resolved type. childType := childScope.ResolvedTypeRef(sb.sg.tdg) module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) operator, rerr := childType.ResolveAccessibleMember(opName, module, typegraph.MemberResolutionOperator) if rerr != nil { sb.decorateWithError(node, "Operator '%v' is not defined on type '%v'", opName, childType) return newScope().Invalid() } returnType, _ := operator.ReturnType() // Check for nullable values. if childType.NullValueAllowed() { sb.decorateWithError(node, "Cannot invoke operator '%v' on nullable type '%v'", opName, childType) return newScope().Invalid().CallsOperator(operator).Resolving(returnType.TransformUnder(childType)) } return newScope().Valid().CallsOperator(operator).Resolving(returnType.TransformUnder(childType)) }
// scopeIndexerExpression scopes an indexer expression (slice with single numerical index) in the SRG. func (sb *scopeBuilder) scopeIndexerExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Lookup the indexing operator. var opName = "index" if context.accessOption == scopeSetAccess { opName = "setindex" } operator, childType, found := sb.scopeSliceChildExpression(node, opName, context) if !found { return newScope().Invalid().GetScope() } // Scope the index expression. exprScope := sb.getScope(node.GetNode(parser.NodeSliceExpressionIndex), context) if !exprScope.GetIsValid() { return newScope().Invalid().GetScope() } // Ensure the index expression type matches that expected. exprType := exprScope.ResolvedTypeRef(sb.sg.tdg) parameterType := operator.ParameterTypes()[0].TransformUnder(childType) if serr := exprType.CheckSubTypeOf(parameterType); serr != nil { sb.decorateWithError(node, "Indexer parameter must be type %v: %v", parameterType, serr) return newScope().Invalid().GetScope() } if context.accessOption == scopeSetAccess { return newScope().Valid().ForNamedScopeUnderType(sb.getNamedScopeForMember(operator), childType, context).GetScope() } else { returnType, _ := operator.ReturnType() return newScope().Valid().CallsOperator(operator).Resolving(returnType.TransformUnder(childType)).GetScope() } }
// buildResolveStatement builds the CodeDOM for a resolve statement. func (db *domBuilder) buildResolveStatement(node compilergraph.GraphNode) (codedom.Statement, codedom.Statement) { sourceExpr := db.getExpression(node, parser.NodeResolveStatementSource) destinationNode := node.GetNode(parser.NodeAssignedDestination) destinationScope, _ := db.scopegraph.GetScope(destinationNode) destinationName := destinationNode.Get(parser.NodeNamedValueName) var destinationStatement codedom.Statement = nil if !destinationScope.GetIsAnonymousReference() { destinationStatement = codedom.VarDefinitionWithInit(destinationName, sourceExpr, node) } else { destinationStatement = codedom.ExpressionStatement(sourceExpr, node) destinationName = "" } // If the resolve statement has a rejection value, then we need to wrap the source expression // call to catch any rejections *or* exceptions. rejectionNode, hasRejection := node.TryGetNode(parser.NodeAssignedRejection) if hasRejection { rejectionName := rejectionNode.Get(parser.NodeNamedValueName) rejectionScope, _ := db.scopegraph.GetScope(rejectionNode) if rejectionScope.GetIsAnonymousReference() { rejectionName = "" } empty := codedom.EmptyStatement(node) return codedom.ResolveExpression(sourceExpr, destinationName, rejectionName, empty, node), empty } else { // Otherwise, we simply execute the expression, optionally assigning it to the // destination variable. return destinationStatement, destinationStatement } }
// scopeInlineLambaExpression scopes an inline lambda expression node in the SRG. func (sb *scopeBuilder) scopeInlineLambaExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var returnType = sb.sg.tdg.AnyTypeReference() // Scope the lambda's internal expression. exprScope := sb.getScope(node.GetNode(parser.NodeLambdaExpressionChildExpr), context) if exprScope.GetIsValid() { returnType = exprScope.ResolvedTypeRef(sb.sg.tdg) } // Build the function type. var functionType = sb.sg.tdg.FunctionTypeReference(returnType) // Add the parameter types. pit := node.StartQuery(). Out(parser.NodeLambdaExpressionInferredParameter). BuildNodeIterator() for pit.Next() { parameterType, hasParameterType := sb.inferredParameterTypes.Get(string(pit.Node().NodeId)) if hasParameterType { functionType = functionType.WithParameter(parameterType.(typegraph.TypeReference)) } else { functionType = functionType.WithParameter(sb.sg.tdg.AnyTypeReference()) } } return newScope().IsValid(exprScope.GetIsValid()).Resolving(functionType).GetScope() }
// buildCastExpression builds the CodeDOM for a cast expression. func (db *domBuilder) buildCastExpression(node compilergraph.GraphNode) codedom.Expression { childExpr := db.getExpression(node, parser.NodeCastExpressionChildExpr) // Determine the resulting type. scope, _ := db.scopegraph.GetScope(node) resultingType := scope.ResolvedTypeRef(db.scopegraph.TypeGraph()) // If the resulting type is a structural subtype of the child expression's type, then // we are accessing the automatically composited inner instance. childScope, _ := db.scopegraph.GetScope(node.GetNode(parser.NodeCastExpressionChildExpr)) childType := childScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) if childType.CheckStructuralSubtypeOf(resultingType) { return codedom.NestedTypeAccess(childExpr, resultingType, node) } // Otherwise, add a cast call with the cast type. typeLiteral := codedom.TypeLiteral(resultingType, node) allowNull := codedom.LiteralValue("false", node) if resultingType.NullValueAllowed() { allowNull = codedom.LiteralValue("true", node) } return codedom.RuntimeFunctionCall(codedom.CastFunction, []codedom.Expression{childExpr, typeLiteral, allowNull}, node) }
// buildArrowStatement builds the CodeDOM for an arrow statement. func (db *domBuilder) buildArrowStatement(node compilergraph.GraphNode) (codedom.Statement, codedom.Statement) { sourceExpr := codedom.RuntimeFunctionCall( codedom.TranslatePromiseFunction, []codedom.Expression{db.getExpression(node, parser.NodeArrowStatementSource)}, node) destinationNode := node.GetNode(parser.NodeArrowStatementDestination) destinationScope, _ := db.scopegraph.GetScope(destinationNode) var destinationTarget codedom.Expression = nil var rejectionTarget codedom.Expression = nil // Retrieve the expression of the destination variable. if !destinationScope.GetIsAnonymousReference() { destinationTarget = db.buildAssignmentExpression(destinationNode, codedom.LocalReference("resolved", node), node) } // Retrieve the expression of the rejection variable. rejectionNode, hasRejection := node.TryGetNode(parser.NodeArrowStatementRejection) if hasRejection { rejectionScope, _ := db.scopegraph.GetScope(rejectionNode) if !rejectionScope.GetIsAnonymousReference() { rejectionTarget = db.buildAssignmentExpression(rejectionNode, codedom.LocalReference("rejected", node), node) } } empty := codedom.EmptyStatement(node) promise := codedom.ArrowPromise(sourceExpr, destinationTarget, rejectionTarget, empty, node) return promise, empty }
// buildStructuralNewExpression builds the CodeDOM for a structural new expression. func (db *domBuilder) buildStructuralNewExpression(node compilergraph.GraphNode) codedom.Expression { // Collect the full set of initializers, by member. initializers := map[string]codedom.Expression{} eit := node.StartQuery(). Out(parser.NodeStructuralNewExpressionChildEntry). BuildNodeIterator() for eit.Next() { entryScope, _ := db.scopegraph.GetScope(eit.Node()) entryName, _ := db.scopegraph.GetReferencedName(entryScope) entryMember, _ := entryName.Member() initializers[entryMember.Name()] = db.getExpression(eit.Node(), parser.NodeStructuralNewEntryValue) } childScope, _ := db.scopegraph.GetScope(node.GetNode(parser.NodeStructuralNewTypeExpression)) nodeScope, _ := db.scopegraph.GetScope(node) if nodeScope.HasLabel(proto.ScopeLabel_STRUCTURAL_UPDATE_EXPR) { // Build a call to the Clone() method followed by assignments. resolvedTypeRef := childScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) return db.buildStructCloneExpression(resolvedTypeRef, initializers, node) } else { // Build a call to the new() constructor of the type with the required field expressions. staticTypeRef := childScope.StaticTypeRef(db.scopegraph.TypeGraph()) return db.buildStructInitializerExpression(staticTypeRef, initializers, node) } }
// scopeRootTypeExpression scopes a root-type expression in the SRG. func (sb *scopeBuilder) scopeRootTypeExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Get the scope of the sub expression. childScope := sb.getScope(node.GetNode(parser.NodeUnaryExpressionChildExpr), context) // Ensure that the child scope is valid. if !childScope.GetIsValid() { return newScope().Invalid().GetScope() } childType := childScope.ResolvedTypeRef(sb.sg.tdg) // Ensure the child type is not void. if childType.IsVoid() || childType.IsNull() { sb.decorateWithError(node, "Root type operator (&) cannot be applied to value of type %v", childType) return newScope().Invalid().GetScope() } // Ensure the child type is nominal, interface or any. if !childType.IsAny() { referredType := childType.ReferredType() if referredType.TypeKind() == typegraph.NominalType { // The result of the operator is the nominal type's parent type. return newScope().Valid().Resolving(referredType.ParentTypes()[0]).GetScope() } if referredType.TypeKind() != typegraph.ImplicitInterfaceType && referredType.TypeKind() != typegraph.GenericType { sb.decorateWithError(node, "Root type operator (&) cannot be applied to value of type %v", childType) return newScope().Invalid().GetScope() } } // The result of the operator is a value of any type. return newScope().Valid().Resolving(sb.sg.tdg.AnyTypeReference()).GetScope() }
// buildBinaryOperatorExpression builds the CodeDOM for a binary operator. func (db *domBuilder) buildBinaryOperatorExpression(node compilergraph.GraphNode, modifier exprModifier) codedom.Expression { scope, _ := db.scopegraph.GetScope(node) operator, _ := scope.CalledOperator(db.scopegraph.TypeGraph()) if operator.IsNative() { return db.buildNativeBinaryExpression(node, operatorMap[node.Kind()]) } leftExpr := db.getExpression(node, parser.NodeBinaryExpressionLeftExpr) rightExpr := db.getExpression(node, parser.NodeBinaryExpressionRightExpr) leftScope, _ := db.scopegraph.GetScope(node.GetNode(parser.NodeBinaryExpressionLeftExpr)) parentType := leftScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) optimized, wasOptimized := db.buildOptimizedBinaryOperatorExpression(node, parentType, leftExpr, rightExpr) if wasOptimized { return optimized } callExpr := codedom.MemberCall(codedom.StaticMemberReference(operator, parentType, node), operator, []codedom.Expression{leftExpr, rightExpr}, node) if modifier != nil { return modifier(callExpr) } return callExpr }
// scopeTaggedTemplateString scopes a tagged template string expression in the SRG. func (sb *scopeBuilder) scopeTaggedTemplateString(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var isValid = true // Scope the tagging expression. tagScope := sb.getScope(node.GetNode(parser.NodeTaggedTemplateCallExpression), context) if !tagScope.GetIsValid() { isValid = false } // Scope the template string. templateScope := sb.getScope(node.GetNode(parser.NodeTaggedTemplateParsed), context) if !templateScope.GetIsValid() { isValid = false } // Ensure that the tagging expression is a function of type function<(any here)>(slice<string>, slice<stringable>). if tagScope.GetIsValid() { tagType := tagScope.ResolvedTypeRef(sb.sg.tdg) if !tagType.IsDirectReferenceTo(sb.sg.tdg.FunctionType()) || tagType.ParameterCount() != 2 || tagType.Parameters()[0] != sb.sg.tdg.SliceTypeReference(sb.sg.tdg.StringTypeReference()) || tagType.Parameters()[1] != sb.sg.tdg.SliceTypeReference(sb.sg.tdg.StringableTypeReference()) { isValid = false sb.decorateWithError(node, "Tagging expression for template string must be function with parameters ([]string, []stringable). Found: %v", tagType) } return newScope().IsValid(isValid).Resolving(tagType.Generics()[0]).GetScope() } else { return newScope().IsValid(isValid).Resolving(sb.sg.tdg.AnyTypeReference()).GetScope() } }
// scopeSliceLiteralExpression scopes a slice literal expression in the SRG. func (sb *scopeBuilder) scopeSliceLiteralExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var isValid = true declaredTypeNode := node.GetNode(parser.NodeSliceLiteralExpressionType) declaredType, rerr := sb.sg.ResolveSRGTypeRef(sb.sg.srg.GetTypeRef(declaredTypeNode)) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().Resolving(sb.sg.tdg.SliceTypeReference(sb.sg.tdg.AnyTypeReference())).GetScope() } // Scope each of the expressions and ensure they match the slice type. vit := node.StartQuery(). Out(parser.NodeSliceLiteralExpressionValue). BuildNodeIterator() for vit.Next() { valueNode := vit.Node() valueScope := sb.getScope(valueNode, context) if !valueScope.GetIsValid() { isValid = false } else { if serr := valueScope.ResolvedTypeRef(sb.sg.tdg).CheckSubTypeOf(declaredType); serr != nil { isValid = false sb.decorateWithError(node, "Invalid slice literal value: %v", serr) } } } return newScope().IsValid(isValid).Resolving(sb.sg.tdg.SliceTypeReference(declaredType)).GetScope() }
// scopeConditionalExpression scopes a conditional expression in the SRG. func (sb *scopeBuilder) scopeConditionalExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { conditionalExprNode := node.GetNode(parser.NodeConditionalExpressionCheckExpression) thenContext := sb.inferTypesForConditionalExpressionContext(context, conditionalExprNode, inferredDirect) elseContext := sb.inferTypesForConditionalExpressionContext(context, conditionalExprNode, inferredInverted) // Scope the child expressions. checkScope := sb.getScope(conditionalExprNode, context) thenScope := sb.getScope(node.GetNode(parser.NodeConditionalExpressionThenExpression), thenContext) elseScope := sb.getScope(node.GetNode(parser.NodeConditionalExpressionElseExpression), elseContext) if !checkScope.GetIsValid() || !thenScope.GetIsValid() || !elseScope.GetIsValid() { return newScope().Invalid().GetScope() } // Intersect the then and else types. thenType := thenScope.ResolvedTypeRef(sb.sg.tdg) elseType := elseScope.ResolvedTypeRef(sb.sg.tdg) resultType := thenType.Intersect(elseType) // Ensure that the check is a boolean expression. checkType := checkScope.ResolvedTypeRef(sb.sg.tdg) if !checkType.IsDirectReferenceTo(sb.sg.tdg.BoolType()) { sb.decorateWithError(node, "Conditional expression check must be of type 'bool', found: %v", checkType) return newScope().Invalid().GetScope() } return newScope().Valid().Resolving(resultType).GetScope() }
// scopeBooleanBinaryExpression scopes a boolean binary operator expression in the SRG. func (sb *scopeBuilder) scopeBooleanBinaryExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Get the scope of the left and right expressions. leftScope := sb.getScope(node.GetNode(parser.NodeBinaryExpressionLeftExpr), context) rightScope := sb.getScope(node.GetNode(parser.NodeBinaryExpressionRightExpr), context) // Ensure that both scopes are valid. if !leftScope.GetIsValid() || !rightScope.GetIsValid() { return newScope().Invalid().GetScope() } // Ensure that both scopes have type boolean. var isValid = true leftType := leftScope.ResolvedTypeRef(sb.sg.tdg) rightType := rightScope.ResolvedTypeRef(sb.sg.tdg) if !leftType.IsDirectReferenceTo(sb.sg.tdg.BoolType()) { sb.decorateWithError(node, "Boolean operator requires type Boolean for operands. Left hand operand has type: %v", leftType) isValid = false } if !rightType.IsDirectReferenceTo(sb.sg.tdg.BoolType()) { sb.decorateWithError(node, "Boolean operator requires type Boolean for operands. Right hand operand has type: %v", rightType) isValid = false } return newScope().IsValid(isValid).Resolving(sb.sg.tdg.BoolTypeReference()).GetScope() }
// scopeInCollectionExpression scopes an 'in' collection expression in the SRG. func (sb *scopeBuilder) scopeInCollectionExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Get the scope of the left and right expressions. leftScope := sb.getScope(node.GetNode(parser.NodeBinaryExpressionLeftExpr), context) rightScope := sb.getScope(node.GetNode(parser.NodeBinaryExpressionRightExpr), context) // Ensure that both scopes are valid. if !leftScope.GetIsValid() || !rightScope.GetIsValid() { return newScope().Invalid().GetScope() } // Ensure that the right side has a 'contains' operator defined. rightType := rightScope.ResolvedTypeRef(sb.sg.tdg) module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) operator, rerr := rightType.ResolveAccessibleMember("contains", module, typegraph.MemberResolutionOperator) if rerr != nil { sb.decorateWithError(node, "Operator 'contains' is not defined on type '%v'", rightType) return newScope().Invalid().GetScope() } // Ensure the right side is not nullable. if rightType.NullValueAllowed() { sb.decorateWithError(node, "Cannot invoke operator 'in' on nullable value of type '%v'", rightType) return newScope().Invalid().GetScope() } // Ensure that the left side can be used as the operator's parameter. parameterType := operator.ParameterTypes()[0].TransformUnder(rightType) leftType := leftScope.ResolvedTypeRef(sb.sg.tdg) if serr := leftType.CheckSubTypeOf(parameterType); serr != nil { sb.decorateWithError(node, "Cannot invoke operator 'in' with value of type '%v': %v", leftType, serr) return newScope().Invalid().GetScope() } return newScope().Valid().CallsOperator(operator).Resolving(sb.sg.tdg.BoolTypeReference()).GetScope() }
// buildRootTypeExpression builds the CodeDOM for a root type expression. func (db *domBuilder) buildRootTypeExpression(node compilergraph.GraphNode) codedom.Expression { childExprNode := node.GetNode(parser.NodeUnaryExpressionChildExpr) childScope, _ := db.scopegraph.GetScope(childExprNode) childType := childScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) childExpr := db.buildExpression(childExprNode) return codedom.NominalUnwrapping(childExpr, childType, node) }
// scopeExpressionStatement scopes an expression statement in the SRG. func (sb *scopeBuilder) scopeExpressionStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { scope := sb.getScope(node.GetNode(parser.NodeExpressionStatementExpression), context) if !scope.GetIsValid() { return newScope().Invalid().GetScope() } return newScope().Valid().GetScope() }
// scopeLoopStatement scopes a loop statement in the SRG. func (sb *scopeBuilder) scopeLoopStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Scope the underlying block. blockNode := node.GetNode(parser.NodeLoopStatementBlock) blockScope := sb.getScope(blockNode, context.withContinuable(node)) // If the loop has no expression, it is most likely an infinite loop, so we know it is valid and // returns whatever the internal type is. loopExprNode, hasExpr := node.TryGetNode(parser.NodeLoopStatementExpression) if !hasExpr { loopScopeBuilder := newScope(). IsValid(blockScope.GetIsValid()). ReturningTypeOf(blockScope). LabelSetOfExcept(blockScope, proto.ScopeLabel_BROKEN_FLOW) // If the loop contains a break statement somewhere, then it isn't an infinite // loop. if !blockScope.HasLabel(proto.ScopeLabel_BROKEN_FLOW) { loopScopeBuilder.IsTerminatingStatement() } // for { ... } return loopScopeBuilder.GetScope() } // Otherwise, scope the expression. loopExprScope := sb.getScope(loopExprNode, context) if !loopExprScope.GetIsValid() { return newScope(). Invalid(). GetScope() } loopExprType := loopExprScope.ResolvedTypeRef(sb.sg.tdg) // If the loop has a variable defined, we'll check above that the loop expression is a Stream or Streamable. Otherwise, // it must be a boolean value. varNode, hasVar := node.TryGetNode(parser.NodeStatementNamedValue) if hasVar { if !sb.getScope(varNode, context).GetIsValid() { return newScope().Invalid().GetScope() } return newScope().Valid().LabelSetOf(blockScope).GetScope() } else { if !loopExprType.IsDirectReferenceTo(sb.sg.tdg.BoolType()) { sb.decorateWithError(node, "Loop conditional expression must be of type 'bool', found: %v", loopExprType) return newScope(). Invalid(). GetScope() } return newScope().Valid().LabelSetOfExcept(blockScope, proto.ScopeLabel_BROKEN_FLOW).GetScope() } }
// scopeResolveStatement scopes a resolve statement in the SRG. func (sb *scopeBuilder) scopeResolveStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { destinationScope := sb.getScope(node.GetNode(parser.NodeAssignedDestination), context) sourceScope := sb.getScope(node.GetNode(parser.NodeResolveStatementSource), context) isValid := sourceScope.GetIsValid() && destinationScope.GetIsValid() if rejection, ok := node.TryGetNode(parser.NodeAssignedRejection); ok { rejectionScope := sb.getScope(rejection, context) isValid = isValid && rejectionScope.GetIsValid() } return newScope().IsValid(isValid).GetScope() }
// scopeMemberAccessExpression scopes a member access expression in the SRG. func (sb *scopeBuilder) scopeMemberAccessExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Get the scope of the child expression. childScope := sb.getScope(node.GetNode(parser.NodeMemberAccessChildExpr), context) if !childScope.GetIsValid() { return newScope().Invalid().GetScope() } memberName := node.Get(parser.NodeMemberAccessIdentifier) module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) switch childScope.GetKind() { case proto.ScopeKind_VALUE: childType := childScope.ResolvedTypeRef(sb.sg.tdg) if childType.IsNullable() { sb.decorateWithError(node, "Cannot access name '%v' under nullable type '%v'. Please use the ?. operator to ensure type safety.", memberName, childType) return newScope().Invalid().GetScope() } typeMember, rerr := childType.ResolveAccessibleMember(memberName, module, typegraph.MemberResolutionInstance) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().GetScope() } memberScope := sb.getNamedScopeForMember(typeMember) context.staticDependencyCollector.checkNamedScopeForDependency(memberScope) return newScope().ForNamedScopeUnderType(memberScope, childType, context).GetScope() case proto.ScopeKind_GENERIC: namedScope, _ := sb.getNamedScopeForScope(childScope) sb.decorateWithError(node, "Cannot attempt member access of '%v' under %v %v, as it is generic without specification", memberName, namedScope.Title(), namedScope.Name()) return newScope().Invalid().GetScope() case proto.ScopeKind_STATIC: staticType := childScope.StaticTypeRef(sb.sg.tdg) namedScope, _ := sb.getNamedScopeForScope(childScope) memberScope, rerr := namedScope.ResolveStaticMember(memberName, module, staticType) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().GetScope() } return newScope().ForNamedScopeUnderType(memberScope, staticType, context).GetScope() default: panic("Unknown scope 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() }
// scopeStreamMemberAccessExpression scopes a stream member access expression in the SRG. func (sb *scopeBuilder) scopeStreamMemberAccessExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Get the scope of the child expression. childScope := sb.getScope(node.GetNode(parser.NodeMemberAccessChildExpr), context) if !childScope.GetIsValid() { return newScope().Invalid().GetScope() } memberName := node.Get(parser.NodeMemberAccessIdentifier) module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) switch childScope.GetKind() { case proto.ScopeKind_VALUE: childType := childScope.ResolvedTypeRef(sb.sg.tdg) // Ensure the child type is a stream. generics, serr := childType.CheckConcreteSubtypeOf(sb.sg.tdg.StreamType()) if serr != nil { sb.decorateWithError(node, "Cannot attempt stream access of name '%v' under non-stream type '%v': %v", memberName, childType, serr) return newScope().Invalid().GetScope() } valueType := generics[0] typeMember, rerr := valueType.ResolveAccessibleMember(memberName, module, typegraph.MemberResolutionInstance) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().GetScope() } memberScope := sb.getNamedScopeForMember(typeMember) context.staticDependencyCollector.checkNamedScopeForDependency(memberScope) return newScope().ForNamedScopeUnderModifiedType(memberScope, valueType, makeStream, context).GetScope() case proto.ScopeKind_GENERIC: namedScope, _ := sb.getNamedScopeForScope(childScope) sb.decorateWithError(node, "Cannot attempt stream member access of '%v' under %v %v, as it is generic without specification", memberName, namedScope.Title(), namedScope.Name()) return newScope().Invalid().GetScope() case proto.ScopeKind_STATIC: namedScope, _ := sb.getNamedScopeForScope(childScope) sb.decorateWithError(node, "Cannot attempt stream member access of '%v' under %v %v, as it is a static type", memberName, namedScope.Title(), namedScope.Name()) return newScope().Invalid().GetScope() default: panic("Unknown scope kind") } return newScope().Invalid().GetScope() }
// scopeTypeConversionExpression scopes a conversion from a nominal type to a base type. func (sb *scopeBuilder) scopeTypeConversionExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { childScope := sb.getScope(node.GetNode(parser.NodeFunctionCallExpressionChildExpr), context) conversionType := childScope.StaticTypeRef(sb.sg.tdg) // Ensure that the function call has a single argument. ait := node.StartQuery(). Out(parser.NodeFunctionCallArgument). BuildNodeIterator() var index = 0 var isValid = true for ait.Next() { if index > 0 { sb.decorateWithError(node, "Type conversion requires a single argument") isValid = false break } index = index + 1 // Make sure the argument's scope is valid. argumentScope := sb.getScope(ait.Node(), context) if !argumentScope.GetIsValid() { isValid = false continue } // The argument must be a nominal subtype of the conversion type. argumentType := argumentScope.ResolvedTypeRef(sb.sg.tdg) if nerr := argumentType.CheckNominalConvertable(conversionType); nerr != nil { sb.decorateWithError(node, "Cannot perform type conversion: %v", nerr) isValid = false break } if argumentType.IsNullable() { conversionType = conversionType.AsNullable() } } if index == 0 { sb.decorateWithError(node, "Type conversion requires a single argument") isValid = false } // The type conversion returns an instance of the converted type. return newScope().IsValid(isValid).Resolving(conversionType).GetScope() }
// scopeConditionalStatement scopes a conditional statement in the SRG. func (sb *scopeBuilder) scopeConditionalStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var returningType = sb.sg.tdg.VoidTypeReference() var valid = true var labelSet = newLabelSet() conditionalExprNode := node.GetNode(parser.NodeConditionalStatementConditional) // Scope the child block(s). statementBlockContext := sb.inferTypesForConditionalExpressionContext(context, conditionalExprNode, inferredDirect) statementBlockScope := sb.getScope(node.GetNode(parser.NodeConditionalStatementBlock), statementBlockContext) labelSet.AppendLabelsOf(statementBlockScope) if !statementBlockScope.GetIsValid() { valid = false } var isSettlingScope = false elseClauseNode, hasElseClause := node.TryGetNode(parser.NodeConditionalStatementElseClause) if hasElseClause { elseClauseContext := sb.inferTypesForConditionalExpressionContext(context, conditionalExprNode, inferredInverted) elseClauseScope := sb.getScope(elseClauseNode, elseClauseContext) labelSet.AppendLabelsOf(elseClauseScope) if !elseClauseScope.GetIsValid() { valid = false } // The type returned by this conditional is only non-void if both the block and the // else clause return values. returningType = statementBlockScope.ReturnedTypeRef(sb.sg.tdg). Intersect(elseClauseScope.ReturnedTypeRef(sb.sg.tdg)) isSettlingScope = statementBlockScope.GetIsSettlingScope() && elseClauseScope.GetIsSettlingScope() } // Scope the conditional expression and make sure it has type boolean. conditionalExprScope := sb.getScope(conditionalExprNode, context) if !conditionalExprScope.GetIsValid() { return newScope().Invalid().Returning(returningType, isSettlingScope).WithLabelSet(labelSet).GetScope() } conditionalExprType := conditionalExprScope.ResolvedTypeRef(sb.sg.tdg) if !conditionalExprType.IsDirectReferenceTo(sb.sg.tdg.BoolType()) { sb.decorateWithError(node, "Conditional expression must be of type 'bool', found: %v", conditionalExprType) return newScope().Invalid().Returning(returningType, isSettlingScope).WithLabelSet(labelSet).GetScope() } return newScope().IsValid(valid).Returning(returningType, isSettlingScope).WithLabelSet(labelSet).GetScope() }
// scopeMappingLiteralExpression scopes a mapping literal expression in the SRG. func (sb *scopeBuilder) scopeMappingLiteralExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var isValid = true declaredTypeNode := node.GetNode(parser.NodeMappingLiteralExpressionType) declaredType, rerr := sb.sg.ResolveSRGTypeRef(sb.sg.srg.GetTypeRef(declaredTypeNode)) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return newScope().Invalid().Resolving(sb.sg.tdg.MappingTypeReference(sb.sg.tdg.AnyTypeReference())).GetScope() } // Scope each of the entries and ensure they match the mapping value type. eit := node.StartQuery(). Out(parser.NodeMappingLiteralExpressionEntryRef). BuildNodeIterator() for eit.Next() { entryNode := eit.Node() keyNode := entryNode.GetNode(parser.NodeMappingLiteralExpressionEntryKey) valueNode := entryNode.GetNode(parser.NodeMappingLiteralExpressionEntryValue) keyScope := sb.getScope(keyNode, context) valueScope := sb.getScope(valueNode, context) if keyScope.GetIsValid() { localKeyType := keyScope.ResolvedTypeRef(sb.sg.tdg) if serr := localKeyType.CheckSubTypeOf(sb.sg.tdg.StringableTypeReference()); serr != nil { sb.decorateWithError(keyNode, "Mapping literal keys must be of type Stringable: %v", serr) isValid = false } } else { isValid = false } if valueScope.GetIsValid() { localValueType := valueScope.ResolvedTypeRef(sb.sg.tdg) if serr := localValueType.CheckSubTypeOf(declaredType); serr != nil { sb.decorateWithError(keyNode, "Expected mapping values of type %v: %v", declaredType, serr) isValid = false } } else { isValid = false } } return newScope().IsValid(isValid).Resolving(sb.sg.tdg.MappingTypeReference(declaredType)).GetScope() }
// buildIsComparisonExpression builds the CodeDOM for an is comparison operator. func (db *domBuilder) buildIsComparisonExpression(node compilergraph.GraphNode) codedom.Expression { generatedLeftExpr := db.getExpression(node, parser.NodeBinaryExpressionLeftExpr) // Check for a `not` subexpression. If found, we invert the check. op := "==" rightExpr := node.GetNode(parser.NodeBinaryExpressionRightExpr) if rightExpr.Kind() == parser.NodeKeywordNotExpression { op = "!=" rightExpr = rightExpr.GetNode(parser.NodeUnaryExpressionChildExpr) } generatedRightExpr := db.buildExpression(rightExpr) return codedom.NominalWrapping( codedom.BinaryOperation(generatedLeftExpr, op, generatedRightExpr, node), db.scopegraph.TypeGraph().BoolType(), node) }
// buildFunctionCall builds the CodeDOM for a function call. func (db *domBuilder) buildFunctionCall(node compilergraph.GraphNode) codedom.Expression { childExprNode := node.GetNode(parser.NodeFunctionCallExpressionChildExpr) childScope, _ := db.scopegraph.GetScope(childExprNode) // 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 { wrappedExprNode := node.GetNode(parser.NodeFunctionCallArgument) wrappedExprScope, _ := db.scopegraph.GetScope(wrappedExprNode) wrappedExprType := wrappedExprScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) wrappedExpr := db.buildExpression(wrappedExprNode) targetTypeRef := childScope.StaticTypeRef(db.scopegraph.TypeGraph()) // If the targetTypeRef is not nominal or structural, then we know we are unwrapping. if !targetTypeRef.IsNominalOrStruct() { return codedom.NominalUnwrapping(wrappedExpr, wrappedExprType, node) } else { return codedom.NominalRefWrapping(wrappedExpr, wrappedExprType, targetTypeRef, node) } } // Collect the expressions for the arguments. ait := node.StartQuery(). Out(parser.NodeFunctionCallArgument). BuildNodeIterator() arguments := db.buildExpressions(ait, buildExprCheckNominalShortcutting) childExpr := db.buildExpression(childExprNode) // If the function call is to a member, then we return a MemberCall. namedRef, isNamed := db.scopegraph.GetReferencedName(childScope) if isNamed && !namedRef.IsLocal() { member, _ := namedRef.Member() if childExprNode.Kind() == parser.NodeNullableMemberAccessExpression { return codedom.NullableMemberCall(childExpr, member, arguments, node) } return codedom.MemberCall(childExpr, member, arguments, node) } // Otherwise, this is a normal function call with an await. return codedom.AwaitPromise(codedom.FunctionCall(childExpr, arguments, node), node) }
// scopeAssertNotNullExpression scopes an assert-not-null operator expression in the SRG. func (sb *scopeBuilder) scopeAssertNotNullExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Get the scope of the child expression. childScope := sb.getScope(node.GetNode(parser.NodeUnaryExpressionChildExpr), context) if !childScope.GetIsValid() { return newScope().Invalid().GetScope() } nullableType := childScope.ResolvedTypeRef(sb.sg.tdg) // Ensure that the nullable type is nullable. if !nullableType.IsNullable() { sb.decorateWithError(node, "Child expression of an assert not nullable operator must be nullable. Found: %v", nullableType) return newScope().Invalid().GetScope() } return newScope().Valid().Resolving(nullableType.AsNonNullable()).GetScope() }
// buildLoopExpression builds the CodeDOM for a loop expression. func (db *domBuilder) buildLoopExpression(node compilergraph.GraphNode) codedom.Expression { member, found := db.scopegraph.TypeGraph().StreamType().ParentModule().FindMember("MapStream") if !found { panic("Missing MapStream function under Stream's module") } // Retrieve the item type for the members of the stream and the mapped values. mapExpr := node.GetNode(parser.NodeLoopExpressionMapExpression) namedValue := node.GetNode(parser.NodeLoopExpressionNamedValue) namedScope, _ := db.scopegraph.GetScope(namedValue) namedItemType := namedScope.AssignableTypeRef(db.scopegraph.TypeGraph()) mapScope, _ := db.scopegraph.GetScope(mapExpr) mappedItemType := mapScope.ResolvedTypeRef(db.scopegraph.TypeGraph()) // Build a reference to the Map function. mapFunctionReference := codedom.FunctionCall( codedom.StaticMemberReference(member, db.scopegraph.TypeGraph().StreamTypeReference(mappedItemType), node), []codedom.Expression{ codedom.TypeLiteral(namedItemType, node), codedom.TypeLiteral(mappedItemType, node), }, node) // A loop expression is replaced with a call to the Map function, with the stream as the first parameter // and a mapper function which resolves the mapped value as the second. builtMapExpr := db.buildExpression(mapExpr) builtStreamExpr := db.getExpression(node, parser.NodeLoopExpressionStreamExpression) loopValueName := namedValue.Get(parser.NodeNamedValueName) mapperFunction := codedom.FunctionDefinition( []string{}, []string{loopValueName}, codedom.Resolution(builtMapExpr, builtMapExpr.BasisNode()), false, codedom.NormalFunction, builtMapExpr.BasisNode()) return codedom.AwaitPromise( codedom.FunctionCall(mapFunctionReference, []codedom.Expression{builtStreamExpr, mapperFunction}, node), node) }
// scopeStructuralNewExpression scopes a structural new-type expressions. func (sb *scopeBuilder) scopeStructuralNewExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Scope the child expression and ensure it refers to a type or an existing struct. childScope := sb.getScope(node.GetNode(parser.NodeStructuralNewTypeExpression), context) if !childScope.GetIsValid() { return newScope().Invalid().GetScope() } // If the child scope refers to a static type, then we are constructing a new instance of that // type. Otherwise, we are "modifying" an existing structural instance via a clone. if childScope.GetKind() == proto.ScopeKind_STATIC { return sb.scopeStructuralNewTypeExpression(node, childScope, context) } else if childScope.GetKind() == proto.ScopeKind_VALUE { return sb.scopeStructuralNewCloneExpression(node, childScope, context) } else { sb.decorateWithError(node, "Cannot construct non-type, non-struct expression") return newScope().Invalid().GetScope() } }