github.com/nevalang/neva@v0.23.1-0.20240507185603-7696a9bb8dda/internal/compiler/analyzer/component_net.go (about)

     1  package analyzer
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/nevalang/neva/internal/compiler"
     8  	src "github.com/nevalang/neva/internal/compiler/sourcecode"
     9  	"github.com/nevalang/neva/internal/compiler/sourcecode/core"
    10  	ts "github.com/nevalang/neva/internal/compiler/sourcecode/typesystem"
    11  )
    12  
    13  var (
    14  	ErrUnusedOutports                = errors.New("All component's outports are unused")
    15  	ErrUnusedOutport                 = errors.New("Unused outport found")
    16  	ErrUnusedInports                 = errors.New("All component inports are unused")
    17  	ErrUnusedInport                  = errors.New("Unused inport found")
    18  	ErrLiteralSenderTypeEmpty        = errors.New("Literal network sender must contain message value")
    19  	ErrComplexLiteralSender          = errors.New("Literal network sender must have primitive type")
    20  	ErrIllegalPortlessConnection     = errors.New("Connection to a node, with more than one port, must always has a port name")
    21  	ErrGuardMixedWithExplicitErrConn = errors.New("If node has error guard '?' it's ':err' outport must not be explicitly used in the network")
    22  )
    23  
    24  // analyzeComponentNetwork must be called after analyzeNodes so we sure nodes are resolved.
    25  func (a Analyzer) analyzeComponentNetwork(
    26  	net []src.Connection,
    27  	compInterface src.Interface,
    28  	hasGuard bool,
    29  	nodes map[string]src.Node,
    30  	nodesIfaces map[string]foundInterface,
    31  	scope src.Scope,
    32  ) ([]src.Connection, *compiler.Error) {
    33  	// we create it here because there's recursion down there
    34  	nodesUsage := make(map[string]nodeNetUsage, len(nodes))
    35  
    36  	resolvedNet, err := a.analyzeConnections(net, compInterface, nodes, nodesIfaces, nodesUsage, scope)
    37  	if err != nil {
    38  		return nil, compiler.Error{Location: &scope.Location}.Wrap(err)
    39  	}
    40  
    41  	if err := a.checkNetPortsUsage(
    42  		compInterface,
    43  		nodesIfaces,
    44  		hasGuard,
    45  		scope,
    46  		nodesUsage,
    47  		nodes,
    48  	); err != nil {
    49  		return nil, compiler.Error{Location: &scope.Location}.Wrap(err)
    50  	}
    51  
    52  	return resolvedNet, nil
    53  }
    54  
    55  // analyzeConnections does two things:
    56  // 1. Analyzes every connection and terminates with non-nil error if any of them is invalid.
    57  // 2. Updates nodesUsage (we mutate it in-place instead of returning to avoid merging across recursive calls).
    58  func (a Analyzer) analyzeConnections(
    59  	net []src.Connection,
    60  	compInterface src.Interface,
    61  	nodes map[string]src.Node,
    62  	nodesIfaces map[string]foundInterface,
    63  	nodesUsage map[string]nodeNetUsage,
    64  	scope src.Scope,
    65  ) ([]src.Connection, *compiler.Error) {
    66  	resolvedNet := make([]src.Connection, 0, len(net))
    67  
    68  	for _, conn := range net {
    69  		resolvedConn, err := a.analyzeConnection(
    70  			conn,
    71  			compInterface,
    72  			nodes,
    73  			nodesIfaces,
    74  			scope,
    75  			nodesUsage,
    76  		)
    77  		if err != nil {
    78  			return nil, err
    79  		}
    80  
    81  		resolvedNet = append(resolvedNet, resolvedConn)
    82  	}
    83  
    84  	return resolvedNet, nil
    85  }
    86  
    87  func (a Analyzer) analyzeConnection(
    88  	conn src.Connection,
    89  	compInterface src.Interface,
    90  	nodes map[string]src.Node,
    91  	nodesIfaces map[string]foundInterface,
    92  	scope src.Scope,
    93  	nodesUsage map[string]nodeNetUsage,
    94  ) (src.Connection, *compiler.Error) {
    95  	// first handle array bypass connection, they are simple
    96  	if conn.ArrayBypass != nil {
    97  		arrBypassConn := conn.ArrayBypass
    98  
    99  		senderType, isArray, err := a.getSenderPortAddrType(
   100  			arrBypassConn.SenderOutport,
   101  			scope,
   102  			compInterface,
   103  			nodes,
   104  			nodesIfaces,
   105  		)
   106  		if err != nil {
   107  			return src.Connection{}, compiler.Error{
   108  				Location: &scope.Location,
   109  				Meta:     &conn.Normal.SenderSide.Meta,
   110  			}.Wrap(err)
   111  		}
   112  		if !isArray {
   113  			return src.Connection{}, &compiler.Error{
   114  				Err:      errors.New("Non-array outport in array-bypass connection"),
   115  				Location: &scope.Location,
   116  				Meta:     &arrBypassConn.SenderOutport.Meta,
   117  			}
   118  		}
   119  
   120  		receiverType, isArray, err := a.getReceiverType(
   121  			arrBypassConn.ReceiverInport,
   122  			compInterface,
   123  			nodes,
   124  			nodesIfaces,
   125  			scope,
   126  		)
   127  		if err != nil {
   128  			return src.Connection{}, compiler.Error{
   129  				Location: &scope.Location,
   130  				Meta:     &conn.Meta,
   131  			}.Wrap(err)
   132  		}
   133  		if !isArray {
   134  			return src.Connection{}, &compiler.Error{
   135  				Err:      errors.New("Non-array outport in array-bypass connection"),
   136  				Location: &scope.Location,
   137  				Meta:     &arrBypassConn.SenderOutport.Meta,
   138  			}
   139  		}
   140  
   141  		if err := a.resolver.IsSubtypeOf(
   142  			senderType,
   143  			receiverType,
   144  			scope,
   145  		); err != nil {
   146  			return src.Connection{}, &compiler.Error{
   147  				Err: fmt.Errorf(
   148  					"Incompatible types: %v -> %v: %w",
   149  					arrBypassConn.SenderOutport, arrBypassConn.ReceiverInport, err,
   150  				),
   151  				Location: &scope.Location,
   152  				Meta:     &conn.Meta,
   153  			}
   154  		}
   155  
   156  		nodesNetUsage(nodesUsage).AddOutport(
   157  			arrBypassConn.SenderOutport.Node,
   158  			arrBypassConn.SenderOutport.Port,
   159  		)
   160  		nodesNetUsage(nodesUsage).AddInport(
   161  			arrBypassConn.ReceiverInport.Node,
   162  			arrBypassConn.ReceiverInport.Port,
   163  		)
   164  
   165  		return conn, nil
   166  	}
   167  
   168  	// now handle normal connections
   169  	normConn := conn.Normal
   170  
   171  	resolvedSender, resolvedSenderType, isSenderArr, err := a.getSenderSideType(
   172  		normConn.SenderSide,
   173  		compInterface,
   174  		nodes,
   175  		nodesIfaces,
   176  		scope,
   177  	)
   178  	if err != nil {
   179  		return src.Connection{}, compiler.Error{
   180  			Location: &scope.Location,
   181  			Meta:     &normConn.SenderSide.Meta,
   182  		}.Wrap(err)
   183  	}
   184  
   185  	if normConn.SenderSide.PortAddr != nil {
   186  		// make sure only array outports has indexes
   187  		if !isSenderArr && normConn.SenderSide.PortAddr.Idx != nil {
   188  			return src.Connection{}, &compiler.Error{
   189  				Err:      errors.New("Index for non-array port"),
   190  				Meta:     &normConn.SenderSide.PortAddr.Meta,
   191  				Location: &scope.Location,
   192  			}
   193  		}
   194  
   195  		// make sure array outports always has indexes (it's not arr-bypass)
   196  		if isSenderArr && normConn.SenderSide.PortAddr.Idx == nil {
   197  			return src.Connection{}, &compiler.Error{
   198  				Err:      errors.New("Index needed for array outport"),
   199  				Meta:     &normConn.SenderSide.PortAddr.Meta,
   200  				Location: &scope.Location,
   201  			}
   202  		}
   203  
   204  		// mark node's outport as used since sender isn't const ref
   205  		nodesNetUsage(nodesUsage).AddOutport(
   206  			normConn.SenderSide.PortAddr.Node,
   207  			normConn.SenderSide.PortAddr.Port,
   208  		)
   209  	}
   210  
   211  	if len(normConn.SenderSide.Selectors) > 0 {
   212  		lastFieldType, err := a.getStructFieldTypeByPath(
   213  			resolvedSenderType,
   214  			normConn.SenderSide.Selectors,
   215  			scope,
   216  		)
   217  		if err != nil {
   218  			return src.Connection{}, &compiler.Error{
   219  				Err:      err,
   220  				Location: &scope.Location,
   221  				Meta:     &conn.Meta,
   222  			}
   223  		}
   224  		resolvedSenderType = lastFieldType
   225  	}
   226  
   227  	if len(normConn.ReceiverSide.DeferredConnections) == 0 && len(normConn.ReceiverSide.Receivers) == 0 {
   228  		return src.Connection{}, &compiler.Error{
   229  			Err: errors.New(
   230  				"Connection's receiver side cannot be empty, it must either have deferred connection or receivers",
   231  			),
   232  			Location: &scope.Location,
   233  			Meta:     &conn.Meta,
   234  		}
   235  	}
   236  
   237  	resolvedDefConns := make([]src.Connection, 0, len(normConn.ReceiverSide.DeferredConnections))
   238  	if len(normConn.ReceiverSide.DeferredConnections) != 0 {
   239  		// note that we call analyzeConnections instead of analyzeComponentNetwork
   240  		// because we only need to analyze connections and update nodesUsage
   241  		// analyzeComponentNetwork OTOH will also validate nodesUsage by itself
   242  		var err *compiler.Error
   243  		resolvedDefConns, err = a.analyzeConnections( // indirect recursion
   244  			normConn.ReceiverSide.DeferredConnections,
   245  			compInterface,
   246  			nodes,
   247  			nodesIfaces,
   248  			nodesUsage,
   249  			scope,
   250  		)
   251  		if err != nil {
   252  			return src.Connection{}, err
   253  		}
   254  		// receiver side can contain both deferred connections and receivers so we don't return yet
   255  	}
   256  
   257  	// TODO handle chain connection
   258  
   259  	for _, receiver := range normConn.ReceiverSide.Receivers {
   260  		inportTypeExpr, isReceiverArr, err := a.getReceiverType(
   261  			receiver.PortAddr,
   262  			compInterface,
   263  			nodes,
   264  			nodesIfaces,
   265  			scope,
   266  		)
   267  		if err != nil {
   268  			return src.Connection{}, compiler.Error{
   269  				Location: &scope.Location,
   270  				Meta:     &receiver.Meta,
   271  			}.Wrap(err)
   272  		}
   273  
   274  		// make sure only array outports has indexes
   275  		if !isReceiverArr && receiver.PortAddr.Idx != nil {
   276  			return src.Connection{}, &compiler.Error{
   277  				Err:      errors.New("Index for non-array port"),
   278  				Meta:     &receiver.PortAddr.Meta,
   279  				Location: &scope.Location,
   280  			}
   281  		}
   282  
   283  		// make sure array inports always has indexes (it's not arr-bypass)
   284  		if isReceiverArr && receiver.PortAddr.Idx == nil {
   285  			return src.Connection{}, &compiler.Error{
   286  				Err:      errors.New("Index needed for array inport"),
   287  				Meta:     &receiver.PortAddr.Meta,
   288  				Location: &scope.Location,
   289  			}
   290  		}
   291  
   292  		if err := a.resolver.IsSubtypeOf(resolvedSenderType, inportTypeExpr, scope); err != nil {
   293  			return src.Connection{}, &compiler.Error{
   294  				Err: fmt.Errorf(
   295  					"Incompatible types: %v -> %v: %w",
   296  					normConn.SenderSide, receiver, err,
   297  				),
   298  				Location: &scope.Location,
   299  				Meta:     &conn.Meta,
   300  			}
   301  		}
   302  
   303  		nodesNetUsage(nodesUsage).AddInport(
   304  			receiver.PortAddr.Node,
   305  			receiver.PortAddr.Port,
   306  		)
   307  	}
   308  
   309  	return src.Connection{
   310  		Normal: &src.NormalConnection{
   311  			SenderSide: resolvedSender,
   312  			ReceiverSide: src.ConnectionReceiverSide{
   313  				DeferredConnections: resolvedDefConns,
   314  				Receivers:           normConn.ReceiverSide.Receivers,
   315  			},
   316  		},
   317  		Meta: conn.Meta,
   318  	}, nil
   319  }
   320  
   321  // nodeNetUsage shows which ports was used by the network
   322  type nodeNetUsage struct {
   323  	In, Out map[string]struct{}
   324  }
   325  
   326  // checkNetPortsUsage ensures that:
   327  // Every component's inport and outport is used;
   328  // Every sub-node's inport is used;
   329  // For every  sub-node's there's at least one used outport.
   330  func (Analyzer) checkNetPortsUsage(
   331  	compInterface src.Interface,
   332  	nodesIfaces map[string]foundInterface,
   333  	hasGuard bool,
   334  	scope src.Scope,
   335  	nodesUsage map[string]nodeNetUsage,
   336  	nodes map[string]src.Node,
   337  ) *compiler.Error {
   338  	inportsUsage, ok := nodesUsage["in"]
   339  	if !ok {
   340  		return &compiler.Error{
   341  			Err:      ErrUnusedInports,
   342  			Location: &scope.Location,
   343  			Meta:     &compInterface.Meta,
   344  		}
   345  	}
   346  
   347  	for inportName := range compInterface.IO.In {
   348  		if _, ok := inportsUsage.Out[inportName]; !ok { // note that self inports are outports for the network
   349  			return &compiler.Error{
   350  				Err:      fmt.Errorf("%w '%v'", ErrUnusedInport, inportName),
   351  				Location: &scope.Location,
   352  			}
   353  		}
   354  	}
   355  
   356  	outportsUsage, ok := nodesUsage["out"]
   357  	if !ok {
   358  		return &compiler.Error{
   359  			Err:      ErrUnusedOutports,
   360  			Location: &scope.Location,
   361  			Meta:     &compInterface.Meta,
   362  		}
   363  	}
   364  
   365  	for outportName := range compInterface.IO.Out {
   366  		// note that self outports are inports for the network
   367  		if _, ok := outportsUsage.In[outportName]; ok {
   368  			continue
   369  		}
   370  
   371  		if outportName == "err" && hasGuard {
   372  			continue
   373  		}
   374  
   375  		return &compiler.Error{
   376  			Err:      fmt.Errorf("%w '%v'", ErrUnusedOutport, outportName),
   377  			Location: &scope.Location,
   378  		}
   379  	}
   380  
   381  	for nodeName, nodeIface := range nodesIfaces {
   382  		nodeUsage, ok := nodesUsage[nodeName]
   383  		if !ok {
   384  			return &compiler.Error{
   385  				Err:      fmt.Errorf("%w: %v", ErrUnusedNode, nodeName),
   386  				Location: &scope.Location,
   387  			}
   388  		}
   389  
   390  		for inportName := range nodeIface.iface.IO.In {
   391  			if _, ok := nodeUsage.In[inportName]; !ok {
   392  				// maybe it's portless connection
   393  				if _, ok := nodeUsage.In[""]; ok && len(nodeIface.iface.IO.In) == 1 {
   394  					continue
   395  				}
   396  
   397  				meta := nodeIface.iface.IO.In[inportName].Meta
   398  
   399  				return &compiler.Error{
   400  					Err: fmt.Errorf(
   401  						"%w: %v:%v",
   402  						ErrUnusedNodeInport,
   403  						nodeName,
   404  						inportName,
   405  					),
   406  					Location: &scope.Location,
   407  					Meta:     &meta,
   408  				}
   409  			}
   410  		}
   411  
   412  		if len(nodeIface.iface.IO.Out) == 0 { // e.g. std/builtin.Del
   413  			continue
   414  		}
   415  
   416  		atLeastOneOutportIsUsed := false
   417  		for outportName := range nodeIface.iface.IO.Out {
   418  			if _, ok := nodeUsage.Out[outportName]; !ok {
   419  				continue
   420  			}
   421  
   422  			atLeastOneOutportIsUsed = true
   423  
   424  			if outportName != "err" {
   425  				continue
   426  			}
   427  
   428  			meta := nodes[nodeName].Meta
   429  
   430  			if nodes[nodeName].ErrGuard {
   431  				return &compiler.Error{
   432  					Err: fmt.Errorf(
   433  						"%w: %v",
   434  						ErrGuardMixedWithExplicitErrConn,
   435  						nodeName,
   436  					),
   437  					Location: &scope.Location,
   438  					Meta:     &meta,
   439  				}
   440  			}
   441  
   442  		}
   443  
   444  		if !atLeastOneOutportIsUsed {
   445  			// maybe it's portless connection
   446  			if _, ok := nodeUsage.Out[""]; ok && len(nodeIface.iface.IO.Out) == 1 {
   447  				continue
   448  			}
   449  
   450  			return &compiler.Error{
   451  				Err:      fmt.Errorf("%w: %v", ErrUnusedNodeOutports, nodeName),
   452  				Location: &scope.Location,
   453  				Meta:     &nodeIface.iface.Meta,
   454  			}
   455  		}
   456  	}
   457  
   458  	return nil
   459  }
   460  
   461  func (a Analyzer) getReceiverType(
   462  	receiverSide src.PortAddr,
   463  	iface src.Interface,
   464  	nodes map[string]src.Node,
   465  	nodesIfaces map[string]foundInterface,
   466  	scope src.Scope,
   467  ) (ts.Expr, bool, *compiler.Error) {
   468  	if receiverSide.Node == "in" {
   469  		return ts.Expr{}, false, &compiler.Error{
   470  			Err:      ErrWriteSelfIn,
   471  			Location: &scope.Location,
   472  			Meta:     &receiverSide.Meta,
   473  		}
   474  	}
   475  
   476  	if receiverSide.Node == "out" {
   477  		outports := iface.IO.Out
   478  
   479  		outport, ok := outports[receiverSide.Port]
   480  		if !ok {
   481  			return ts.Expr{}, false, &compiler.Error{
   482  				Err:      fmt.Errorf("%w: %v", ErrOutportNotFound, receiverSide.Port),
   483  				Location: &scope.Location,
   484  				Meta:     &receiverSide.Meta,
   485  			}
   486  		}
   487  
   488  		resolvedOutportType, err := a.resolver.ResolveExprWithFrame(
   489  			outport.TypeExpr,
   490  			iface.TypeParams.ToFrame(),
   491  			scope,
   492  		)
   493  		if err != nil {
   494  			return ts.Expr{}, false, &compiler.Error{
   495  				Err:      err,
   496  				Location: &scope.Location,
   497  				Meta:     &receiverSide.Meta,
   498  			}
   499  		}
   500  
   501  		return resolvedOutportType, outport.IsArray, nil
   502  	}
   503  
   504  	nodeInportType, isArray, err := a.getNodeInportType(receiverSide, nodes, nodesIfaces, scope)
   505  	if err != nil {
   506  		return ts.Expr{}, false, compiler.Error{
   507  			Location: &scope.Location,
   508  			Meta:     &receiverSide.Meta,
   509  		}.Wrap(err)
   510  	}
   511  
   512  	return nodeInportType, isArray, nil
   513  }
   514  
   515  func (a Analyzer) getNodeInportType(
   516  	portAddr src.PortAddr,
   517  	nodes map[string]src.Node,
   518  	nodesIfaces map[string]foundInterface,
   519  	scope src.Scope,
   520  ) (ts.Expr, bool, *compiler.Error) {
   521  	node, ok := nodes[portAddr.Node]
   522  	if !ok {
   523  		return ts.Expr{}, false, &compiler.Error{
   524  			Err:      fmt.Errorf("Node not found '%v'", portAddr.Node),
   525  			Location: &scope.Location,
   526  			Meta:     &portAddr.Meta,
   527  		}
   528  	}
   529  
   530  	nodeIface, ok := nodesIfaces[portAddr.Node]
   531  	if !ok {
   532  		return ts.Expr{}, false, &compiler.Error{
   533  			Err:      fmt.Errorf("%w '%v'", ErrNodeNotFound, portAddr.Node),
   534  			Location: &scope.Location,
   535  			Meta:     &portAddr.Meta,
   536  		}
   537  	}
   538  
   539  	// TODO optimize:
   540  	// we can resolve every node's interface just once
   541  	// before processing the network
   542  	resolvedInportType, isArray, aerr := a.getResolvedPortType(
   543  		nodeIface.iface.IO.In,
   544  		nodeIface.iface.TypeParams.Params,
   545  		portAddr,
   546  		node,
   547  		scope.WithLocation(nodeIface.location),
   548  	)
   549  	if aerr != nil {
   550  		return ts.Expr{}, false, compiler.Error{
   551  			Location: &scope.Location,
   552  			Meta:     &portAddr.Meta,
   553  		}.Wrap(aerr)
   554  	}
   555  
   556  	return resolvedInportType, isArray, nil
   557  }
   558  
   559  // getResolvedPortType returns port's type and isArray bool
   560  func (a Analyzer) getResolvedPortType(
   561  	ports map[string]src.Port,
   562  	nodeIfaceParams []ts.Param,
   563  	portAddr src.PortAddr,
   564  	node src.Node,
   565  	scope src.Scope,
   566  ) (ts.Expr, bool, *compiler.Error) {
   567  	if portAddr.Port == "" {
   568  		if len(ports) > 1 {
   569  			return ts.Expr{}, false, &compiler.Error{
   570  				Err:      ErrIllegalPortlessConnection,
   571  				Location: &scope.Location,
   572  				Meta:     &portAddr.Meta,
   573  			}
   574  		}
   575  
   576  		for name := range ports {
   577  			portAddr.Port = name
   578  			break
   579  		}
   580  	}
   581  
   582  	port, ok := ports[portAddr.Port]
   583  	if !ok {
   584  		return ts.Expr{}, false, &compiler.Error{
   585  			Err: fmt.Errorf(
   586  				"Port not found `%v`",
   587  				portAddr,
   588  			),
   589  			Location: &scope.Location,
   590  			Meta:     &portAddr.Meta,
   591  		}
   592  	}
   593  
   594  	// we don't resolve node's args assuming they resolved already
   595  
   596  	// create frame `nodeParam:resolvedArg` to get resolved port type
   597  	frame := make(map[string]ts.Def, len(nodeIfaceParams))
   598  	for i, param := range nodeIfaceParams {
   599  		arg := node.TypeArgs[i]
   600  		frame[param.Name] = ts.Def{
   601  			BodyExpr: &arg,
   602  			Meta:     arg.Meta,
   603  		}
   604  	}
   605  
   606  	resolvedPortType, err := a.resolver.ResolveExprWithFrame(
   607  		port.TypeExpr,
   608  		frame,
   609  		scope,
   610  	)
   611  	if err != nil {
   612  		return ts.Expr{}, false, &compiler.Error{
   613  			Err:      err,
   614  			Location: &scope.Location,
   615  			Meta:     &port.Meta,
   616  		}
   617  	}
   618  
   619  	return resolvedPortType, port.IsArray, nil
   620  }
   621  
   622  func (a Analyzer) getSenderSideType(
   623  	senderSide src.ConnectionSenderSide,
   624  	iface src.Interface,
   625  	nodes map[string]src.Node,
   626  	nodesIfaces map[string]foundInterface,
   627  	scope src.Scope,
   628  ) (src.ConnectionSenderSide, ts.Expr, bool, *compiler.Error) {
   629  	if senderSide.PortAddr == nil && senderSide.Const == nil {
   630  		return src.ConnectionSenderSide{}, ts.Expr{}, false, &compiler.Error{
   631  			Err:      ErrSenderIsEmpty,
   632  			Location: &scope.Location,
   633  			Meta:     &senderSide.Meta,
   634  		}
   635  	}
   636  
   637  	if senderSide.Const != nil {
   638  		resolvedConst, resolvedExpr, err := a.getResolvedSenderConstType(*senderSide.Const, scope)
   639  		if err != nil {
   640  			return src.ConnectionSenderSide{}, ts.Expr{}, false, err
   641  		}
   642  
   643  		return src.ConnectionSenderSide{
   644  			Const:     &resolvedConst,
   645  			Selectors: senderSide.Selectors,
   646  			Meta:      senderSide.Meta,
   647  		}, resolvedExpr, false, nil
   648  	}
   649  
   650  	resolvedExpr, isArr, err := a.getSenderPortAddrType(
   651  		*senderSide.PortAddr,
   652  		scope,
   653  		iface,
   654  		nodes,
   655  		nodesIfaces,
   656  	)
   657  	if err != nil {
   658  		return src.ConnectionSenderSide{}, ts.Expr{}, false, err
   659  	}
   660  
   661  	return src.ConnectionSenderSide{
   662  		PortAddr:  senderSide.PortAddr,
   663  		Selectors: senderSide.Selectors,
   664  		Meta:      senderSide.Meta,
   665  	}, resolvedExpr, isArr, nil
   666  }
   667  
   668  // getSenderPortAddrType returns port's type and isArray bool
   669  func (a Analyzer) getSenderPortAddrType(
   670  	senderSidePortAddr src.PortAddr,
   671  	scope src.Scope,
   672  	iface src.Interface,
   673  	nodes map[string]src.Node,
   674  	nodesIfaces map[string]foundInterface,
   675  ) (ts.Expr, bool, *compiler.Error) {
   676  	if senderSidePortAddr.Node == "out" {
   677  		return ts.Expr{}, false, &compiler.Error{
   678  			Err:      ErrReadSelfOut,
   679  			Location: &scope.Location,
   680  			Meta:     &senderSidePortAddr.Meta,
   681  		}
   682  	}
   683  
   684  	if senderSidePortAddr.Node == "in" {
   685  		inports := iface.IO.In
   686  
   687  		inport, ok := inports[senderSidePortAddr.Port]
   688  		if !ok {
   689  			return ts.Expr{}, false, &compiler.Error{
   690  				Err:      fmt.Errorf("%w: %v", ErrInportNotFound, senderSidePortAddr.Port),
   691  				Location: &scope.Location,
   692  				Meta:     &senderSidePortAddr.Meta,
   693  			}
   694  		}
   695  
   696  		resolvedInportType, err := a.resolver.ResolveExprWithFrame(
   697  			inport.TypeExpr,
   698  			iface.TypeParams.ToFrame(),
   699  			scope,
   700  		)
   701  		if err != nil {
   702  			return ts.Expr{}, false, &compiler.Error{
   703  				Err:      err,
   704  				Location: &scope.Location,
   705  				Meta:     &senderSidePortAddr.Meta,
   706  			}
   707  		}
   708  
   709  		return resolvedInportType, inport.IsArray, nil
   710  	}
   711  
   712  	return a.getNodeOutportType(
   713  		senderSidePortAddr, nodes, nodesIfaces, scope,
   714  	)
   715  }
   716  
   717  func (a Analyzer) getResolvedSenderConstType(
   718  	constSender src.Const,
   719  	scope src.Scope,
   720  ) (src.Const, ts.Expr, *compiler.Error) {
   721  	if constSender.Ref != nil {
   722  		expr, err := a.getResolvedConstTypeByRef(*constSender.Ref, scope)
   723  		if err != nil {
   724  			return src.Const{}, ts.Expr{}, compiler.Error{
   725  				Location: &scope.Location,
   726  				Meta:     &constSender.Ref.Meta,
   727  			}.Wrap(err)
   728  		}
   729  		return constSender, expr, nil
   730  	}
   731  
   732  	if constSender.Message == nil {
   733  		return src.Const{}, ts.Expr{}, &compiler.Error{
   734  			Err:      ErrLiteralSenderTypeEmpty,
   735  			Location: &scope.Location,
   736  			Meta:     &constSender.Meta,
   737  		}
   738  	}
   739  
   740  	resolvedExpr, err := a.resolver.ResolveExpr(
   741  		constSender.Message.TypeExpr,
   742  		scope,
   743  	)
   744  	if err != nil {
   745  		return src.Const{}, ts.Expr{}, &compiler.Error{
   746  			Err:      err,
   747  			Location: &scope.Location,
   748  			Meta:     &constSender.Message.Meta,
   749  		}
   750  	}
   751  
   752  	if err := a.validateLiteralSender(resolvedExpr); err != nil {
   753  		return src.Const{}, ts.Expr{}, &compiler.Error{
   754  			Err:      err,
   755  			Location: &scope.Location,
   756  			Meta:     &constSender.Message.Meta,
   757  		}
   758  	}
   759  
   760  	return src.Const{
   761  		Message: &src.Message{
   762  			TypeExpr:    resolvedExpr,
   763  			Bool:        constSender.Message.Bool,
   764  			Int:         constSender.Message.Int,
   765  			Float:       constSender.Message.Float,
   766  			Str:         constSender.Message.Str,
   767  			List:        constSender.Message.List,
   768  			MapOrStruct: constSender.Message.MapOrStruct,
   769  			Enum:        constSender.Message.Enum,
   770  			Meta:        constSender.Message.Meta,
   771  		},
   772  		Meta: constSender.Meta,
   773  	}, resolvedExpr, nil
   774  }
   775  
   776  func (a Analyzer) validateLiteralSender(resolvedExpr ts.Expr) error {
   777  	if resolvedExpr.Inst != nil {
   778  		switch resolvedExpr.Inst.Ref.String() {
   779  		case "bool", "int", "float", "string":
   780  			return nil
   781  		}
   782  		return ErrComplexLiteralSender
   783  	}
   784  
   785  	if resolvedExpr.Lit == nil ||
   786  		resolvedExpr.Lit.Enum == nil {
   787  		return ErrComplexLiteralSender
   788  	}
   789  
   790  	return nil
   791  }
   792  
   793  // getNodeOutportType returns port's type and isArray bool
   794  func (a Analyzer) getNodeOutportType(
   795  	portAddr src.PortAddr,
   796  	nodes map[string]src.Node,
   797  	nodesIfaces map[string]foundInterface,
   798  	scope src.Scope,
   799  ) (ts.Expr, bool, *compiler.Error) {
   800  	node, ok := nodes[portAddr.Node]
   801  	if !ok {
   802  		return ts.Expr{}, false, &compiler.Error{
   803  			Err:      fmt.Errorf("%w: %v", ErrNodeNotFound, portAddr.Node),
   804  			Location: &scope.Location,
   805  			Meta:     &portAddr.Meta,
   806  		}
   807  	}
   808  
   809  	nodeIface, ok := nodesIfaces[portAddr.Node]
   810  	if !ok {
   811  		return ts.Expr{}, false, &compiler.Error{
   812  			Err:      fmt.Errorf("%w: %v", ErrNodeNotFound, portAddr.Node),
   813  			Location: &scope.Location,
   814  			Meta:     &portAddr.Meta,
   815  		}
   816  	}
   817  
   818  	return a.getResolvedPortType(
   819  		nodeIface.iface.IO.Out,
   820  		nodeIface.iface.TypeParams.Params,
   821  		portAddr,
   822  		node,
   823  		scope.WithLocation(nodeIface.location),
   824  	)
   825  }
   826  
   827  func (a Analyzer) getResolvedConstTypeByRef(ref core.EntityRef, scope src.Scope) (ts.Expr, *compiler.Error) {
   828  	entity, location, err := scope.Entity(ref)
   829  	if err != nil {
   830  		return ts.Expr{}, &compiler.Error{
   831  			Err:      err,
   832  			Location: &scope.Location,
   833  			Meta:     &ref.Meta,
   834  		}
   835  	}
   836  
   837  	if entity.Kind != src.ConstEntity {
   838  		return ts.Expr{}, &compiler.Error{
   839  			Err:      fmt.Errorf("%w: %v", errors.New("Entity found but is not constant"), entity.Kind),
   840  			Location: &location,
   841  			Meta:     entity.Meta(),
   842  		}
   843  	}
   844  
   845  	if entity.Const.Ref != nil {
   846  		expr, err := a.getResolvedConstTypeByRef(*entity.Const.Ref, scope)
   847  		if err != nil {
   848  			return ts.Expr{}, compiler.Error{
   849  				Location: &location,
   850  				Meta:     &entity.Const.Meta,
   851  			}.Wrap(err)
   852  		}
   853  		return expr, nil
   854  	}
   855  
   856  	resolvedExpr, err := a.resolver.ResolveExpr(entity.Const.Message.TypeExpr, scope)
   857  	if err != nil {
   858  		return ts.Expr{}, &compiler.Error{
   859  			Err:      err,
   860  			Location: &scope.Location,
   861  			Meta:     &entity.Const.Message.Meta,
   862  		}
   863  	}
   864  
   865  	return resolvedExpr, nil
   866  }
   867  
   868  func (a Analyzer) getStructFieldTypeByPath(
   869  	senderType ts.Expr,
   870  	path []string,
   871  	scope src.Scope,
   872  ) (ts.Expr, error) {
   873  	if len(path) == 0 {
   874  		return senderType, nil
   875  	}
   876  
   877  	if senderType.Lit == nil || senderType.Lit.Struct == nil {
   878  		return ts.Expr{}, fmt.Errorf("Type not struct: %v", senderType.String())
   879  	}
   880  
   881  	curField := path[0]
   882  	fieldType, ok := senderType.Lit.Struct[curField]
   883  	if !ok {
   884  		return ts.Expr{}, fmt.Errorf("struct field '%v' not found", curField)
   885  	}
   886  
   887  	return a.getStructFieldTypeByPath(fieldType, path[1:], scope)
   888  }