Skip to content

Commit

Permalink
Merge pull request #340 from OpShin/feat/improved_linting
Browse files Browse the repository at this point in the history
Improve linting output and display of warnings
  • Loading branch information
nielstron authored Mar 4, 2024
2 parents 7f0e840 + 09ed8ef commit 83c4385
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 20 deletions.
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ script:
- >
coverage run -a --source=opshin -m opshin build spending examples/smart_contracts/wrapped_token.py '{"bytes": "ae810731b5d21c0d182d89c60a1eff7095dffd1c0dce8707a8611099"}' '{"bytes": "4d494c4b"}' '{"int": 1000000}' --force-three-params
- >
test ! -n "$(coverage run -a --source=opshin -m opshin lint any examples/smart_contracts/wrapped_token.py)"
test ! -n "$(coverage run -a --source=opshin -m opshin lint any examples/smart_contracts/always_true.py)"
- >
test -n "$(coverage run -a --source=opshin -m opshin lint any examples/smart_contracts/wrapped_token.py)"
- >
test -n "$(coverage run -a --source=opshin -m opshin lint any examples/broken.py)"
- >
Expand Down
31 changes: 30 additions & 1 deletion opshin/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import inspect

import argparse
import logging
import tempfile

import cbor2
Expand Down Expand Up @@ -28,7 +29,7 @@
Purpose,
PlutusContract,
)
from .util import CompilerError, data_from_json
from .util import CompilerError, data_from_json, OPSHIN_LOG_HANDLER
from .prelude import ScriptContext


Expand Down Expand Up @@ -227,6 +228,8 @@ def check_params(


def perform_command(args):
if args.verbose:
logging.basicConfig(level=logging.DEBUG)
command = Command(args.command)
purpose = Purpose(args.purpose)
input_file = args.input_file if args.input_file != "-" else sys.stdin
Expand Down Expand Up @@ -466,6 +469,12 @@ def parse_args():
action="version",
version=f"opshin {__version__} {__copyright__}",
)
a.add_argument(
"-v",
"--verbose",
action="store_true",
help="Enable verbose logging.",
)
a.add_argument(
"--recursion-limit",
default=sys.getrecursionlimit(),
Expand All @@ -479,8 +488,28 @@ def main():
args = parse_args()
sys.setrecursionlimit(args.recursion_limit)
if Command(args.command) != Command.lint:
OPSHIN_LOG_HANDLER.setFormatter(
logging.Formatter(
f"%(levelname)s for {args.input_file}:%(lineno)d %(message)s"
)
)
perform_command(args)
else:
OPSHIN_LOG_HANDLER.stream = sys.stdout
if args.output_format_json:
OPSHIN_LOG_HANDLER.setFormatter(
logging.Formatter(
'{"line":%(lineno)d,"column":%(col_offset)d,"error_class":"%(levelname)s","message":"%(message)s"}'
)
)
else:
OPSHIN_LOG_HANDLER.setFormatter(
logging.Formatter(
args.input_file
+ ":%(lineno)d:%(col_offset)d:%(levelname)s: %(message)s"
)
)

try:
perform_command(args)
except Exception as e:
Expand Down
8 changes: 3 additions & 5 deletions opshin/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@
RawPlutoExpr,
)

_LOGGER = logging.getLogger(__name__)


BoolOpMap = {
And: plt.And,
Expand Down Expand Up @@ -230,7 +228,7 @@ def visit_Module(self, node: TypedModule) -> plt.AST:
else:
possible_types = [second_last_arg.typ]
if any(isinstance(t, UnitType) for t in possible_types):
_LOGGER.warning(
OPSHIN_LOGGER.warning(
"The redeemer is annotated to be 'None'. This value is usually encoded in PlutusData with constructor id 0 and no fields. If you want the script to double function as minting and spending script, annotate the second argument with 'NoRedeemer'."
)
enable_double_func_mint_spend = not any(
Expand All @@ -239,7 +237,7 @@ def visit_Module(self, node: TypedModule) -> plt.AST:
for t in possible_types
)
if not enable_double_func_mint_spend:
_LOGGER.warning(
OPSHIN_LOGGER.warning(
"The second argument to the validator function potentially has constructor id 0. The validator will not be able to double function as minting script and spending script."
)

Expand Down Expand Up @@ -331,7 +329,7 @@ def visit_Constant(self, node: TypedConstant) -> plt.AST:
except ValueError:
pass
else:
_LOGGER.warning(
OPSHIN_LOGGER.warning(
f"The string {node.value} looks like it is supposed to be a hex-encoded bytestring but is actually utf8-encoded. Try using `bytes.fromhex('{node.value.decode()}')` instead."
)
plt_val = plt.UPLCConstant(rec_constant_map(node.value))
Expand Down
10 changes: 4 additions & 6 deletions opshin/optimize/optimize_const_folding.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@
except NameError:
from astunparse import unparse

from ..util import CompilingNodeTransformer, CompilingNodeVisitor
from ..util import CompilingNodeTransformer, CompilingNodeVisitor, OPSHIN_LOGGER
from ..type_inference import INITIAL_SCOPE

"""
Pre-evaluates constant statements
"""

_LOGGER = logging.getLogger(__name__)

ACCEPTED_ATOMIC_TYPES = [
int,
str,
Expand Down Expand Up @@ -227,7 +225,7 @@ def update_constants(self, node):
try:
exec(unparse(node), g, l)
except Exception as e:
_LOGGER.debug(e)
OPSHIN_LOGGER.debug(e)
else:
# the class is defined and added to the globals
self.scopes_constants[-1].update(l)
Expand Down Expand Up @@ -259,7 +257,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> FunctionDef:
# we need to pass the global dict as local dict here to make closures possible (rec functions)
exec(unparse(node), g, g)
except Exception as e:
_LOGGER.debug(e)
OPSHIN_LOGGER.debug(e)
else:
# the class is defined and added to the globals
self.scopes_constants[-1][node.name] = g[node.name]
Expand Down Expand Up @@ -331,7 +329,7 @@ def generic_visit(self, node: AST):
l = self._constant_vars()
node_eval = eval(node_source, g, l)
except Exception as e:
_LOGGER.debug(e)
OPSHIN_LOGGER.debug(e)
return node

if any(
Expand Down
2 changes: 0 additions & 2 deletions opshin/type_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@

# from frozendict import frozendict

_LOGGER = logging.getLogger(__name__)


INITIAL_SCOPE = {
# class annotations
Expand Down
2 changes: 0 additions & 2 deletions opshin/typed_ast.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from .types import *

_LOGGER = logging.getLogger(__name__)


class TypedAST(AST):
typ: Type
Expand Down
4 changes: 1 addition & 3 deletions opshin/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@

from .util import *

_LOGGER = logging.getLogger(__name__)


class TypeInferenceError(AssertionError):
pass
Expand Down Expand Up @@ -244,7 +242,7 @@ def cmp(self, op: cmpop, o: "Type") -> plt.AST:
return super().cmp(op, o)

def stringify(self, recursive: bool = False) -> plt.AST:
_LOGGER.warning(
OPSHIN_LOGGER.warning(
"Serializing AnyType will result in RawPlutusData (CBOR representation) to be printed without additional type information. Annotate types where possible to avoid this warning."
)
return OLambda(
Expand Down
38 changes: 38 additions & 0 deletions opshin/util.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from _ast import Name, Store, ClassDef, FunctionDef, Load
from collections import defaultdict
from copy import copy, deepcopy
import logging

import typing

Expand All @@ -15,6 +16,40 @@
import pluthon as plt
from hashlib import sha256

OPSHIN_LOGGER = logging.getLogger("opshin")
OPSHIN_LOG_HANDLER = logging.StreamHandler()
OPSHIN_LOGGER.addHandler(OPSHIN_LOG_HANDLER)


class FileContextFilter(logging.Filter):
"""
This is a filter which injects contextual information into the log.
The information is about the currently inspected AST node.
The information needs to be updated inside the NodeTransformer and NodeVisitor classes.
"""

file_name = "unknown"
node: ast.AST = None

def filter(self, record):

if self.node is None:
record.lineno = 1
record.col_offset = 0
record.end_lineno = 1
record.end_col_offset = 0
else:
record.lineno = self.node.lineno
record.col_offset = self.node.col_offset
record.end_lineno = self.node.end_lineno
record.end_col_offset = self.node.end_col_offset
return True


OPSHIN_LOG_CONTEXT_FILTER = FileContextFilter()
OPSHIN_LOG_HANDLER.addFilter(OPSHIN_LOG_CONTEXT_FILTER)


def distinct(xs: list):
"""Returns true iff the list consists of distinct elements"""
Expand All @@ -24,6 +59,7 @@ def distinct(xs: list):
class TypedNodeTransformer(ast.NodeTransformer):
def visit(self, node):
"""Visit a node."""
OPSHIN_LOG_CONTEXT_FILTER.node = node
node_class_name = node.__class__.__name__
if node_class_name.startswith("Typed"):
node_class_name = node_class_name[len("Typed") :]
Expand All @@ -35,6 +71,7 @@ def visit(self, node):
class TypedNodeVisitor(ast.NodeVisitor):
def visit(self, node):
"""Visit a node."""
OPSHIN_LOG_CONTEXT_FILTER.node = node
node_class_name = node.__class__.__name__
if node_class_name.startswith("Typed"):
node_class_name = node_class_name[len("Typed") :]
Expand All @@ -54,6 +91,7 @@ class CompilingNodeTransformer(TypedNodeTransformer):
step = "Node transformation"

def visit(self, node):
OPSHIN_LOG_CONTEXT_FILTER.node = node
try:
return super().visit(node)
except Exception as e:
Expand Down

0 comments on commit 83c4385

Please sign in to comment.