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

     1  // Package analyzer implements source code static semantic analysis.
     2  // It's important to keep errors as human-readable as possible
     3  // because they are what end-user is facing when something goes wrong.
     4  package analyzer
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  
    10  	"golang.org/x/exp/maps"
    11  
    12  	"github.com/nevalang/neva/internal/compiler"
    13  	src "github.com/nevalang/neva/internal/compiler/sourcecode"
    14  	"github.com/nevalang/neva/internal/compiler/sourcecode/core"
    15  	ts "github.com/nevalang/neva/internal/compiler/sourcecode/typesystem"
    16  )
    17  
    18  var (
    19  	ErrModuleWithoutPkgs    = errors.New("Module must contain at least one package")
    20  	ErrEntryModNotFound     = errors.New("Entry module is not found")
    21  	ErrMainPkgNotFound      = errors.New("Main package not found")
    22  	ErrPkgWithoutFiles      = errors.New("Package must contain at least one file")
    23  	ErrUnknownEntityKind    = errors.New("Entity kind can only be either component, interface, type of constant")
    24  	ErrCompilerVersion      = errors.New("Incompatible compiler version")
    25  	ErrDepModWithoutVersion = errors.New("Every dependency module must have version")
    26  )
    27  
    28  type Analyzer struct {
    29  	compilerVersion string
    30  	resolver        ts.Resolver
    31  }
    32  
    33  func (a Analyzer) AnalyzeExecutableBuild(build src.Build, mainPkgName string) (src.Build, *compiler.Error) {
    34  	location := src.Location{
    35  		ModRef:  build.EntryModRef,
    36  		PkgName: mainPkgName,
    37  	}
    38  
    39  	entryMod, ok := build.Modules[build.EntryModRef]
    40  	
    41  	if !ok {
    42  		return src.Build{}, &compiler.Error{
    43  			Err:      fmt.Errorf("%w: main package name '%s'", ErrEntryModNotFound, build.EntryModRef),
    44  			Location: &location,
    45  		}
    46  	}
    47  
    48  	// FIXME mainPkgName containts full path with "examples/ "
    49  	if _, ok := entryMod.Packages[mainPkgName]; !ok {
    50  		return src.Build{}, &compiler.Error{
    51  			Err:      fmt.Errorf("%w: main package name '%s'", ErrMainPkgNotFound, mainPkgName),
    52  			Location: &location,
    53  		}
    54  	}
    55  
    56  	scope := src.Scope{
    57  		Location: location,
    58  		Build:    build,
    59  	}
    60  
    61  	if err := a.mainSpecificPkgValidation(mainPkgName, entryMod, scope); err != nil {
    62  		return src.Build{}, compiler.Error{Location: &location}.Wrap(err)
    63  	}
    64  
    65  	analyzedBuild, err := a.AnalyzeBuild(build)
    66  	if err != nil {
    67  		return src.Build{}, compiler.Error{Location: &location}.Wrap(err)
    68  	}
    69  
    70  	return analyzedBuild, nil
    71  }
    72  
    73  func (a Analyzer) AnalyzeBuild(build src.Build) (src.Build, *compiler.Error) {
    74  	analyzedMods := make(map[src.ModuleRef]src.Module, len(build.Modules))
    75  
    76  	for modRef, mod := range build.Modules {
    77  		if err := a.semverCheck(mod, modRef); err != nil {
    78  			return src.Build{}, err
    79  		}
    80  
    81  		analyzedPkgs, err := a.analyzeModule(modRef, build)
    82  		if err != nil {
    83  			return src.Build{}, err
    84  		}
    85  
    86  		analyzedMods[modRef] = src.Module{
    87  			Manifest: mod.Manifest,
    88  			Packages: analyzedPkgs,
    89  		}
    90  	}
    91  
    92  	return src.Build{
    93  		EntryModRef: build.EntryModRef,
    94  		Modules:     analyzedMods,
    95  	}, nil
    96  }
    97  
    98  func (a Analyzer) analyzeModule(modRef src.ModuleRef, build src.Build) (map[string]src.Package, *compiler.Error) {
    99  	if modRef != build.EntryModRef && modRef.Version == "" {
   100  		return nil, &compiler.Error{
   101  			Err: ErrDepModWithoutVersion,
   102  		}
   103  	}
   104  
   105  	location := src.Location{ModRef: modRef}
   106  	mod := build.Modules[modRef]
   107  
   108  	if len(mod.Packages) == 0 {
   109  		return nil, &compiler.Error{
   110  			Err:      ErrModuleWithoutPkgs,
   111  			Location: &location,
   112  		}
   113  	}
   114  
   115  	pkgsCopy := make(map[string]src.Package, len(mod.Packages))
   116  	maps.Copy(pkgsCopy, mod.Packages)
   117  
   118  	for pkgName, pkg := range pkgsCopy {
   119  		scope := src.Scope{
   120  			Location: src.Location{
   121  				ModRef:  modRef,
   122  				PkgName: pkgName,
   123  			},
   124  			Build: build,
   125  		}
   126  
   127  		resolvedPkg, err := a.analyzePkg(pkg, scope)
   128  		if err != nil {
   129  			return nil, compiler.Error{
   130  				Location: &src.Location{
   131  					PkgName: pkgName,
   132  				},
   133  			}.Wrap(err)
   134  		}
   135  
   136  		pkgsCopy[pkgName] = resolvedPkg
   137  	}
   138  
   139  	return pkgsCopy, nil
   140  }
   141  
   142  func (a Analyzer) analyzePkg(pkg src.Package, scope src.Scope) (src.Package, *compiler.Error) {
   143  	if len(pkg) == 0 {
   144  		return nil, &compiler.Error{
   145  			Err:      ErrPkgWithoutFiles,
   146  			Location: &scope.Location,
   147  		}
   148  	}
   149  
   150  	// preallocate
   151  	analyzedFiles := make(map[string]src.File, len(pkg))
   152  	for fileName, file := range pkg {
   153  		analyzedFiles[fileName] = src.File{
   154  			Imports:  file.Imports,
   155  			Entities: make(map[string]src.Entity, len(file.Entities)),
   156  		}
   157  	}
   158  
   159  	if err := pkg.Entities(func(entity src.Entity, entityName, fileName string) error {
   160  		scopeWithFile := scope.WithLocation(src.Location{
   161  			FileName: fileName,
   162  			ModRef:   scope.Location.ModRef,
   163  			PkgName:  scope.Location.PkgName,
   164  		})
   165  
   166  		resolvedEntity, err := a.analyzeEntity(entity, scopeWithFile)
   167  		if err != nil {
   168  			return compiler.Error{
   169  				Location: &scopeWithFile.Location,
   170  				Meta:     entity.Meta(),
   171  			}.Wrap(err)
   172  		}
   173  
   174  		analyzedFiles[fileName].Entities[entityName] = resolvedEntity
   175  
   176  		return nil
   177  	}); err != nil {
   178  		return nil, err.(*compiler.Error) //nolint:forcetypeassert
   179  	}
   180  
   181  	return analyzedFiles, nil
   182  }
   183  
   184  func (a Analyzer) analyzeEntity(entity src.Entity, scope src.Scope) (src.Entity, *compiler.Error) {
   185  	resolvedEntity := src.Entity{
   186  		IsPublic: entity.IsPublic,
   187  		Kind:     entity.Kind,
   188  	}
   189  
   190  	isStd := scope.Location.ModRef.Path == "std"
   191  
   192  	switch entity.Kind {
   193  	case src.TypeEntity:
   194  		resolvedTypeDef, err := a.analyzeTypeDef(entity.Type, scope, analyzeTypeDefParams{allowEmptyBody: isStd})
   195  		if err != nil {
   196  			meta := entity.Type.Meta.(core.Meta) //nolint:forcetypeassert
   197  			return src.Entity{}, compiler.Error{
   198  				Location: &scope.Location,
   199  				Meta:     &meta,
   200  			}.Wrap(err)
   201  		}
   202  		resolvedEntity.Type = resolvedTypeDef
   203  	case src.ConstEntity:
   204  		resolvedConst, err := a.analyzeConst(entity.Const, scope)
   205  		if err != nil {
   206  			meta := entity.Const.Meta
   207  			return src.Entity{}, compiler.Error{
   208  				Location: &scope.Location,
   209  				Meta:     &meta,
   210  			}.Wrap(err)
   211  		}
   212  		resolvedEntity.Const = resolvedConst
   213  	case src.InterfaceEntity:
   214  		resolvedInterface, err := a.analyzeInterface(entity.Interface, scope, analyzeInterfaceParams{
   215  			allowEmptyInports:  false,
   216  			allowEmptyOutports: false,
   217  		})
   218  		if err != nil {
   219  			meta := entity.Interface.Meta
   220  			return src.Entity{}, compiler.Error{
   221  				Location: &scope.Location,
   222  				Meta:     &meta,
   223  			}.Wrap(err)
   224  		}
   225  		resolvedEntity.Interface = resolvedInterface
   226  	case src.ComponentEntity:
   227  		analyzedComponent, err := a.analyzeComponent(entity.Component, scope)
   228  		if err != nil {
   229  			return src.Entity{}, compiler.Error{
   230  				Location: &scope.Location,
   231  				Meta:     &entity.Component.Meta,
   232  			}.Wrap(err)
   233  		}
   234  		resolvedEntity.Component = analyzedComponent
   235  	default:
   236  		return src.Entity{}, &compiler.Error{
   237  			Err:      fmt.Errorf("%w: %v", ErrUnknownEntityKind, entity.Kind),
   238  			Location: &scope.Location,
   239  			Meta:     entity.Meta(),
   240  		}
   241  	}
   242  
   243  	return resolvedEntity, nil
   244  }
   245  
   246  func MustNew(version string, resolver ts.Resolver) Analyzer {
   247  	return Analyzer{
   248  		compilerVersion: version,
   249  		resolver:        resolver,
   250  	}
   251  }