// 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 }
// 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 } }
// buildMatchStatement builds the CodeDOM for a match statement. func (db *domBuilder) buildMatchStatement(node compilergraph.GraphNode) (codedom.Statement, codedom.Statement) { // Retrieve (or generate) the name of a variable to hold the value being matched against. var matchExprVarName = "" if namedValue, hasNamedValue := node.TryGetNode(parser.NodeStatementNamedValue); hasNamedValue { matchExprVarName = namedValue.Get(parser.NodeNamedValueName) } else { matchExprVarName = db.generateScopeVarName(node) } // Set the match expression's value into the variable. startStatement := codedom.VarDefinitionWithInit(matchExprVarName, db.getExpression(node, parser.NodeMatchStatementExpression), node) getCheckExpression := func(caseTypeRefNode compilergraph.GraphNode) codedom.Expression { caseTypeLiteral, _ := db.scopegraph.ResolveSRGTypeRef( db.scopegraph.SourceGraph().GetTypeRef(caseTypeRefNode)) return codedom.NominalWrapping( codedom.RuntimeFunctionCall(codedom.IsTypeFunction, []codedom.Expression{ codedom.LocalReference(matchExprVarName, caseTypeRefNode), codedom.TypeLiteral(caseTypeLiteral, caseTypeRefNode), }, caseTypeRefNode), db.scopegraph.TypeGraph().BoolType(), caseTypeRefNode) } return db.buildJumpingCaseStatement(node, parser.NodeMatchStatementCase, parser.NodeMatchStatementCaseStatement, parser.NodeMatchStatementCaseTypeReference, startStatement, getCheckExpression) }
// scopeLambdaExpression scopes a lambda expression in the SRG. func (sb *scopeBuilder) scopeLambdaExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { if _, ok := node.TryGetNode(parser.NodeLambdaExpressionBlock); ok { return sb.scopeFullLambaExpression(node, context) } else { return sb.scopeInlineLambaExpression(node, context) } }
// scopeImplementedMember scopes an implemented type member. func (sb *scopeBuilder) scopeImplementedMember(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { if body, hasBody := node.TryGetNode(parser.NodePredicateBody); hasBody { scope := sb.getScope(body, context) return *scope } else { return newScope().GetScope() } }
// tryGetExpression attempts to retrieve the predicate on the given node and, if found, build it as an // expression. func (db *domBuilder) tryGetExpression(node compilergraph.GraphNode, predicate compilergraph.Predicate) (codedom.Expression, bool) { childNode, hasChild := node.TryGetNode(predicate) if !hasChild { return nil, false } return db.buildExpression(childNode), true }
// tryGetStatements attempts to retrieve the predicate on the given node and, if found, build it as // start and end statements. func (db *domBuilder) tryGetStatements(node compilergraph.GraphNode, predicate compilergraph.Predicate) (codedom.Statement, codedom.Statement, bool) { childNode, hasChild := node.TryGetNode(predicate) if !hasChild { return nil, nil, false } start, end := db.buildStatements(childNode) return start, end, true }
// buildSliceExpression builds the CodeDOM for a slicer or indexer expression. func (db *domBuilder) buildSliceExpression(node compilergraph.GraphNode) codedom.Expression { // Check if this is a slice vs an index. _, isIndexer := node.TryGetNode(parser.NodeSliceExpressionIndex) if isIndexer { return db.buildIndexerExpression(node) } else { return db.buildSlicerExpression(node) } }
// scopeSliceExpression scopes a slice expression in the SRG. func (sb *scopeBuilder) scopeSliceExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Check if this is a slice vs an index. _, isIndexer := node.TryGetNode(parser.NodeSliceExpressionIndex) if isIndexer { return sb.scopeIndexerExpression(node, context) } else { return sb.scopeSlicerExpression(node, context) } }
// 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() } }
// buildLambdaExpression builds the CodeDOM for a lambda expression. func (db *domBuilder) buildLambdaExpression(node compilergraph.GraphNode) codedom.Expression { if blockNode, ok := node.TryGetNode(parser.NodeLambdaExpressionBlock); ok { blockStatement, _ := db.buildStatements(blockNode) bodyScope, _ := db.scopegraph.GetScope(blockNode) isGenerator := bodyScope.HasLabel(proto.ScopeLabel_GENERATOR_STATEMENT) return db.buildLambdaExpressionInternal(node, parser.NodeLambdaExpressionParameter, blockStatement, isGenerator) } else { bodyExpr := db.getExpression(node, parser.NodeLambdaExpressionChildExpr) return db.buildLambdaExpressionInternal(node, parser.NodeLambdaExpressionInferredParameter, bodyExpr, false) } }
// 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() }
// buildWithStatement builds the CodeDOM for a with statement. func (db *domBuilder) buildWithStatement(node compilergraph.GraphNode) codedom.Statement { namedVar, hasNamed := node.TryGetNode(parser.NodeStatementNamedValue) var resourceVar = "" if hasNamed { resourceVar = namedVar.Get(parser.NodeNamedValueName) } else { resourceVar = db.generateScopeVarName(node) } resourceExpr := db.getExpression(node, parser.NodeWithStatementExpression) withStatement, _ := db.getStatements(node, parser.NodeWithStatementBlock) return codedom.ResourceBlock(resourceVar, resourceExpr, withStatement, node) }
// 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() }
// scopeReturnStatement scopes a return statement in the SRG. func (sb *scopeBuilder) scopeReturnStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var actualReturnType typegraph.TypeReference = sb.sg.tdg.VoidTypeReference() exprNode, found := node.TryGetNode(parser.NodeReturnStatementValue) if found { exprScope := sb.getScope(exprNode, context) if !exprScope.GetIsValid() { return newScope(). Invalid(). GetScope() } actualReturnType = exprScope.ResolvedTypeRef(sb.sg.tdg) } // Ensure the return types match. expectedReturnType, _ := sb.sg.tdg.LookupReturnType(context.parentImplemented) if expectedReturnType.IsVoid() { if !actualReturnType.IsVoid() { sb.decorateWithError(node, "No return value expected here, found value of type '%v'", actualReturnType) return newScope(). Invalid(). Returning(actualReturnType, true). GetScope() } } else if actualReturnType.IsVoid() { sb.decorateWithError(node, "Expected non-void resolved value") return newScope(). Invalid(). Returning(actualReturnType, true). GetScope() } else { if serr := actualReturnType.CheckSubTypeOf(expectedReturnType); serr != nil { sb.decorateWithError(node, "Expected return value of type '%v': %v", expectedReturnType, serr) return newScope(). Invalid(). Returning(actualReturnType, true). GetScope() } } return newScope(). IsTerminatingStatement(). Valid(). Returning(actualReturnType, true). GetScope() }
// scopeSlicerExpression scopes a slice expression (one with left and/or right expressions) in the SRG. func (sb *scopeBuilder) scopeSlicerExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var isValid = true // Lookup the slice operator. operator, childType, found := sb.scopeSliceChildExpression(node, "slice", context) if !found { isValid = false } scopeAndCheckExpr := func(exprNode compilergraph.GraphNode) bool { exprScope := sb.getScope(exprNode, context) if !exprScope.GetIsValid() { return false } exprType := exprScope.ResolvedTypeRef(sb.sg.tdg) if !exprType.IsDirectReferenceTo(sb.sg.tdg.IntType()) { sb.decorateWithError(node, "Slice index must be of type Integer, found: %v", exprType) return false } return true } // Check the left and/or right expressions. leftNode, hasLeftNode := node.TryGetNode(parser.NodeSliceExpressionLeftIndex) rightNode, hasRightNode := node.TryGetNode(parser.NodeSliceExpressionRightIndex) if hasLeftNode && !scopeAndCheckExpr(leftNode) { isValid = false } if hasRightNode && !scopeAndCheckExpr(rightNode) { isValid = false } if !isValid { return newScope().Invalid().GetScope() } returnType, _ := operator.ReturnType() return newScope().IsValid(isValid).CallsOperator(operator).Resolving(returnType.TransformUnder(childType)).GetScope() }
// scopeFullLambaExpression scopes a fully defined lambda expression node in the SRG. func (sb *scopeBuilder) scopeFullLambaExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { var returnType = sb.sg.tdg.AnyTypeReference() // Check for a defined return type for the lambda expression. returnTypeNode, hasReturnType := node.TryGetNode(parser.NodeLambdaExpressionReturnType) if hasReturnType { resolvedReturnType, rerr := sb.sg.ResolveSRGTypeRef(sb.sg.srg.GetTypeRef(returnTypeNode)) if rerr != nil { panic(rerr) } returnType = resolvedReturnType } // Scope the block. If the function has no defined return type, we use the return type of the block. blockScope := sb.getScope(node.GetNode(parser.NodeLambdaExpressionBlock), context.withImplemented(node)) if !hasReturnType && blockScope.GetIsValid() { returnType = blockScope.ReturnedTypeRef(sb.sg.tdg) } // Build the function type. var functionType = sb.sg.tdg.FunctionTypeReference(returnType) // Add the parameter types. pit := node.StartQuery(). Out(parser.NodeLambdaExpressionParameter). BuildNodeIterator() for pit.Next() { parameterTypeNode := pit.Node().GetNode(parser.NodeParameterType) parameterType, perr := sb.sg.ResolveSRGTypeRef(sb.sg.srg.GetTypeRef(parameterTypeNode)) if perr != nil { panic(perr) } functionType = functionType.WithParameter(parameterType) } return newScope().IsValid(blockScope.GetIsValid()).Resolving(functionType).GetScope() }
// scopeSmlNormalAttribute scopes an SML expression attribute under a declaration. func (sb *scopeBuilder) scopeSmlAttribute(node compilergraph.GraphNode, propsType typegraph.TypeReference, context scopeContext) (string, bool) { attributeName := node.Get(parser.NodeSmlAttributeName) // If the props type is a struct or class, ensure that the attribute name exists. var allowedValueType = sb.sg.tdg.AnyTypeReference() if propsType.IsRefToStruct() || propsType.IsRefToClass() { module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) resolvedMember, rerr := propsType.ResolveAccessibleMember(attributeName, module, typegraph.MemberResolutionInstance) if rerr != nil { sb.decorateWithError(node, "%v", rerr) return attributeName, false } allowedValueType = resolvedMember.AssignableType() } else { // The props type must be a mapping, so the value must match it value type. allowedValueType = propsType.Generics()[0] } // Scope the attribute value (if any). If none, then we default to a boolean value. var attributeValueType = sb.sg.tdg.BoolTypeReference() valueNode, hasValueNode := node.TryGetNode(parser.NodeSmlAttributeValue) if hasValueNode { attributeValueScope := sb.getScope(valueNode, context) if !attributeValueScope.GetIsValid() { return attributeName, false } attributeValueType = attributeValueScope.ResolvedTypeRef(sb.sg.tdg) } // Ensure it matches the assignable value type. if serr := attributeValueType.CheckSubTypeOf(allowedValueType); serr != nil { sb.decorateWithError(node, "Cannot assign value of type %v for attribute %v: %v", attributeValueType, attributeName, serr) return attributeName, false } return attributeName, true }
// scopeDeclaredValue scopes a declared value (variable statement, variable member, type field). func (sb *scopeBuilder) scopeDeclaredValue(node compilergraph.GraphNode, title string, option requiresInitializerOption, exprPredicate compilergraph.Predicate, context scopeContext) proto.ScopeInfo { var exprScope *proto.ScopeInfo = nil exprNode, hasExpression := node.TryGetNode(exprPredicate) if hasExpression { // Scope the expression. exprScope = sb.getScope(exprNode, context) if !exprScope.GetIsValid() { return newScope().Invalid().GetScope() } } // Load the declared type, if any. declaredType, hasDeclaredType := sb.getDeclaredVariableType(node) if !hasDeclaredType { if exprScope == nil { panic("Somehow ended up with no declared type and no expr scope") } return newScope().Valid().AssignableResolvedTypeOf(exprScope).GetScope() } // Compare against the type of the expression. if hasExpression { exprType := exprScope.ResolvedTypeRef(sb.sg.tdg) if serr := exprType.CheckSubTypeOf(declaredType); serr != nil { sb.decorateWithError(node, "%s '%s' has declared type '%v': %v", title, node.Get(parser.NodeVariableStatementName), declaredType, serr) return newScope().Invalid().GetScope() } } else if option == requiresInitializer { // Make sure if the type is non-nullable that there is an expression. if !declaredType.IsNullable() { sb.decorateWithError(node, "%s '%s' must have explicit initializer as its type '%v' is non-nullable", title, node.Get(parser.NodeVariableStatementName), declaredType) return newScope().Invalid().Assignable(declaredType).GetScope() } } return newScope().Valid().Assignable(declaredType).GetScope() }
// ResolutionPath returns the full resolution path for this type reference. // Panics if this is not a RefKind of TypeRefPath. func (t SRGTypeRef) ResolutionPath() string { compilerutil.DCHECK(func() bool { return t.RefKind() == TypeRefPath }, "Expected type ref path") var resolvePathPieces = make([]string, 0) var currentPath compilergraph.GraphNode = t.GraphNode. GetNode(parser.NodeTypeReferencePath). GetNode(parser.NodeIdentifierPathRoot) for { // Add the path piece to the array. name := currentPath.Get(parser.NodeIdentifierAccessName) resolvePathPieces = append([]string{name}, resolvePathPieces...) // If there is a source, continue searching. source, found := currentPath.TryGetNode(parser.NodeIdentifierAccessSource) if !found { break } currentPath = source } return strings.Join(resolvePathPieces, ".") }
// scopeYieldStatement scopes a yield statement in the SRG. func (sb *scopeBuilder) scopeYieldStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Ensure it returns a stream. returnType, ok := sb.sg.tdg.LookupReturnType(context.parentImplemented) if !ok || !returnType.IsDirectReferenceTo(sb.sg.tdg.StreamType()) { sb.decorateWithError(node, "'yield' statement must be under a function or property returning a Stream. Found: %v", returnType) return newScope(). Invalid(). GetScope() } // Handle the three kinds of yield statement: // - yield break if _, isBreak := node.TryGet(parser.NodeYieldStatementBreak); isBreak { return newScope(). Valid(). WithLabel(proto.ScopeLabel_GENERATOR_STATEMENT). IsTerminatingStatement(). GetScope() } // - yield in {someStreamExpr} if streamExpr, hasStreamExpr := node.TryGetNode(parser.NodeYieldStatementStreamValue); hasStreamExpr { // Scope the stream expression. streamExprScope := sb.getScope(streamExpr, context) if !streamExprScope.GetIsValid() { return newScope(). Invalid(). GetScope() } // Ensure it is is a subtype of the parent stream type. if serr := streamExprScope.ResolvedTypeRef(sb.sg.tdg).CheckSubTypeOf(returnType); serr != nil { sb.decorateWithError(node, "'yield in' expression must have subtype of %v: %v", returnType, serr) return newScope(). Invalid(). GetScope() } return newScope(). Valid(). WithLabel(proto.ScopeLabel_GENERATOR_STATEMENT). GetScope() } // - yield {someExpr} // Scope the value expression. valueExprScope := sb.getScope(node.GetNode(parser.NodeYieldStatementValue), context) if !valueExprScope.GetIsValid() { return newScope(). Invalid(). GetScope() } // Ensure it is is a subtype of the parent stream value type. streamValueType := returnType.Generics()[0] if serr := valueExprScope.ResolvedTypeRef(sb.sg.tdg).CheckSubTypeOf(streamValueType); serr != nil { sb.decorateWithError(node, "'yield' expression must have subtype of %v: %v", streamValueType, serr) return newScope(). Invalid(). GetScope() } return newScope(). Valid(). WithLabel(proto.ScopeLabel_GENERATOR_STATEMENT). 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()) } } }
// scopeMatchStatement scopes a match statement in the SRG. func (sb *scopeBuilder) scopeMatchStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Scope the match expression. matchExprScope := sb.getScope(node.GetNode(parser.NodeMatchStatementExpression), context) if !matchExprScope.GetIsValid() { return newScope().Invalid().GetScope() } matchExprType := matchExprScope.ResolvedTypeRef(sb.sg.tdg) // Scope each of the case statements under the match, ensuring that their type is a subtype // of the match expression's type. var returnedType = sb.sg.tdg.VoidTypeReference() var settlesScope = true var hasDefault = false var labelSet = newLabelSet() var isValid = true // Lookup the named value, if any. matchNamedValue, hasNamedValue := node.TryGetNode(parser.NodeStatementNamedValue) sit := node.StartQuery(). Out(parser.NodeMatchStatementCase). BuildNodeIterator() for sit.Next() { var matchBranchType = sb.sg.tdg.AnyTypeReference() // Check the case's type reference (if any) against the type expected. caseTypeRefNode, hasCaseTypeRef := sit.Node().TryGetNode(parser.NodeMatchStatementCaseTypeReference) if hasCaseTypeRef { matchTypeRef, rerr := sb.sg.ResolveSRGTypeRef(sb.sg.srg.GetTypeRef(caseTypeRefNode)) if rerr != nil { panic(rerr) } // Ensure that the type is not nullable, as then a null could match anything. if matchTypeRef.IsNullable() { sb.decorateWithError(node, "Match cases cannot be nullable. Found: %v", matchTypeRef) isValid = false } else if serr := matchTypeRef.CheckCastableFrom(matchExprType); serr != nil { // Ensure that the type is a subtype of the expression type. sb.decorateWithError(node, "Match cases must be castable from type '%v': %v", matchExprType, serr) isValid = false } else { matchBranchType = matchTypeRef } } else { hasDefault = true } // Build the local context for scoping. If this match has an 'as', then its type is overridden // to the match type for each branch. localContext := context if hasNamedValue { localContext = context.withTypeOverride(matchNamedValue, matchBranchType) } // Scope the statement block under the case. statementBlockNode := sit.Node().GetNode(parser.NodeMatchStatementCaseStatement) statementBlockScope := sb.getScope(statementBlockNode, localContext.withBreakable(node)) if !statementBlockScope.GetIsValid() { isValid = false } labelSet.AppendLabelsOf(statementBlockScope) returnedType = returnedType.Intersect(statementBlockScope.ReturnedTypeRef(sb.sg.tdg)) settlesScope = settlesScope && statementBlockScope.GetIsSettlingScope() } // If there isn't a default case, then the match cannot be known to return in all cases. if !hasDefault { returnedType = sb.sg.tdg.VoidTypeReference() settlesScope = false } return newScope().IsValid(isValid).Returning(returnedType, settlesScope).WithLabelSet(labelSet).GetScope() }
// scopeSmlDecoratorAttribute scopes a decorator SML expression attribute under a declaration. func (sb *scopeBuilder) scopeSmlDecorator(node compilergraph.GraphNode, declaredType typegraph.TypeReference, context scopeContext) (typegraph.TypeReference, bool) { // Resolve the scope of the decorator. decoratorScope := sb.getScope(node.GetNode(parser.NodeSmlDecoratorPath), context) if !decoratorScope.GetIsValid() { return declaredType, false } namedScope, _ := sb.getNamedScopeForScope(decoratorScope) decoratorName := namedScope.Name() // Register that we make use of the decorator function. context.staticDependencyCollector.registerNamedDependency(namedScope) // Ensure the decorator refers to a function. decoratorType := decoratorScope.ResolvedTypeRef(sb.sg.tdg) if !decoratorType.IsDirectReferenceTo(sb.sg.tdg.FunctionType()) { sb.decorateWithError(node, "SML declaration decorator '%v' must refer to a function. Found: %v", decoratorName, decoratorType) return declaredType, false } // Ensure that the decorator doesn't return void. returnType := decoratorType.Generics()[0] if returnType.IsVoid() { sb.decorateWithError(node, "SML declaration decorator '%v' cannot return void", decoratorName) return declaredType, false } // Scope the attribute value (if any). var attributeValueType = sb.sg.tdg.BoolTypeReference() valueNode, hasValueNode := node.TryGetNode(parser.NodeSmlDecoratorValue) if hasValueNode { attributeValueScope := sb.getScope(valueNode, context) if !attributeValueScope.GetIsValid() { return returnType, false } attributeValueType = attributeValueScope.ResolvedTypeRef(sb.sg.tdg) } // Ensure the decorator takes the decorated type and a value as parameters. if decoratorType.ParameterCount() != 2 { sb.decorateWithError(node, "SML declaration decorator '%v' must refer to a function with two parameters. Found: %v", decoratorName, decoratorType) return returnType, false } // Ensure the first parameter is the declared type. allowedDecoratedType := decoratorType.Parameters()[0] allowedValueType := decoratorType.Parameters()[1] if serr := declaredType.CheckSubTypeOf(allowedDecoratedType); serr != nil { sb.decorateWithError(node, "SML declaration decorator '%v' expects to decorate an instance of type %v: %v", decoratorName, allowedDecoratedType, serr) return declaredType, false } // Ensure that the second parameter is the value type. if serr := attributeValueType.CheckSubTypeOf(allowedValueType); serr != nil { sb.decorateWithError(node, "Cannot assign value of type %v for decorator '%v': %v", attributeValueType, decoratorName, serr) return returnType, false } // The returned type is that of the decorator. return returnType, true }
// scopeSwitchStatement scopes a switch statement in the SRG. func (sb *scopeBuilder) scopeSwitchStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Check for an expression. If a switch has an expression, then all cases must match against it. var isValid = true var switchValueType = sb.sg.tdg.BoolTypeReference() exprNode, hasExpression := node.TryGetNode(parser.NodeSwitchStatementExpression) if hasExpression { exprScope := sb.getScope(exprNode, context) if exprScope.GetIsValid() { switchValueType = exprScope.ResolvedTypeRef(sb.sg.tdg) } else { isValid = false } } // Ensure that the switch type has a defined accessible comparison operator. if isValid { module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) _, rerr := switchValueType.ResolveAccessibleMember("equals", module, typegraph.MemberResolutionOperator) if rerr != nil { sb.decorateWithError(node, "Cannot switch over instance of type '%v', as it does not define or export an 'equals' operator", switchValueType) isValid = false } } // Scope each of the case statements under the switch. var returnedType = sb.sg.tdg.VoidTypeReference() var settlesScope = true var hasDefault = false var labelSet = newLabelSet() sit := node.StartQuery(). Out(parser.NodeSwitchStatementCase). BuildNodeIterator() for sit.Next() { // Scope the statement block under the case. statementBlockNode := sit.Node().GetNode(parser.NodeSwitchStatementCaseStatement) statementBlockScope := sb.getScope(statementBlockNode, context.withBreakable(node)) if !statementBlockScope.GetIsValid() { isValid = false } labelSet.AppendLabelsOf(statementBlockScope) returnedType = returnedType.Intersect(statementBlockScope.ReturnedTypeRef(sb.sg.tdg)) settlesScope = settlesScope && statementBlockScope.GetIsSettlingScope() // Check the case's expression (if any) against the type expected. caseExprNode, hasCaseExpression := sit.Node().TryGetNode(parser.NodeSwitchStatementCaseExpression) if hasCaseExpression { caseExprScope := sb.getScope(caseExprNode, context) if caseExprScope.GetIsValid() { caseExprType := caseExprScope.ResolvedTypeRef(sb.sg.tdg) if serr := caseExprType.CheckSubTypeOf(switchValueType); serr != nil { sb.decorateWithError(node, "Switch cases must have values matching type '%v': %v", switchValueType, serr) isValid = false } } else { isValid = false } } else { hasDefault = true } } // If there isn't a default case, then the switch cannot be known to return in all cases. if !hasDefault { returnedType = sb.sg.tdg.VoidTypeReference() settlesScope = false } return newScope().IsValid(isValid).Returning(returnedType, settlesScope).WithLabelSet(labelSet).GetScope() }
// scopeArrowStatement scopes an arrow statement in the SRG. func (sb *scopeBuilder) scopeArrowStatement(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Scope the source node. sourceNode := node.GetNode(parser.NodeArrowStatementSource) sourceScope := sb.getScope(sourceNode, context) if !sourceScope.GetIsValid() { return newScope().Invalid().GetScope() } // Ensure the source node is a Awaitable<T>. sourceType := sourceScope.ResolvedTypeRef(sb.sg.tdg) generics, err := sourceType.CheckConcreteSubtypeOf(sb.sg.tdg.AwaitableType()) if err != nil { sb.decorateWithError(sourceNode, "Right hand side of an arrow statement must be of type Awaitable: %v", err) return newScope().Invalid().GetScope() } receivedType := generics[0] // Scope the destination. destinationScope := sb.getScope(node.GetNode(parser.NodeArrowStatementDestination), context) if !destinationScope.GetIsValid() || !sourceScope.GetIsValid() { return newScope().Invalid().GetScope() } // Ensure the destination is a named node, is assignable, and has the proper type. if !destinationScope.GetIsAnonymousReference() { destinationName, isNamed := sb.getNamedScopeForScope(destinationScope) if !isNamed { sb.decorateWithError(node, "Destination of arrow statement must be named") return newScope().Invalid().GetScope() } if !destinationName.IsAssignable() { sb.decorateWithError(node, "Destination of arrow statement must be assignable. %v %v is not assignable", destinationName.Title(), destinationName.Name()) return newScope().Invalid().GetScope() } // The destination must match the received type. destinationType, isValid := destinationName.AssignableType(context) if !isValid { return newScope().Invalid().GetScope() } if serr := receivedType.CheckSubTypeOf(destinationType); serr != nil { sb.decorateWithError(node, "Destination of arrow statement must accept type %v: %v", receivedType, serr) return newScope().Invalid().GetScope() } } // Scope the rejection (if any). rejectionNode, hasRejection := node.TryGetNode(parser.NodeArrowStatementRejection) if hasRejection { rejectionScope := sb.getScope(rejectionNode, context) if !rejectionScope.GetIsAnonymousReference() { rejectionName, isNamed := sb.getNamedScopeForScope(rejectionScope) if !isNamed { sb.decorateWithError(node, "Rejection of arrow statement must be named") return newScope().Invalid().GetScope() } if !rejectionName.IsAssignable() { sb.decorateWithError(node, "Rejection of arrow statement must be assignable. %v %v is not assignable", rejectionName.Title(), rejectionName.Name()) return newScope().Invalid().GetScope() } // The rejection must match the error type. rejectionType, isValid := rejectionName.AssignableType(context) if !isValid { return newScope().Invalid().GetScope() } if serr := sb.sg.tdg.ErrorTypeReference().CheckSubTypeOf(rejectionType); serr != nil { sb.decorateWithError(node, "Rejection of arrow statement must accept type Error: %v", serr) return newScope().Invalid().GetScope() } } } return newScope().Valid().GetScope() }
// buildLoopStatement builds the CodeDOM for a loop statement. func (db *domBuilder) buildLoopStatement(node compilergraph.GraphNode) (codedom.Statement, codedom.Statement) { startStatement := codedom.EmptyStatement(node) startStatement.MarkReferenceable() finalStatement := codedom.EmptyStatement(node) // Save initial continue and break statements for the loop. db.continueStatementMap[node.NodeId] = startStatement db.breakStatementMap[node.NodeId] = finalStatement // A loop statement is buildd as a start statement which conditionally jumps to either the loop body // (on true) or, on false, jumps to a final state after the loop. if loopExpr, hasLoopExpr := db.tryGetExpression(node, parser.NodeLoopStatementExpression); hasLoopExpr { // Check for a named value under the loop. If found, this is a loop over a stream or streamable. namedValue, hasNamedValue := node.TryGetNode(parser.NodeStatementNamedValue) if hasNamedValue { namedValueName := namedValue.Get(parser.NodeNamedValueName) resultVarName := db.generateScopeVarName(node) namedValueScope, _ := db.scopegraph.GetScope(namedValue) // Create the stream variable. streamVarName := db.generateScopeVarName(node) var streamVariable = codedom.VarDefinitionWithInit(streamVarName, loopExpr, node) // If the expression is Streamable, first call .Stream() on the expression to get the stream // for the variable. if namedValueScope.HasLabel(proto.ScopeLabel_STREAMABLE_LOOP) { // Call .Stream() on the expression. streamableMember, _ := db.scopegraph.TypeGraph().StreamableType().GetMember("Stream") streamExpr := codedom.MemberCall( codedom.MemberReference(loopExpr, streamableMember, namedValue), streamableMember, []codedom.Expression{}, namedValue) streamVariable = codedom.VarDefinitionWithInit(streamVarName, streamExpr, node) } // Create variables to hold the named value (as requested in the SRG) and the loop result. namedVariable := codedom.VarDefinition(namedValueName, node) resultVariable := codedom.VarDefinition(resultVarName, node) // Create an expression statement to set the result variable to a call to Next(). streamMember, _ := db.scopegraph.TypeGraph().StreamType().GetMember("Next") nextCallExpr := codedom.MemberCall( codedom.MemberReference(codedom.LocalReference(streamVarName, node), streamMember, node), streamMember, []codedom.Expression{}, namedValue) resultExpressionStatement := codedom.ExpressionStatement(codedom.LocalAssignment(resultVarName, nextCallExpr, namedValue), namedValue) resultExpressionStatement.MarkReferenceable() // Set the continue statement to call Next() again. db.continueStatementMap[node.NodeId] = resultExpressionStatement // Create an expression statement to set the named variable to the first part of the tuple. namedExpressionStatement := codedom.ExpressionStatement( codedom.LocalAssignment(namedValueName, codedom.NativeAccess( codedom.LocalReference(resultVarName, namedValue), "First", namedValue), namedValue), namedValue) // Jump to the body state if the second part of the tuple in the result variable is true. bodyStart, bodyEnd := db.getStatements(node, parser.NodeLoopStatementBlock) checkJump := codedom.ConditionalJump( codedom.NativeAccess(codedom.LocalReference(resultVarName, node), "Second", node), bodyStart, finalStatement, node) // Steps: // 1) Empty statement // 2) Create the stream's variable (with no value) // 3) Create the named value variable (with no value) // 4) Create the result variable (with no value) // 5) (loop starts here) Pull the Next() result out of the stream // 6) Pull the true/false bool out of the result // 7) Pull the named value out of the result // 8) Jump based on the true/false boolean value codedom.AssignNextStatement(startStatement, streamVariable) codedom.AssignNextStatement(streamVariable, namedVariable) codedom.AssignNextStatement(namedVariable, resultVariable) codedom.AssignNextStatement(resultVariable, resultExpressionStatement) codedom.AssignNextStatement(resultExpressionStatement, namedExpressionStatement) codedom.AssignNextStatement(namedExpressionStatement, checkJump) // Jump to the result checking expression once the loop body completes. directJump := codedom.UnconditionalJump(resultExpressionStatement, node) codedom.AssignNextStatement(bodyEnd, directJump) return startStatement, finalStatement } else { bodyStart, bodyEnd := db.getStatements(node, parser.NodeLoopStatementBlock) // Loop over a direct boolean expression which is evaluated on each iteration. initialJump := codedom.ConditionalJump(loopExpr, bodyStart, finalStatement, node) directJump := codedom.UnconditionalJump(initialJump, node) codedom.AssignNextStatement(bodyEnd, directJump) codedom.AssignNextStatement(startStatement, initialJump) return startStatement, finalStatement } } else { bodyStart, bodyEnd := db.getStatements(node, parser.NodeLoopStatementBlock) // A loop without an expression just loops infinitely over the body. directJump := codedom.UnconditionalJump(bodyStart, node) codedom.AssignNextStatement(bodyEnd, directJump) codedom.AssignNextStatement(startStatement, bodyStart) return startStatement, directJump } }
// scopeSmlExpression scopes an SML expression in the SRG. func (sb *scopeBuilder) scopeSmlExpression(node compilergraph.GraphNode, context scopeContext) proto.ScopeInfo { // Scope the type or function and ensure it refers to a "declarable" type or function. // A type is "declarable" if it has a constructor named Declare with a "declarable" function type signature. // // A function is "declarable" if it has the following properties: // - Must return a non-void type // - Parameter #1 represents the properties (attributes) and must be: // 1) A struct // 2) A class with at least one field // 3) A mapping // // - Parameter #2 (optional) represents the children (contents). typeOrFuncScope := sb.getScope(node.GetNode(parser.NodeSmlExpressionTypeOrFunction), context) if !typeOrFuncScope.GetIsValid() { return newScope().Invalid().GetScope() } var functionType = sb.sg.tdg.AnyTypeReference() var declarationLabel = proto.ScopeLabel_SML_FUNCTION var childrenLabel = proto.ScopeLabel_SML_NO_CHILDREN var propsLabel = proto.ScopeLabel_SML_PROPS_MAPPING switch typeOrFuncScope.GetKind() { case proto.ScopeKind_VALUE: // Function. functionType = typeOrFuncScope.ResolvedTypeRef(sb.sg.tdg) declarationLabel = proto.ScopeLabel_SML_FUNCTION calledFunctionScope, _ := sb.getNamedScopeForScope(typeOrFuncScope) context.staticDependencyCollector.registerNamedDependency(calledFunctionScope) case proto.ScopeKind_STATIC: // Type. Ensure it has a Declare constructor. module := compilercommon.InputSource(node.Get(parser.NodePredicateSource)) staticType := typeOrFuncScope.StaticTypeRef(sb.sg.tdg) declareConstructor, rerr := staticType.ResolveAccessibleMember("Declare", module, typegraph.MemberResolutionStatic) if rerr != nil { sb.decorateWithError(node, "A type used in a SML declaration tag must have a 'Declare' constructor: %v", rerr) return newScope().Invalid().GetScope() } context.staticDependencyCollector.registerDependency(declareConstructor) functionType = declareConstructor.MemberType() declarationLabel = proto.ScopeLabel_SML_CONSTRUCTOR case proto.ScopeKind_GENERIC: sb.decorateWithError(node, "A generic type cannot be used in a SML declaration tag") return newScope().Invalid().GetScope() default: panic("Unknown scope kind") } // Ensure the function type is a function. if !functionType.IsDirectReferenceTo(sb.sg.tdg.FunctionType()) { sb.decorateWithError(node, "Declared reference in an SML declaration tag must be a function. Found: %v", functionType) return newScope().Invalid().GetScope() } // Ensure the function doesn't return void. declaredType := functionType.Generics()[0] if declaredType.IsVoid() { sb.decorateWithError(node, "Declarable function used in an SML declaration tag cannot return void") return newScope().Invalid().GetScope() } parameters := functionType.Parameters() // Check for attributes. var isValid = true var resolvedType = declaredType if _, ok := node.TryGetNode(parser.NodeSmlExpressionAttribute); ok || len(parameters) >= 1 { if len(parameters) < 1 { sb.decorateWithError(node, "Declarable function or constructor used in an SML declaration tag with attributes must have a 'props' parameter as parameter #1. Found: %v", functionType) return newScope().Invalid().Resolving(declaredType).GetScope() } propsType := parameters[0] // Ensure that the first parameter is either structural, a class with ForProps or a Mapping. if propsType.NullValueAllowed() { sb.decorateWithError(node, "Props parameter (parameter #1) of a declarable function or constructor used in an SML declaration tag cannot allow null values. Found: %v", propsType) return newScope().Invalid().Resolving(declaredType).GetScope() } switch { case propsType.IsDirectReferenceTo(sb.sg.tdg.MappingType()): // Mappings are always allowed. propsLabel = proto.ScopeLabel_SML_PROPS_MAPPING case propsType.IsRefToStruct(): // Structs are always allowed. propsLabel = proto.ScopeLabel_SML_PROPS_STRUCT case propsType.IsRefToClass(): // Classes are allowed if they have at least one field. if len(propsType.ReferredType().Fields()) == 0 { sb.decorateWithError(node, "Props parameter (parameter #1) of a declarable function or constructor used in an SML declaration tag has type %v, which does not have any settable fields; use an empty `struct` instead if this is the intended behavior", propsType) return newScope().Invalid().Resolving(declaredType).GetScope() } propsLabel = proto.ScopeLabel_SML_PROPS_CLASS default: // Otherwise, the type is not valid. sb.decorateWithError(node, "Props parameter (parameter #1) of a declarable function or constructor used in an SML declaration tag must be a struct, a class with a ForProps constructor or a Mapping. Found: %v", propsType) return newScope().Invalid().Resolving(declaredType).GetScope() } // At this point we know we have a declarable function used in the tag. // Scope the attributes to match the props type. ait := node.StartQuery(). Out(parser.NodeSmlExpressionAttribute). BuildNodeIterator() attributesEncountered := map[string]bool{} for ait.Next() { attributeName, ok := sb.scopeSmlAttribute(ait.Node(), propsType, context) attributesEncountered[attributeName] = true isValid = isValid && ok } // If the props type is not a mapping, ensure that all required fields were set. if !propsType.IsDirectReferenceTo(sb.sg.tdg.MappingType()) { for _, requiredField := range propsType.ReferredType().RequiredFields() { if _, ok := attributesEncountered[requiredField.Name()]; !ok { sb.decorateWithError(node, "Required attribute '%v' is missing for SML declaration props type %v", requiredField.Name(), propsType) isValid = false } } } } // Scope decorators. dit := node.StartQuery(). Out(parser.NodeSmlExpressionDecorator). BuildNodeIterator() for dit.Next() { decoratorReturnType, ok := sb.scopeSmlDecorator(dit.Node(), resolvedType, context) resolvedType = decoratorReturnType isValid = isValid && ok } // Scope the children to match the childs type. if _, ok := node.TryGetNode(parser.NodeSmlExpressionChild); ok || len(parameters) >= 2 { if len(parameters) < 2 { sb.decorateWithError(node, "Declarable function or constructor used in an SML declaration tag with children must have a 'children' parameter. Found: %v", functionType) return newScope().Invalid().Resolving(resolvedType).GetScope() } childsType := parameters[1] // Scope and collect the types of all the children. var childrenTypes = make([]typegraph.TypeReference, 0) cit := node.StartQuery(). Out(parser.NodeSmlExpressionChild). BuildNodeIterator() for cit.Next() { childNode := cit.Node() childScope := sb.getScope(childNode, context) if !childScope.GetIsValid() { isValid = false } childrenTypes = append(childrenTypes, childScope.ResolvedTypeRef(sb.sg.tdg)) } // Check if the children parameter is a stream. If so, we match the stream type. Otherwise, // we check if the child matches the value expected. if childsType.IsDirectReferenceTo(sb.sg.tdg.StreamType()) { // If there is a single child that is also a matching stream, then we allow it. if len(childrenTypes) == 1 && childrenTypes[0] == childsType { childrenLabel = proto.ScopeLabel_SML_STREAM_CHILD } else { childrenLabel = proto.ScopeLabel_SML_CHILDREN streamValueType := childsType.Generics()[0] // Ensure the child types match. for index, childType := range childrenTypes { if serr := childType.CheckSubTypeOf(streamValueType); serr != nil { sb.decorateWithError(node, "Child #%v under SML declaration must be subtype of %v: %v", index+1, streamValueType, serr) isValid = false } } } } else if len(childrenTypes) > 1 { // Since not a slice, it can only be one or zero children. sb.decorateWithError(node, "SML declaration tag allows at most a single child. Found: %v", len(childrenTypes)) return newScope().Invalid().Resolving(resolvedType).GetScope() } else { childrenLabel = proto.ScopeLabel_SML_SINGLE_CHILD // If the child type is not nullable, then we need to make sure a value was specified. if !childsType.NullValueAllowed() && len(childrenTypes) < 1 { sb.decorateWithError(node, "SML declaration tag requires a single child. Found: %v", len(childrenTypes)) return newScope().Invalid().Resolving(resolvedType).GetScope() } // Ensure the child type matches. if len(childrenTypes) == 1 { if serr := childrenTypes[0].CheckSubTypeOf(childsType); serr != nil { sb.decorateWithError(node, "SML declaration tag requires a child of type %v: %v", childsType, serr) return newScope().Invalid().Resolving(resolvedType).GetScope() } } } } return newScope(). IsValid(isValid). Resolving(resolvedType). WithLabel(declarationLabel). WithLabel(propsLabel). WithLabel(childrenLabel). GetScope() }