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 }