Exemplo n.º 1
0
// 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
}
Exemplo n.º 2
0
// 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
	}
}
Exemplo n.º 3
0
// 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)
}
Exemplo n.º 4
0
// 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)
	}
}
Exemplo n.º 5
0
// 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()
	}
}
Exemplo n.º 6
0
// 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
}
Exemplo n.º 7
0
// 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
}
Exemplo n.º 8
0
// 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)
	}
}
Exemplo n.º 9
0
// 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)
	}
}
Exemplo n.º 10
0
// 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()
	}
}
Exemplo n.º 11
0
// 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)
	}
}
Exemplo n.º 12
0
// 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()
}
Exemplo n.º 13
0
// 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)
}
Exemplo n.º 14
0
// 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()
}
Exemplo n.º 15
0
// 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()
}
Exemplo n.º 16
0
// 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()
}
Exemplo n.º 17
0
// 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()
}
Exemplo n.º 18
0
// 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
}
Exemplo n.º 19
0
// 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()
}
Exemplo n.º 20
0
// 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, ".")
}
Exemplo n.º 21
0
// 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()
}
Exemplo n.º 22
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())
		}
	}
}
Exemplo n.º 23
0
// 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()
}
Exemplo n.º 24
0
// 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
}
Exemplo n.º 25
0
// 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()
}
Exemplo n.º 26
0
// 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()
}
Exemplo n.º 27
0
// 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
	}
}
Exemplo n.º 28
0
// 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()
}